diff --git a/.gitignore b/.gitignore index 123812cc..f4461f60 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ /db/*.sqlite3 /db/schema.rb /files/* +/lib/redmine/scm/adapters/mercurial/redminehelper.pyc +/lib/redmine/scm/adapters/mercurial/redminehelper.pyo /log/*.log* /log/mongrel_debug /public/dispatch.* diff --git a/.hgignore b/.hgignore index 733faf9f..e407dd69 100644 --- a/.hgignore +++ b/.hgignore @@ -12,6 +12,8 @@ db/*.db db/*.sqlite3 db/schema.rb files/* +lib/redmine/scm/adapters/mercurial/redminehelper.pyc +lib/redmine/scm/adapters/mercurial/redminehelper.pyo log/*.log* log/mongrel_debug public/dispatch.* @@ -23,3 +25,5 @@ tmp/sockets/* tmp/test/* vendor/rails *.rbc +.svn/ +.git/ diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 29e4e4b0..db56eff9 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -132,7 +132,7 @@ class GroupsController < ApplicationController def autocomplete_for_user @group = Group.find(params[:id]) - @users = User.active.like(params[:q]).find(:all, :limit => 100) - @group.users + @users = User.active.not_in_group(@group).like(params[:q]).all(:limit => 100) render :layout => false end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 051f8910..fdce296c 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -112,7 +112,7 @@ class IssuesController < ApplicationController @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @priorities = IssuePriority.all - @time_entry = TimeEntry.new + @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) respond_to do |format| format.html { render :template => 'issues/show.rhtml' } format.api @@ -236,7 +236,13 @@ class IssuesController < ApplicationController return unless api_request? end end - @issues.each(&:destroy) + @issues.each do |issue| + begin + issue.reload.destroy + rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists + # nothing to do, issue was already deleted (eg. by a parent) + end + end respond_to do |format| format.html { redirect_back_or_default(:action => 'index', :project_id => @project) } format.api { head :ok } @@ -265,7 +271,7 @@ private @allowed_statuses = @issue.new_statuses_allowed_to(User.current) @priorities = IssuePriority.all @edit_allowed = User.current.allowed_to?(:edit_issues, @project) - @time_entry = TimeEntry.new + @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) @time_entry.attributes = params[:time_entry] @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) diff --git a/app/controllers/journals_controller.rb b/app/controllers/journals_controller.rb index 1f65dc82..ac3cbb67 100644 --- a/app/controllers/journals_controller.rb +++ b/app/controllers/journals_controller.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -16,13 +16,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. class JournalsController < ApplicationController - before_filter :find_journal, :only => [:edit] + before_filter :find_journal, :only => [:edit, :diff] before_filter :find_issue, :only => [:new] before_filter :find_optional_project, :only => [:index] - before_filter :authorize, :only => [:new, :edit] + before_filter :authorize, :only => [:new, :edit, :diff] accept_key_auth :index - + menu_item :issues + helper :issues + helper :custom_fields helper :queries include QueriesHelper helper :sort @@ -44,6 +46,17 @@ class JournalsController < ApplicationController render_404 end + def diff + @issue = @journal.issue + if params[:detail_id].present? + @detail = @journal.details.find_by_id(params[:detail_id]) + else + @detail = @journal.details.detect {|d| d.prop_key == 'description'} + end + (render_404; return false) unless @issue && @detail + @diff = Redmine::Helpers::Diff.new(@detail.value, @detail.old_value) + end + def new journal = Journal.find(params[:journal_id]) if params[:journal_id] if journal @@ -68,6 +81,7 @@ class JournalsController < ApplicationController end def edit + (render_403; return false) unless @journal.editable_by?(User.current) if request.post? @journal.update_attributes(:notes => params[:notes]) if params[:notes] @journal.destroy if @journal.details.empty? && @journal.notes.blank? @@ -76,13 +90,21 @@ class JournalsController < ApplicationController format.html { redirect_to :controller => 'issues', :action => 'show', :id => @journal.journalized_id } format.js { render :action => 'update' } end + else + respond_to do |format| + format.html { + # TODO: implement non-JS journal update + render :nothing => true + } + format.js + end end end -private + private + def find_journal @journal = Journal.find(params[:id]) - (render_403; return false) unless @journal.editable_by?(User.current) @project = @journal.journalized.project rescue ActiveRecord::RecordNotFound render_404 diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index e7c643e9..bc7eabb5 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -25,6 +25,8 @@ class NewsController < ApplicationController before_filter :find_optional_project, :only => :index accept_key_auth :index + helper :watchers + def index case params[:format] when 'xml', 'json' diff --git a/app/controllers/previews_controller.rb b/app/controllers/previews_controller.rb index 612025cb..f1dbedf7 100644 --- a/app/controllers/previews_controller.rb +++ b/app/controllers/previews_controller.rb @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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. + class PreviewsController < ApplicationController before_filter :find_project diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 459b5478..ea2ddd13 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -143,7 +143,7 @@ class ProjectsController < ApplicationController end @users_by_role = @project.users_by_role - @subprojects = @project.children.visible + @subprojects = @project.children.visible.all @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC") @trackers = @project.rolled_up_trackers @@ -156,11 +156,10 @@ class ProjectsController < ApplicationController :include => [:project, :status, :tracker], :conditions => cond) - TimeEntry.visible_by(User.current) do - @total_hours = TimeEntry.sum(:hours, - :include => :project, - :conditions => cond).to_f + if User.current.allowed_to?(:view_time_entries, @project) + @total_hours = TimeEntry.visible.sum(:hours, :include => :project, :conditions => cond).to_f end + @key = User.current.rss_key respond_to do |format| diff --git a/app/controllers/queries_controller.rb b/app/controllers/queries_controller.rb index 0b798636..60f3b445 100644 --- a/app/controllers/queries_controller.rb +++ b/app/controllers/queries_controller.rb @@ -25,10 +25,11 @@ class QueriesController < ApplicationController @query.project = params[:query_is_for_all] ? nil : @project @query.user = User.current @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? - @query.column_names = nil if params[:default_columns] - @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields] + @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f] @query.group_by ||= params[:group_by] + @query.column_names = params[:c] if params[:c] + @query.column_names = nil if params[:default_columns] if request.post? && params[:confirm] && @query.save flash[:notice] = l(:notice_successful_create) @@ -41,10 +42,12 @@ class QueriesController < ApplicationController def edit if request.post? @query.filters = {} - @query.add_filters(params[:fields], params[:operators], params[:values]) if params[:fields] + @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) if params[:fields] || params[:f] @query.attributes = params[:query] @query.project = nil if params[:query_is_for_all] @query.is_public = false unless User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? + @query.group_by ||= params[:group_by] + @query.column_names = params[:c] if params[:c] @query.column_names = nil if params[:default_columns] if @query.save diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index de44f1d0..47547250 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -77,6 +77,7 @@ class RepositoriesController < ApplicationController @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? @entries = @repository.entries(@path, @rev) + @changeset = @repository.find_changeset_by_name(@rev) if request.xhr? @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) else @@ -122,17 +123,35 @@ class RepositoriesController < ApplicationController @content = @repository.cat(@path, @rev) (show_error_not_found; return) unless @content - if 'raw' == params[:format] || @content.is_binary_data? || - (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte) + if 'raw' == params[:format] || + (@content.size && @content.size > Setting.file_max_size_displayed.to_i.kilobyte) || + ! is_entry_text_data?(@content, @path) # Force the download - send_data @content, :filename => filename_for_content_disposition(@path.split('/').last) + send_opt = { :filename => filename_for_content_disposition(@path.split('/').last) } + send_type = Redmine::MimeType.of(@path) + send_opt[:type] = send_type.to_s if send_type + send_data @content, send_opt else # Prevent empty lines when displaying a file with Windows style eol + # TODO: UTF-16 + # Is this needs? AttachmentsController reads file simply. @content.gsub!("\r\n", "\n") @changeset = @repository.find_changeset_by_name(@rev) - end + end end + def is_entry_text_data?(ent, path) + # UTF-16 contains "\x00". + # It is very strict that file contains less than 30% of ascii symbols + # in non Western Europe. + return true if Redmine::MimeType.is_type?('text', path) + # Ruby 1.8.6 has a bug of integer divisions. + # http://apidock.com/ruby/v1_8_6_287/String/is_binary_data%3F + return false if ent.is_binary_data? + true + end + private :is_entry_text_data? + def annotate @entry = @repository.entry(@path, @rev) (show_error_not_found; return) unless @entry @@ -218,7 +237,7 @@ class RepositoriesController < ApplicationController @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip @rev_to = params[:rev_to] - unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) + unless @rev.to_s.match(REV_PARAM_RE) && @rev_to.to_s.match(REV_PARAM_RE) if @repository.branches.blank? raise InvalidRevisionParam end diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb index f75d5833..604a8f24 100644 --- a/app/controllers/timelog_controller.rb +++ b/app/controllers/timelog_controller.rb @@ -40,60 +40,56 @@ class TimelogController < ApplicationController 'hours' => 'hours' cond = ARCondition.new - if @project.nil? - cond << Project.allowed_to_condition(User.current, :view_time_entries) - elsif @issue.nil? - cond << @project.project_condition(Setting.display_subprojects_issues?) - else + if @issue cond << "#{Issue.table_name}.root_id = #{@issue.root_id} AND #{Issue.table_name}.lft >= #{@issue.lft} AND #{Issue.table_name}.rgt <= #{@issue.rgt}" + elsif @project + cond << @project.project_condition(Setting.display_subprojects_issues?) end retrieve_date_range cond << ['spent_on BETWEEN ? AND ?', @from, @to] - TimeEntry.visible_by(User.current) do - respond_to do |format| - format.html { - # Paginate results - @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions) - @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] - @entries = TimeEntry.find(:all, - :include => [:project, :activity, :user, {:issue => :tracker}], - :conditions => cond.conditions, - :order => sort_clause, - :limit => @entry_pages.items_per_page, - :offset => @entry_pages.current.offset) - @total_hours = TimeEntry.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f + respond_to do |format| + format.html { + # Paginate results + @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions) + @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] + @entries = TimeEntry.visible.find(:all, + :include => [:project, :activity, :user, {:issue => :tracker}], + :conditions => cond.conditions, + :order => sort_clause, + :limit => @entry_pages.items_per_page, + :offset => @entry_pages.current.offset) + @total_hours = TimeEntry.visible.sum(:hours, :include => [:project, :issue], :conditions => cond.conditions).to_f - render :layout => !request.xhr? - } - format.api { - @entry_count = TimeEntry.count(:include => [:project, :issue], :conditions => cond.conditions) - @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] - @entries = TimeEntry.find(:all, - :include => [:project, :activity, :user, {:issue => :tracker}], - :conditions => cond.conditions, - :order => sort_clause, - :limit => @entry_pages.items_per_page, - :offset => @entry_pages.current.offset) - } - format.atom { - entries = TimeEntry.find(:all, - :include => [:project, :activity, :user, {:issue => :tracker}], - :conditions => cond.conditions, - :order => "#{TimeEntry.table_name}.created_on DESC", - :limit => Setting.feeds_limit.to_i) - render_feed(entries, :title => l(:label_spent_time)) - } - format.csv { - # Export all entries - @entries = TimeEntry.find(:all, - :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], - :conditions => cond.conditions, - :order => sort_clause) - send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') - } - end + render :layout => !request.xhr? + } + format.api { + @entry_count = TimeEntry.visible.count(:include => [:project, :issue], :conditions => cond.conditions) + @entry_pages = Paginator.new self, @entry_count, per_page_option, params['page'] + @entries = TimeEntry.visible.find(:all, + :include => [:project, :activity, :user, {:issue => :tracker}], + :conditions => cond.conditions, + :order => sort_clause, + :limit => @entry_pages.items_per_page, + :offset => @entry_pages.current.offset) + } + format.atom { + entries = TimeEntry.visible.find(:all, + :include => [:project, :activity, :user, {:issue => :tracker}], + :conditions => cond.conditions, + :order => "#{TimeEntry.table_name}.created_on DESC", + :limit => Setting.feeds_limit.to_i) + render_feed(entries, :title => l(:label_spent_time)) + } + format.csv { + # Export all entries + @entries = TimeEntry.visible.find(:all, + :include => [:project, :activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], + :conditions => cond.conditions, + :order => sort_clause) + send_data(entries_to_csv(@entries), :type => 'text/csv; header=present', :filename => 'timelog.csv') + } end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c7dadf04..c0ab99a3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2010 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -38,6 +38,9 @@ class UsersController < ApplicationController @limit = per_page_option end + scope = User + scope = scope.in_group(params[:group_id].to_i) if params[:group_id].present? + @status = params[:status] ? params[:status].to_i : 1 c = ARCondition.new(@status == 0 ? "status <> 0" : ["status = ?", @status]) @@ -46,19 +49,22 @@ class UsersController < ApplicationController c << ["LOWER(login) LIKE ? OR LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(mail) LIKE ?", name, name, name, name] end - @user_count = User.count(:conditions => c.conditions) + @user_count = scope.count(:conditions => c.conditions) @user_pages = Paginator.new self, @user_count, @limit, params['page'] @offset ||= @user_pages.current.offset - @users = User.find :all, + @users = scope.find :all, :order => sort_clause, :conditions => c.conditions, :limit => @limit, :offset => @offset - respond_to do |format| - format.html { render :layout => !request.xhr? } + respond_to do |format| + format.html { + @groups = Group.all.sort + render :layout => !request.xhr? + } format.api - end + end end def show diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index e7e38930..5b87f902 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -44,7 +44,14 @@ class WikiController < ApplicationController # List of pages, sorted alphabetically and by parent (hierarchy) def index - load_pages_grouped_by_date_without_content + load_pages_for_index + @pages_by_parent_id = @pages.group_by(&:parent_id) + end + + # List of page, by last update + def date_index + load_pages_for_index + @pages_by_date = @pages.group_by {|p| p.updated_on.to_date} end # display a page (in editing mode if it doesn't exist) @@ -93,9 +100,6 @@ class WikiController < ApplicationController # To prevent StaleObjectError exception when reverting to a previous version @content.version = @page.content.version - rescue ActiveRecord::StaleObjectError - # Optimistic locking exception - flash[:error] = l(:notice_locking_conflict) end verify :method => :put, :only => :update, :render => {:nothing => true, :status => :method_not_allowed } @@ -131,7 +135,8 @@ class WikiController < ApplicationController rescue ActiveRecord::StaleObjectError # Optimistic locking exception - flash[:error] = l(:notice_locking_conflict) + flash.now[:error] = l(:notice_locking_conflict) + render :action => 'edit' end # rename a page @@ -215,10 +220,6 @@ class WikiController < ApplicationController redirect_to :action => 'show', :project_id => @project, :id => nil end end - - def date_index - load_pages_grouped_by_date_without_content - end def preview page = @wiki.find_page(params[:id]) @@ -266,14 +267,8 @@ private extend helper unless self.instance_of?(helper) helper.instance_method(:initial_page_content).bind(self).call(page) end - - # eager load information about last updates, without loading text - def load_pages_grouped_by_date_without_content - @pages = @wiki.pages.find :all, :select => "#{WikiPage.table_name}.*, #{WikiContent.table_name}.updated_on", - :joins => "LEFT JOIN #{WikiContent.table_name} ON #{WikiContent.table_name}.page_id = #{WikiPage.table_name}.id", - :order => 'title' - @pages_by_date = @pages.group_by {|p| p.updated_on.to_date} - @pages_by_parent_id = @pages.group_by(&:parent_id) - end + def load_pages_for_index + @pages = @wiki.pages.with_updated_on.all(:order => 'title', :include => {:wiki => :project}) + end end diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index ed464012..9ca1a98c 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -32,14 +32,17 @@ class WorkflowsController < ApplicationController if request.post? Workflow.destroy_all( ["role_id=? and tracker_id=?", @role.id, @tracker.id]) - (params[:issue_status] || []).each { |old, news| - news.each { |new| - @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => old, :new_status_id => new) + (params[:issue_status] || []).each { |status_id, transitions| + transitions.each { |new_status_id, options| + author = options.is_a?(Array) && options.include?('author') && !options.include?('always') + assignee = options.is_a?(Array) && options.include?('assignee') && !options.include?('always') + @role.workflows.build(:tracker_id => @tracker.id, :old_status_id => status_id, :new_status_id => new_status_id, :author => author, :assignee => assignee) } } if @role.save flash[:notice] = l(:notice_successful_update) redirect_to :action => 'edit', :role_id => @role, :tracker_id => @tracker + return end end @@ -48,6 +51,14 @@ class WorkflowsController < ApplicationController @statuses = @tracker.issue_statuses end @statuses ||= IssueStatus.find(:all, :order => 'position') + + if @tracker && @role && @statuses.any? + workflows = Workflow.all(:conditions => {:role_id => @role.id, :tracker_id => @tracker.id}) + @workflows = {} + @workflows['always'] = workflows.select {|w| !w.author && !w.assignee} + @workflows['author'] = workflows.select {|w| w.author} + @workflows['assignee'] = workflows.select {|w| w.assignee} + end end def copy diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 67509b22..69d55f34 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2010 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -187,15 +187,15 @@ module ApplicationHelper end end - def render_page_hierarchy(pages, node=nil) + def render_page_hierarchy(pages, node=nil, options={}) content = '' if pages[node] content << "\n" @@ -223,8 +223,7 @@ module ApplicationHelper # Renders the project quick-jump box def render_project_jump_box - # Retrieve them now to avoid a COUNT query - projects = User.current.projects.all + projects = User.current.memberships.collect(&:project).compact.uniq if projects.any? s = ' - <%= select_tag 'query[column_names][]', + <%= select_tag 'c[]', options_for_select(query.columns.collect {|column| [column.caption, column.name]}), :id => 'selected_columns', :multiple => true, :size => 10, :style => "width:150px" %> diff --git a/app/views/queries/_filters.rhtml b/app/views/queries/_filters.rhtml index 29f916eb..1fb59d34 100644 --- a/app/views/queries/_filters.rhtml +++ b/app/views/queries/_filters.rhtml @@ -21,10 +21,14 @@ function toggle_filter(field) { if (check_box.checked) { Element.show("operators_" + field); + Form.Element.enable("operators_" + field); + Form.Element.enable("values_" + field); toggle_operator(field); } else { Element.hide("operators_" + field); Element.hide("div_values_" + field); + Form.Element.disable("operators_" + field); + Form.Element.disable("values_" + field); } } @@ -77,26 +81,26 @@ Event.observe(document,"dom:loaded", apply_filters_observer); options = filter[1] %> id="tr_<%= field %>" class="filter"> - <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> + <%= check_box_tag 'f[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> - <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %> + <%= select_tag "op[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %> @@ -114,4 +118,4 @@ Event.observe(document,"dom:loaded", apply_filters_observer); -<%= hidden_field_tag 'fields[]', '' %> +<%= hidden_field_tag 'f[]', '' %> diff --git a/app/views/repositories/_breadcrumbs.rhtml b/app/views/repositories/_breadcrumbs.rhtml index 1dd2445c..63236800 100644 --- a/app/views/repositories/_breadcrumbs.rhtml +++ b/app/views/repositories/_breadcrumbs.rhtml @@ -10,12 +10,19 @@ dirs.each do |dir| link_path << '/' unless link_path.empty? link_path << "#{dir}" %> - / <%= link_to h(dir), :action => 'show', :id => @project, :path => to_path_param(link_path), :rev => @rev %> + / <%= link_to h(dir), :action => 'show', :id => @project, + :path => to_path_param(link_path), :rev => @rev %> <% end %> <% if filename %> - / <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> + / <%= link_to h(filename), + :action => 'changes', :id => @project, + :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> <% end %> - -<%= "@ #{h format_revision(@changeset)}" if @changeset %> +<% + # @rev is revsion or Git and Mercurial branch or tag. + # For Mercurial *tip*, @rev and @changeset are nil. + rev_text = @changeset.nil? ? @rev : format_revision(@changeset) +%> +<%= "@ #{h rev_text}" unless rev_text.blank? %> <% html_title(with_leading_slash(path)) -%> diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml index 66574f1c..fd9dd7af 100644 --- a/app/views/repositories/_dir_list_content.rhtml +++ b/app/views/repositories/_dir_list_content.rhtml @@ -1,25 +1,27 @@ <% @entries.each do |entry| %> <% tr_id = Digest::MD5.hexdigest(entry.path) depth = params[:depth].to_i %> +<% ent_path = replace_invalid_utf8(entry.path) %> +<% ent_name = replace_invalid_utf8(entry.name) %> <% if entry.is_dir? %> - "scmEntryClick('#{tr_id}')"%>">  <% end %> -<%= link_to h(entry.name), - {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev}, - :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(entry.name)}")%> +<%= link_to h(ent_name), + {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(ent_path), :rev => @rev}, + :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%> <%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %> -<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> +<% changeset = @project.repository.find_changeset_by_name(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> <%= link_to_revision(changeset, @project) if changeset %> <%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %> -<%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %> +<%= changeset.nil? ? h(replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : changeset.author if entry.lastrev %> <%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %> <% end %> diff --git a/app/views/repositories/show.rhtml b/app/views/repositories/show.rhtml index 35106fe6..fc148e8d 100644 --- a/app/views/repositories/show.rhtml +++ b/app/views/repositories/show.rhtml @@ -12,23 +12,49 @@ <%= render_properties(@properties) %> -<% if @changesets && !@changesets.empty? && authorize_for('repositories', 'revisions') %> +<% if authorize_for('repositories', 'revisions') %> +<% if @changesets && !@changesets.empty? %>

<%= l(:label_latest_revision_plural) %>

-<%= render :partial => 'revisions', :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => nil }%> +<%= render :partial => 'revisions', + :locals => {:project => @project, :path => @path, + :revisions => @changesets, :entry => nil }%> +<% end %> +

+<% + has_branches = (!@repository.branches.nil? && @repository.branches.length > 0) + sep = '' + %> +<% if @repository.supports_all_revisions? && @path.blank? %> +<%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project %> +<% sep = '|' %> +<% end %> +<% + if @repository.supports_directory_revisions? && + ( has_branches || !@path.blank? || !@rev.blank? ) + %> +<%= sep %> +<%= + link_to l(:label_view_revisions), + :action => 'changes', + :path => to_path_param(@path), + :id => @project, + :rev => @rev + %> +<% end %> +

-<% if @path.blank? %> -

<%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project %>

-<% else %> -

<%= link_to l(:label_view_revisions), :action => 'changes', :path => to_path_param(@path), :id => @project %>

-<% end %> +<% if true # @path.blank? %> +<% content_for :header_tags do %> + <%= auto_discovery_link_tag( + :atom, params.merge( + {:format => 'atom', :action => 'revisions', + :id => @project, :page => nil, :key => User.current.rss_key})) %> +<% end %> -<% content_for :header_tags do %> - <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :action => 'revisions', :id => @project, :page => nil, :key => User.current.rss_key})) %> -<% end %> - -<% other_formats_links do |f| %> - <%= f.link_to 'Atom', :url => {:action => 'revisions', :id => @project, :key => User.current.rss_key} %> -<% end %> +<% other_formats_links do |f| %> + <%= f.link_to 'Atom', :url => {:action => 'revisions', :id => @project, :key => User.current.rss_key} %> +<% end %> +<% end %> <% end %> <% content_for :header_tags do %> diff --git a/app/views/roles/report.rhtml b/app/views/roles/report.rhtml index e2b01994..77cf41fb 100644 --- a/app/views/roles/report.rhtml +++ b/app/views/roles/report.rhtml @@ -2,6 +2,7 @@ <% form_tag({:action => 'report'}, :id => 'permissions_form') do %> <%= hidden_field_tag 'permissions[0]', '', :id => nil %> +
@@ -21,7 +22,7 @@ <% unless mod.blank? %> @@ -45,6 +46,7 @@ <% end %>
-   +   <%= l_or_humanize(mod, :prefix => 'project_module_') %>
+

<%= check_all_links 'permissions_form' %>

<%= submit_tag l(:button_save) %>

<% end %> diff --git a/app/views/search/index.rhtml b/app/views/search/index.rhtml index 348421d3..13ec567d 100644 --- a/app/views/search/index.rhtml +++ b/app/views/search/index.rhtml @@ -35,16 +35,12 @@

<% if @pagination_previous_date %> -<%= link_to_remote ('« ' + l(:label_previous)), - {:update => :content, - :url => params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S")) - }, :href => url_for(params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>  +<%= link_to_content_update('« ' + l(:label_previous), + params.merge(:previous => 1, :offset => @pagination_previous_date.strftime("%Y%m%d%H%M%S"))) %>  <% end %> <% if @pagination_next_date %> -<%= link_to_remote (l(:label_next) + ' »'), - {:update => :content, - :url => params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S")) - }, :href => url_for(params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %> +<%= link_to_content_update(l(:label_next) + ' »', + params.merge(:previous => nil, :offset => @pagination_next_date.strftime("%Y%m%d%H%M%S"))) %> <% end %>

diff --git a/app/views/settings/_display.rhtml b/app/views/settings/_display.rhtml index c6fe8339..25f65fc5 100644 --- a/app/views/settings/_display.rhtml +++ b/app/views/settings/_display.rhtml @@ -5,7 +5,7 @@

<%= setting_select :default_language, lang_options_for_select(false) %>

-

<%= setting_select :start_of_week, [[day_name(1),'1'], [day_name(7),'7']], :blank => :label_language_based %>

+

<%= setting_select :start_of_week, [[day_name(1),'1'], [day_name(6),'6'], [day_name(7),'7']], :blank => :label_language_based %>

<%= setting_select :date_format, Setting::DATE_FORMATS.collect {|f| [Date.today.strftime(f), f]}, :blank => :label_language_based %>

@@ -15,7 +15,7 @@

<%= setting_check_box :gravatar_enabled %>

-

<%= setting_select :gravatar_default, [["Wavatars", 'wavatar'], ["Identicons", 'identicon'], ["Monster ids", 'monsterid'], ["Retro", "retro"]], :blank => :label_none %>

+

<%= setting_select :gravatar_default, [["Wavatars", 'wavatar'], ["Identicons", 'identicon'], ["Monster ids", 'monsterid'], ["Retro", 'retro'], ["Mystery man", 'mm']], :blank => :label_none %>

<%= submit_tag l(:button_save) %> diff --git a/app/views/settings/_repositories.rhtml b/app/views/settings/_repositories.rhtml index 9f2b26d4..7f892c2d 100644 --- a/app/views/settings/_repositories.rhtml +++ b/app/views/settings/_repositories.rhtml @@ -18,8 +18,6 @@

<%= setting_text_field :repositories_encodings, :size => 60 %>
<%= l(:text_comma_separated) %>

-

<%= setting_select :commit_logs_encoding, Setting::ENCODINGS %>

-

<%= setting_text_field :repository_log_display_limit, :size => 6 %>

diff --git a/app/views/time_entry_reports/report.rhtml b/app/views/time_entry_reports/report.rhtml index 5ae9d655..de2d1093 100644 --- a/app/views/time_entry_reports/report.rhtml +++ b/app/views/time_entry_reports/report.rhtml @@ -6,13 +6,10 @@

<%= l(:label_spent_time) %>

-<% form_remote_tag(:url => {}, :html => {:method => :get, :id => 'query_form'}, :method => :get, :update => 'content') do %> +<% form_tag({:controller => 'time_entry_reports', :action => 'report', :project_id => @project, :issue_id => @issue}, :method => :get, :id => 'query_form') do %> <% @criterias.each do |criteria| %> <%= hidden_field_tag 'criterias[]', criteria, :id => nil %> <% end %> - <%# TODO: get rid of the project_id field, that should already be in the URL %> - <%= hidden_field_tag('project_id', params[:project_id]) if @project %> - <%= hidden_field_tag('issue_id', params[:issue_id]) if @issue %> <%= render :partial => 'timelog/date_range' %>

<%= l(:label_details) %>: <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'], @@ -22,14 +19,11 @@ :onchange => "this.form.onsubmit();" %> <%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l_or_humanize(@available_criterias[k][:label]), k]}), - :onchange => "this.form.onsubmit();", + :onchange => "this.form.submit();", :style => 'width: 200px', :id => nil, :disabled => (@criterias.length >= 3)) %> - <%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, - :method => :get, - :update => 'content' - }, :class => 'icon icon-reload' %>

+ <%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, :class => 'icon icon-reload' %>

<% end %> <% unless @criterias.empty? %> diff --git a/app/views/timelog/_date_range.rhtml b/app/views/timelog/_date_range.rhtml index 727de25e..e916bc18 100644 --- a/app/views/timelog/_date_range.rhtml +++ b/app/views/timelog/_date_range.rhtml @@ -2,27 +2,24 @@ <%= l(:label_date_range) %>

-<%= radio_button_tag 'period_type', '1', !@free_period %> +<%= radio_button_tag 'period_type', '1', !@free_period, :onclick => 'Form.Element.disable("from");Form.Element.disable("to");Form.Element.enable("period");' %> <%= select_tag 'period', options_for_period_select(params[:period]), - :onchange => 'this.form.onsubmit();', - :onfocus => '$("period_type_1").checked = true;' %> + :onchange => 'this.form.submit();', + :onfocus => '$("period_type_1").checked = true;', + :disabled => @free_period %>

-<%= radio_button_tag 'period_type', '2', @free_period %> +<%= radio_button_tag 'period_type', '2', @free_period, :onclick => 'Form.Element.enable("from");Form.Element.enable("to");Form.Element.disable("period");' %> -<%= l(:label_date_from_to, :start => (text_field_tag('from', @from, :size => 10) + calendar_for('from')), - :end => (text_field_tag('to', @to, :size => 10) + calendar_for('to'))) %> +<%= l(:label_date_from_to, :start => (text_field_tag('from', @from, :size => 10, :disabled => !@free_period) + calendar_for('from')), + :end => (text_field_tag('to', @to, :size => 10, :disabled => !@free_period) + calendar_for('to'))) %>

- <%= link_to_remote l(:button_apply), - { :url => { }, - :update => "content", - :with => "Form.serialize('query_form')", - :method => :get - }, :class => 'icon icon-checked' %> + <%= link_to_function l(:button_apply), '$("query_form").submit()', :class => 'icon icon-checked' %> + <%= link_to l(:button_clear), {:controller => controller_name, :action => action_name, :project_id => @project, :issue_id => @issue}, :class => 'icon icon-reload' %>

diff --git a/app/views/timelog/index.html.erb b/app/views/timelog/index.html.erb index 737476f3..d943d430 100644 --- a/app/views/timelog/index.html.erb +++ b/app/views/timelog/index.html.erb @@ -6,11 +6,7 @@

<%= l(:label_spent_time) %>

-<% form_remote_tag( :url => {}, :html => {:method => :get, :id => 'query_form'}, :method => :get, :update => 'content' ) do %> -<%# TOOD: remove the project_id and issue_id hidden fields, that information is -already in the URI %> -<%= hidden_field_tag('project_id', params[:project_id]) if @project %> -<%= hidden_field_tag 'issue_id', params[:issue_id] if @issue %> +<% form_tag({:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}, :method => :get, :id => 'query_form') do %> <%= render :partial => 'date_range' %> <% end %> diff --git a/app/views/users/_preferences.html.erb b/app/views/users/_preferences.html.erb index 85b5990e..57f050b1 100644 --- a/app/views/users/_preferences.html.erb +++ b/app/views/users/_preferences.html.erb @@ -2,5 +2,6 @@

<%= pref_fields.check_box :hide_mail %>

<%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %>

<%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %>

+

<%= pref_fields.check_box :warn_on_leaving_unsaved %>

<% end %> diff --git a/app/views/users/index.rhtml b/app/views/users/index.rhtml index 69ad7374..f7bf8e0d 100644 --- a/app/views/users/index.rhtml +++ b/app/views/users/index.rhtml @@ -8,9 +8,16 @@
<%= l(:label_filter_plural) %> <%= select_tag 'status', users_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %> + +<% if @groups.present? %> + +<%= select_tag 'group_id', '' + options_from_collection_for_select(@groups, :id, :name, params[:group_id].to_i), :onchange => "this.form.submit(); return false;" %> +<% end %> + <%= text_field_tag 'name', params[:name], :size => 30 %> <%= submit_tag l(:button_apply), :class => "small", :name => nil %> +<%= link_to l(:button_clear), users_path, :class => 'icon icon-reload' %>
<% end %>   diff --git a/app/views/versions/_issue_counts.rhtml b/app/views/versions/_issue_counts.rhtml index 38f3edbc..7ef8c4bc 100644 --- a/app/views/versions/_issue_counts.rhtml +++ b/app/views/versions/_issue_counts.rhtml @@ -19,8 +19,8 @@ :action => 'index', :project_id => version.project, :set_filter => 1, - :fixed_version_id => version, - "#{criteria}_id" => count[:group]} %> + :status_id => '*', + :fixed_version_id => version}.merge("#{criteria}_id".to_sym => count[:group]) %> <%= progress_bar((count[:closed].to_f / count[:total])*100, diff --git a/app/views/wiki/diff.rhtml b/app/views/wiki/diff.rhtml index 264c677b..a1006dce 100644 --- a/app/views/wiki/diff.rhtml +++ b/app/views/wiki/diff.rhtml @@ -12,6 +12,6 @@ (<%= @diff.content_to.author ? @diff.content_to.author.name : "anonyme" %>, <%= format_time(@diff.content_to.updated_on) %>)

-
- -<%= html_diff(@diff) %> +
+<%= simple_format_without_paragraph @diff.to_html %> +
diff --git a/app/views/wiki/index.html.erb b/app/views/wiki/index.html.erb index e85f83b9..d42ee966 100644 --- a/app/views/wiki/index.html.erb +++ b/app/views/wiki/index.html.erb @@ -8,7 +8,7 @@

<%= l(:label_no_data) %>

<% end %> -<%= render_page_hierarchy(@pages_by_parent_id) %> +<%= render_page_hierarchy(@pages_by_parent_id, nil, :timestamp => true) %> <% content_for :sidebar do %> <%= render :partial => 'sidebar' %> diff --git a/app/views/workflows/_form.html.erb b/app/views/workflows/_form.html.erb new file mode 100644 index 00000000..e5b54c07 --- /dev/null +++ b/app/views/workflows/_form.html.erb @@ -0,0 +1,40 @@ + + + + + + + + + <% for new_status in @statuses %> + + <% end %> + + + + <% for old_status in @statuses %> + "> + + <% for new_status in @statuses -%> + + <% end -%> + + <% end %> + +
+ <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%=l(:label_current_status)%> + <%=l(:label_new_statuses_allowed)%>
+ <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.new-status-#{new_status.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + <%=h new_status.name %> +
+ <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('table.transitions-#{name} input.old-status-#{old_status.id}')", + :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> + + <%=h old_status.name %> + + <%= check_box_tag "issue_status[#{ old_status.id }][#{new_status.id}][]", name, workflows.detect {|w| w.old_status_id == old_status.id && w.new_status_id == new_status.id}, + :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %> +
\ No newline at end of file diff --git a/app/views/workflows/edit.rhtml b/app/views/workflows/edit.rhtml index 26f2cf96..575500f3 100644 --- a/app/views/workflows/edit.rhtml +++ b/app/views/workflows/edit.rhtml @@ -20,54 +20,31 @@

<% end %> - <% if @tracker && @role && @statuses.any? %> -<% form_tag({}, :id => 'workflow_form' ) do %> -<%= hidden_field_tag 'tracker_id', @tracker.id %> -<%= hidden_field_tag 'role_id', @role.id %> -
- - - - - - - - - <% for new_status in @statuses %> - - <% end %> - - - - <% for old_status in @statuses %> - "> - - <% new_status_ids_allowed = old_status.find_new_statuses_allowed_to([@role], @tracker).collect(&:id) -%> - <% for new_status in @statuses -%> - - <% end -%> - - <% end %> - -
<%=l(:label_current_status)%><%=l(:label_new_statuses_allowed)%>
- <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.new-status-#{new_status.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - <%= new_status.name %> -
- <%= link_to_function(image_tag('toggle_check.png'), "toggleCheckboxesBySelector('input.old-status-#{old_status.id}')", - :title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}") %> - - <%= old_status.name %> - - <%= check_box_tag "issue_status[#{ old_status.id }][]", new_status.id, new_status_ids_allowed.include?(new_status.id), - :class => "old-status-#{old_status.id} new-status-#{new_status.id}" %> -
-
-

<%= check_all_links 'workflow_form' %>

- -<%= submit_tag l(:button_save) %> -<% end %> + <% form_tag({}, :id => 'workflow_form' ) do %> + <%= hidden_field_tag 'tracker_id', @tracker.id %> + <%= hidden_field_tag 'role_id', @role.id %> +
+ <%= render :partial => 'form', :locals => {:name => 'always', :workflows => @workflows['always']} %> + +
+ <%= l(:label_additional_workflow_transitions_for_author) %> +
+ <%= render :partial => 'form', :locals => {:name => 'author', :workflows => @workflows['author']} %> +
+
+ <%= javascript_tag "hideFieldset($('author_workflows'))" unless @workflows['author'].present? %> + +
+ <%= l(:label_additional_workflow_transitions_for_assignee) %> +
+ <%= render :partial => 'form', :locals => {:name => 'assignee', :workflows => @workflows['assignee']} %> +
+
+ <%= javascript_tag "hideFieldset($('assignee_workflows'))" unless @workflows['assignee'].present? %> +
+ <%= submit_tag l(:button_save) %> + <% end %> <% end %> <% html_title(l(:label_workflow)) -%> diff --git a/app/views/workflows/index.rhtml b/app/views/workflows/index.rhtml index bed6a34d..21287a22 100644 --- a/app/views/workflows/index.rhtml +++ b/app/views/workflows/index.rhtml @@ -24,7 +24,7 @@ <%= h tracker %> <% roles.each do |role, count| -%> - <%= link_to((count > 1 ? count : image_tag('false.png')), {:action => 'edit', :role_id => role, :tracker_id => tracker}, :title => l(:button_edit)) %> + <%= link_to((count > 0 ? count : image_tag('false.png')), {:action => 'edit', :role_id => role, :tracker_id => tracker}, :title => l(:button_edit)) %> <% end -%> diff --git a/config/configuration.yml.example b/config/configuration.yml.example index 0858d1ca..b75a9243 100644 --- a/config/configuration.yml.example +++ b/config/configuration.yml.example @@ -136,6 +136,20 @@ default: scm_bazaar_command: scm_darcs_command: + # Key used to encrypt sensitive data in the database (SCM and LDAP passwords). + # If you don't want to enable data encryption, just leave it blank. + # WARNING: losing/changing this key will make encrypted data unreadable. + # + # If you want to encrypt existing passwords in your database: + # * set the cipher key here in your configuration file + # * encrypt data using 'rake db:encrypt RAILS_ENV=production' + # + # If you have encrypted data and want to change this key, you have to: + # * decrypt data using 'rake db:decrypt RAILS_ENV=production' first + # * change the cipher key here in your configuration file + # * encrypt data using 'rake db:encrypt RAILS_ENV=production' + database_cipher_key: + # specific configuration options for production environment # that overrides the default ones production: diff --git a/config/environment.rb b/config/environment.rb index 19966557..00e81517 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -5,7 +5,7 @@ # ENV['RAILS_ENV'] ||= 'production' # Specifies gem version of Rails to use when vendor/rails is not present -RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION +RAILS_GEM_VERSION = '2.3.11' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') @@ -24,7 +24,7 @@ Rails::Initializer.run do |config| # config.frameworks -= [ :action_web_service, :action_mailer ] # Add additional load paths for sweepers - config.load_paths += %W( #{RAILS_ROOT}/app/sweepers ) + config.autoload_paths += %W( #{RAILS_ROOT}/app/sweepers ) # Force all environments to use the same logger level # (by default production uses :info, the others :debug) @@ -36,7 +36,7 @@ Rails::Initializer.run do |config| # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector - config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer, :wiki_content_observer + config.active_record.observers = :message_observer, :issue_observer, :journal_observer, :news_observer, :document_observer, :wiki_content_observer, :comment_observer # Make Active Record use UTC-base instead of local time # config.active_record.default_timezone = :utc diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb index 7d201e85..0e23a3d4 100644 --- a/config/initializers/10-patches.rb +++ b/config/initializers/10-patches.rb @@ -79,16 +79,12 @@ end ActionMailer::Base.send :include, AsynchronousMailer -# TODO: Hack to support i18n 4.x on Rails 2.3.5. Remove post 2.3.6. -# See http://www.redmine.org/issues/6428 and http://www.redmine.org/issues/5608 -module I18n - module Backend - module Base - def warn_syntax_deprecation!(*args) - return if @skip_syntax_deprecation - ActiveSupport::Deprecation.warn "The {{key}} interpolation syntax in I18n messages is deprecated and will be removed in ChiliProject 2.0. Please use %{key} instead. See the notice at https://www.chiliproject.org/boards/2/topics/243 for more information." - @skip_syntax_deprecation = true - end +# TMail::Unquoter.convert_to_with_fallback_on_iso_8859_1 introduced in TMail 1.2.7 +# triggers a test failure in test_add_issue_with_japanese_keywords(MailHandlerTest) +module TMail + class Unquoter + class << self + alias_method :convert_to, :convert_to_without_fallback_on_iso_8859_1 end end end diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 1f79f60a..a836fd71 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -302,6 +302,7 @@ bg: field_assigned_to_role: Assignee's role field_text: Текстово поле field_visible: Видим + field_warn_on_leaving_unsaved: Предупреди ме, когато напускам страница с незаписано съдържание setting_app_title: Заглавие setting_app_subtitle: Описание @@ -535,6 +536,7 @@ bg: label_news_latest: Последни новини label_news_view_all: Виж всички label_news_added: Добавена новина + label_news_comment_added: Добавен коментар към новина label_settings: Настройки label_overview: Общ изглед label_version: Версия @@ -593,6 +595,7 @@ bg: label_query: Потребителска справка label_query_plural: Потребителски справки label_query_new: Нова заявка + label_my_queries: Моите заявки label_filter_add: Добави филтър label_filter_plural: Филтри label_equals: е @@ -857,6 +860,7 @@ bg: text_are_you_sure: Сигурни ли сте? text_are_you_sure_with_children: Изтриване на задачата и нейните подзадачи? text_journal_changed: "%{label} променен от %{old} на %{new}" + text_journal_changed_no_detail: "%{label} променен" text_journal_set_to: "%{label} установен на %{value}" text_journal_deleted: "%{label} изтрит (%{old})" text_journal_added: "Добавено %{label} %{value}" @@ -907,6 +911,7 @@ bg: text_own_membership_delete_confirmation: "Вие сте на път да премахнете някои или всички ваши разрешения и е възможно след това да не можете да редактирате този проект.\nСигурен ли сте, че искате да продължите?" text_zoom_in: Увеличаване text_zoom_out: Намаляване + text_warn_on_leaving_unsaved: Страницата съдържа незаписано съдържание, което може да бъде загубено, ако я напуснете. default_role_manager: Мениджър default_role_developer: Разработчик @@ -945,3 +950,8 @@ bg: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 8d991ee0..ba4ff7e6 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -959,3 +959,13 @@ bs: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 0bdf1d41..475a64d7 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -948,3 +948,13 @@ ca: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 268821f8..ca50d4a8 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1,4 +1,9 @@ +# Update to 1.1 by Michal Gebauer +# Updated by Josef Liška +# CZ translation by Maxim Krušina | Massimo Filippi, s.r.o. | maxim@mxm.cz +# Based on original CZ translation by Jan Kadleček cs: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) direction: ltr date: formats: @@ -26,7 +31,7 @@ cs: long: "%B %d, %Y %H:%M" am: "dop." pm: "odp." - + datetime: distance_in_words: half_a_minute: "půl minuty" @@ -65,24 +70,26 @@ cs: other: "téměř %{count} roky" number: + # Výchozí formát pro čísla format: - separator: "." + separator: "." delimiter: "" precision: 3 - human: - format: - precision: 1 + human: + format: delimiter: "" - storage_units: + precision: 1 + storage_units: format: "%n %u" - units: - kb: KB - tb: TB - gb: GB - byte: - one: Byte - other: Bytes - mb: MB + units: + byte: + one: "Bajt" + other: "Bajtů" + kb: "kB" + mb: "MB" + gb: "GB" + tb: "TB" + # Used in array.to_sentence. support: @@ -94,8 +101,8 @@ cs: errors: template: header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" + one: "1 chyba zabránila uložení %{model}" + other: "%{count} chyb zabránilo uložení %{model}" messages: inclusion: "není zahrnuto v seznamu" exclusion: "je rezervováno" @@ -122,10 +129,6 @@ cs: circular_dependency: "Tento vztah by vytvořil cyklickou závislost" cant_link_an_issue_with_a_descendant: "Úkol nemůže být spojen s jedním z jeho dílčích úkolů" - # Updated by Josef Liška - # CZ translation by Maxim Krušina | Massimo Filippi, s.r.o. | maxim@mxm.cz - # Based on original CZ translation by Jan Kadleček - actionview_instancetag_blank_option: Prosím vyberte general_text_No: 'Ne' @@ -154,20 +157,40 @@ cs: notice_successful_connection: Úspěšné připojení. notice_file_not_found: Stránka na kterou se snažíte zobrazit neexistuje nebo byla smazána. notice_locking_conflict: Údaje byly změněny jiným uživatelem. - notice_scm_error: Záznam a/nebo revize neexistuje v repozitáři. notice_not_authorized: Nemáte dostatečná práva pro zobrazení této stránky. + notice_not_authorized_archived_project: Projekt ke kterému se snažíte přistupovat byl archivován. notice_email_sent: "Na adresu %{value} byl odeslán email" notice_email_error: "Při odesílání emailu nastala chyba (%{value})" notice_feeds_access_key_reseted: Váš klíč pro přístup k RSS byl resetován. + notice_api_access_key_reseted: Váš API přístupový klíč byl resetován. notice_failed_to_save_issues: "Chyba při uložení %{count} úkolu(ů) z %{total} vybraných: %{ids}." + notice_failed_to_save_members: "Nepodařilo se uložit člena(y): %{errors}." notice_no_issue_selected: "Nebyl zvolen žádný úkol. Prosím, zvolte úkoly, které chcete editovat" notice_account_pending: "Váš účet byl vytvořen, nyní čeká na schválení administrátorem." notice_default_data_loaded: Výchozí konfigurace úspěšně nahrána. + notice_unable_delete_version: Nemohu odstanit verzi + notice_unable_delete_time_entry: Nelze smazat čas ze záznamu. + notice_issue_done_ratios_updated: Koeficienty dokončení úkolu byly aktualizovány. + notice_gantt_chart_truncated: Graf byl oříznut, počet položek přesáhl limit pro zobrazení (%{max}) error_can_t_load_default_data: "Výchozí konfigurace nebyla nahrána: %{value}" error_scm_not_found: "Položka a/nebo revize neexistují v repozitáři." error_scm_command_failed: "Při pokusu o přístup k repozitáři došlo k chybě: %{value}" + error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." error_issue_not_found_in_project: 'Úkol nebyl nalezen nebo nepatří k tomuto projektu' + error_no_tracker_in_project: Žádná fronta nebyla přiřazena tomuto projektu. Prosím zkontroluje nastavení projektu. + error_no_default_issue_status: Není nastaven výchozí stav úkolu. Prosím zkontrolujte nastavení ("Administrace -> Stavy úkolů"). + error_can_not_delete_custom_field: Nelze smazat volitelné pole + error_can_not_delete_tracker: Tato fronta obsahuje úkoly a nemůže být smazán. + error_can_not_remove_role: Tato role je právě používaná a nelze ji smazat. + error_can_not_reopen_issue_on_closed_version: Úkol přiřazený k uzavřené verzi nemůže být znovu otevřen + error_can_not_archive_project: Tento projekt nemůže být archivován + error_issue_done_ratios_not_updated: Koeficient dokončení úkolu nebyl aktualizován. + error_workflow_copy_source: Prosím vyberte zdrojovou frontu nebo roly + error_workflow_copy_target: Prosím vyberte cílovou frontu(y) a roly(e) + error_unable_delete_issue_status: Nelze smazat stavy úkolů + error_unable_to_connect: Nelze se připojit (%{value}) + warning_attachments_not_saved: "%{count} soubor(ů) nebylo možné uložit." mail_subject_lost_password: "Vaše heslo (%{value})" mail_body_lost_password: 'Pro změnu vašeho hesla klikněte na následující odkaz:' @@ -177,6 +200,12 @@ cs: mail_body_account_information: Informace o vašem účtu mail_subject_account_activation_request: "Aktivace %{value} účtu" mail_body_account_activation_request: "Byl zaregistrován nový uživatel %{value}. Aktivace jeho účtu závisí na vašem potvrzení." + mail_subject_reminder: "%{count} úkol(ů) má termín během několik dní (%{days})" + mail_body_reminder: "%{count} úkol(ů), které máte přiřazeny má termín během několik dní (%{days}):" + mail_subject_wiki_content_added: "'%{id}' Wiki stránka byla přidána" + mail_body_wiki_content_added: "'%{id}' Wiki stránka byla přidána od %{author}." + mail_subject_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována" + mail_body_wiki_content_updated: "'%{id}' Wiki stránka byla aktualizována od %{author}." gui_validation_error: 1 chyba gui_validation_error_plural: "%{count} chyb(y)" @@ -216,6 +245,7 @@ cs: field_priority: Priorita field_fixed_version: Cílová verze field_user: Uživatel + field_principal: Hlavní field_role: Role field_homepage: Domovská stránka field_is_public: Veřejný @@ -259,10 +289,23 @@ cs: field_redirect_existing_links: Přesměrovat stvávající odkazy field_estimated_hours: Odhadovaná doba field_column_names: Sloupce + field_time_entries: Zaznamenaný čas field_time_zone: Časové pásmo field_searchable: Umožnit vyhledávání field_default_value: Výchozí hodnota field_comments_sorting: Zobrazit komentáře + field_parent_title: Rodičovská stránka + field_editable: Editovatelný + field_watcher: Sleduje + field_identity_url: OpenID URL + field_content: Obsah + field_group_by: Seskupovat výsledky podle + field_sharing: Sdílení + field_parent_issue: Rodičovský úkol + field_member_of_group: Skupina přiřaditele + field_assigned_to_role: Role přiřaditele + field_text: Textové pole + field_visible: Viditelný setting_app_title: Název aplikace setting_app_subtitle: Podtitulek aplikace @@ -274,6 +317,7 @@ cs: setting_issues_export_limit: Limit pro export úkolů setting_mail_from: Odesílat emaily z adresy setting_bcc_recipients: Příjemci skryté kopie (bcc) + setting_plain_text_mail: pouze prostý text (ne HTML) setting_host_name: Jméno serveru setting_text_formatting: Formátování textu setting_wiki_compression: Komprese historie Wiki @@ -289,12 +333,94 @@ cs: setting_cross_project_issue_relations: Povolit vazby úkolů napříč projekty setting_issue_list_default_columns: Výchozí sloupce zobrazené v seznamu úkolů setting_repositories_encodings: Kódování + setting_commit_logs_encoding: Kódování zpráv při commitu + setting_emails_header: Hlavička emailů setting_emails_footer: Patička emailů setting_protocol: Protokol setting_per_page_options: Povolené počty řádků na stránce setting_user_format: Formát zobrazení uživatele setting_activity_days_default: Dny zobrazené v činnosti projektu setting_display_subprojects_issues: Automaticky zobrazit úkoly podprojektu v hlavním projektu + setting_enabled_scm: Povolené SCM + setting_mail_handler_body_delimiters: Zkrátit e-maily po jednom z těchto řádků + setting_mail_handler_api_enabled: Povolit WS pro příchozí e-maily + setting_mail_handler_api_key: API klíč + setting_sequential_project_identifiers: Generovat sekvenční identifikátory projektů + setting_gravatar_enabled: Použít uživatelské ikony Gravatar + setting_gravatar_default: Výchozí Gravatar + setting_diff_max_lines_displayed: Maximální počet zobrazenách řádků rozdílů + setting_file_max_size_displayed: Maximální velikost textových souborů zobrazených přímo na stránce + setting_repository_log_display_limit: Maximální počet revizí zobrazených v logu souboru + setting_openid: Umožnit přihlašování a registrace s OpenID + setting_password_min_length: Minimální délka hesla + setting_new_project_user_role_id: Role přiřazená uživateli bez práv administrátora, který projekt vytvořil + setting_default_projects_modules: Výchozí zapnutné moduly pro nový projekt + setting_issue_done_ratio: Spočítat koeficient dokončení úkolu s + setting_issue_done_ratio_issue_field: Použít pole úkolu + setting_issue_done_ratio_issue_status: Použít stav úkolu + setting_start_of_week: Začínat kalendáře + setting_rest_api_enabled: Zapnout službu REST + setting_cache_formatted_text: Ukládat formátovaný text do vyrovnávací paměti + setting_default_notification_option: Výchozí nastavení oznámení + setting_commit_logtime_enabled: Povolit zapisování času + setting_commit_logtime_activity_id: Aktivita pro zapsaný čas + setting_gantt_items_limit: Maximální počet položek zobrazený na ganttově grafu + + permission_add_project: Vytvořit projekt + permission_add_subprojects: Vytvořit podprojekty + permission_edit_project: Úprava projektů + permission_select_project_modules: Výběr modulů projektu + permission_manage_members: Spravování členství + permission_manage_project_activities: Spravovat aktivity projektu + permission_manage_versions: Spravování verzí + permission_manage_categories: Spravování kategorií úkolů + permission_view_issues: Zobrazit úkoly + permission_add_issues: Přidávání úkolů + permission_edit_issues: Upravování úkolů + permission_manage_issue_relations: Spravování vztahů mezi úkoly + permission_add_issue_notes: Přidávání poznámek + permission_edit_issue_notes: Upravování poznámek + permission_edit_own_issue_notes: Upravování vlastních poznámek + permission_move_issues: Přesouvání úkolů + permission_delete_issues: Mazání úkolů + permission_manage_public_queries: Správa veřejných dotazů + permission_save_queries: Ukládání dotazů + permission_view_gantt: Zobrazené Ganttova diagramu + permission_view_calendar: Prohlížení kalendáře + permission_view_issue_watchers: Zobrazení seznamu sledujícíh uživatelů + permission_add_issue_watchers: Přidání sledujících uživatelů + permission_delete_issue_watchers: Smazat přihlížející + permission_log_time: Zaznamenávání stráveného času + permission_view_time_entries: Zobrazení stráveného času + permission_edit_time_entries: Upravování záznamů o stráveném času + permission_edit_own_time_entries: Upravování vlastních zázamů o stráveném čase + permission_manage_news: Spravování novinek + permission_comment_news: Komentování novinek + permission_manage_documents: Správa dokumentů + permission_view_documents: Prohlížení dokumentů + permission_manage_files: Spravování souborů + permission_view_files: Prohlížení souborů + permission_manage_wiki: Spravování Wiki + permission_rename_wiki_pages: Přejmenovávání Wiki stránek + permission_delete_wiki_pages: Mazání stránek na Wiki + permission_view_wiki_pages: Prohlížení Wiki + permission_view_wiki_edits: Prohlížení historie Wiki + permission_edit_wiki_pages: Upravování stránek Wiki + permission_delete_wiki_pages_attachments: Mazání příloh + permission_protect_wiki_pages: Zabezpečení Wiki stránek + permission_manage_repository: Spravování repozitáře + permission_browse_repository: Procházení repozitáře + permission_view_changesets: Zobrazování sady změn + permission_commit_access: Commit přístup + permission_manage_boards: Správa diskusních fór + permission_view_messages: Prohlížení zpráv + permission_add_messages: Posílání zpráv + permission_edit_messages: Upravování zpráv + permission_edit_own_messages: Upravit vlastní zprávy + permission_delete_messages: Mazání zpráv + permission_delete_own_messages: Smazat vlastní zprávy + permission_export_wiki_pages: Exportovat Wiki stránky + permission_manage_subtasks: Spravovat podúkoly project_module_issue_tracking: Sledování úkolů project_module_time_tracking: Sledování času @@ -304,10 +430,13 @@ cs: project_module_wiki: Wiki project_module_repository: Repozitář project_module_boards: Diskuse + project_module_calendar: Kalendář + project_module_gantt: Gantt label_user: Uživatel label_user_plural: Uživatelé label_user_new: Nový uživatel + label_user_anonymous: Anonymní label_project: Projekt label_project_new: Nový projekt label_project_plural: Projekty @@ -354,11 +483,13 @@ cs: label_information_plural: Informace label_please_login: Prosím přihlašte se label_register: Registrovat + label_login_with_open_id_option: nebo se přihlašte s OpenID label_password_lost: Zapomenuté heslo label_home: Úvodní label_my_page: Moje stránka label_my_account: Můj účet label_my_projects: Moje projekty + label_my_page_block: Bloky na mé stránce label_administration: Administrace label_login: Přihlášení label_logout: Odhlášení @@ -369,6 +500,7 @@ cs: label_registered_on: Registrován label_activity: Aktivita label_overall_activity: Celková aktivita + label_user_activity: "Aktivita uživatele: %{value}" label_new: Nový label_logged_as: Přihlášen jako label_environment: Prostředí @@ -377,6 +509,8 @@ cs: label_auth_source_new: Nový mód autentifikace label_auth_source_plural: Módy autentifikace label_subproject_plural: Podprojekty + label_subproject_new: Nový podprojekt + label_and_its_subprojects: "%{value} a jeho podprojekty" label_min_max_length: Min - Max délka label_list: Seznam label_date: Datum @@ -410,6 +544,7 @@ cs: label_version: Verze label_version_new: Nová verze label_version_plural: Verze + label_close_versions: Zavřít dokončené verze label_confirmation: Potvrzení label_export_to: 'Také k dispozici:' label_read: Načítá se... @@ -468,6 +603,8 @@ cs: label_not_equals: není label_in_less_than: je měší než label_in_more_than: je větší než + label_greater_or_equal: '>=' + label_less_or_equal: '<=' label_in: v label_today: dnes label_all_time: vše @@ -490,15 +627,21 @@ cs: label_browse: Procházet label_modification: "%{count} změna" label_modification_plural: "%{count} změn" + label_branch: Větev + label_tag: Tag label_revision: Revize label_revision_plural: Revizí + label_revision_id: "Revize %{value}" label_associated_revisions: Související verze label_added: přidáno label_modified: změněno + label_copied: zkopírováno + label_renamed: přejmenováno label_deleted: odstraněno label_latest_revision: Poslední revize label_latest_revision_plural: Poslední revize label_view_revisions: Zobrazit revize + label_view_all_revisions: Zobrazit všechny revize label_max_size: Maximální velikost label_sort_highest: Přesunout na začátek label_sort_higher: Přesunout nahoru @@ -524,6 +667,7 @@ cs: label_changes_details: Detail všech změn label_issue_tracking: Sledování úkolů label_spent_time: Strávený čas + label_overall_spent_time: Celkem strávený čas label_f_hour: "%{value} hodina" label_f_hour_plural: "%{value} hodin" label_time_tracking: Sledování času @@ -544,7 +688,8 @@ cs: label_relation_new: Nová souvislost label_relation_delete: Odstranit souvislost label_relates_to: související s - label_duplicates: duplicity + label_duplicates: duplikuje + label_duplicated_by: zduplikován label_blocks: blokuje label_blocked_by: zablokován label_precedes: předchází @@ -560,6 +705,8 @@ cs: label_board: Fórum label_board_new: Nové fórum label_board_plural: Fóra + label_board_locked: Uzamčeno + label_board_sticky: Nálepka label_topic_plural: Témata label_message_plural: Zprávy label_message_last: Poslední zpráva @@ -575,9 +722,12 @@ cs: label_language_based: Podle výchozího jazyku label_sort_by: "Seřadit podle %{value}" label_send_test_email: Poslat testovací email + label_feeds_access_key: Přístupový klíč pro RSS + label_missing_feeds_access_key: Postrádá přístupový klíč pro RSS label_feeds_access_key_created_on: "Přístupový klíč pro RSS byl vytvořen před %{value}" label_module_plural: Moduly label_added_time_by: "Přidáno uživatelem %{author} před %{age}" + label_updated_time_by: "Aktualizováno uživatelem %{author} před %{age}" label_updated_time: "Aktualizováno před %{value}" label_jump_to_a_project: Vyberte projekt... label_file_plural: Soubory @@ -590,6 +740,10 @@ cs: label_search_titles_only: Vyhledávat pouze v názvech label_user_mail_option_all: "Pro všechny události všech mých projektů" label_user_mail_option_selected: "Pro všechny události vybraných projektů..." + label_user_mail_option_none: "Žádné události" + label_user_mail_option_only_my_events: "Jen pro věci co sleduji nebo jsem v nich zapojen" + label_user_mail_option_only_assigned: "Jen pro všeci kterým sem přiřazen" + label_user_mail_option_only_owner: "Jen pro věci které vlastním" label_user_mail_no_self_notified: "Nezasílat informace o mnou vytvořených změnách" label_registration_activation_by_email: aktivace účtu emailem label_registration_manual_activation: manuální aktivace účtu @@ -608,6 +762,40 @@ cs: label_preferences: Nastavení label_chronological_order: V chronologickém pořadí label_reverse_chronological_order: V obrácaném chronologickém pořadí + label_planning: Plánování + label_incoming_emails: Příchozí e-maily + label_generate_key: Generovat klíč + label_issue_watchers: Sledování + label_example: Příklad + label_display: Zobrazit + label_sort: Řazení + label_ascending: Vzestupně + label_descending: Sestupně + label_date_from_to: Od %{start} do %{end} + label_wiki_content_added: Wiki stránka přidána + label_wiki_content_updated: Wiki stránka aktualizována + label_group: Skupina + label_group_plural: Skupiny + label_group_new: Nová skupina + label_time_entry_plural: Strávený čas + label_version_sharing_none: Nesdíleno + label_version_sharing_descendants: S podprojekty + label_version_sharing_hierarchy: S hierarchií projektu + label_version_sharing_tree: Se stromem projektu + label_version_sharing_system: Se všemi projekty + label_update_issue_done_ratios: Aktualizovat koeficienty dokončení úkolů + label_copy_source: Zdroj + label_copy_target: Cíl + label_copy_same_as_target: Stejný jako cíl + label_display_used_statuses_only: Zobrazit pouze stavy které jsou použité touto frontou + label_api_access_key: API přístupový klíč + label_missing_api_access_key: Chybějící přístupový klíč API + label_api_access_key_created_on: API přístupový klíč vytvořen %{value} + label_profile: Profil + label_subtask_plural: Podúkol + label_project_copy_notifications: Odeslat email oznámení v průběhu kopie projektu + label_principal_search: "Hledat uživatele nebo skupinu:" + label_user_search: "Hledat uživatele:" button_login: Přihlásit button_submit: Potvrdit @@ -616,8 +804,10 @@ cs: button_uncheck_all: Odšrtnout vše button_delete: Odstranit button_create: Vytvořit - button_test: Test + button_create_and_continue: Vytvořit a pokračovat + button_test: Testovat button_edit: Upravit + button_edit_associated_wikipage: "Upravit přiřazenou Wiki stránku: %{page_title}" button_add: Přidat button_change: Změnit button_apply: Použít @@ -628,6 +818,7 @@ cs: button_list: Vypsat button_view: Zobrazit button_move: Přesunout + button_move_and_follow: Přesunout a následovat button_back: Zpět button_cancel: Storno button_activate: Aktivovat @@ -639,24 +830,40 @@ cs: button_reply: Odpovědět button_archive: Archivovat button_unarchive: Odarchivovat - button_reset: Reset + button_reset: Resetovat button_rename: Přejmenovat button_change_password: Změnit heslo button_copy: Kopírovat + button_copy_and_follow: Kopírovat a následovat button_annotate: Komentovat button_update: Aktualizovat button_configure: Konfigurovat + button_quote: Citovat + button_duplicate: Duplikát + button_show: Zobrazit status_active: aktivní status_registered: registrovaný status_locked: uzamčený + version_status_open: otevřený + version_status_locked: uzamčený + version_status_closed: zavřený + + field_active: Aktivní + text_select_mail_notifications: Vyberte akci při které bude zasláno upozornění emailem. text_regexp_info: např. ^[A-Z0-9]+$ text_min_max_length_info: 0 znamená bez limitu text_project_destroy_confirmation: Jste si jisti, že chcete odstranit tento projekt a všechna související data ? + text_subprojects_destroy_warning: "Jeho podprojek(y): %{value} budou také smazány." text_workflow_edit: Vyberte roli a frontu k editaci průběhu práce text_are_you_sure: Jste si jisti? + text_are_you_sure_with_children: Smazat úkol včetně všech podúkolů? + text_journal_changed: "%{label} změněn z %{old} na %{new}" + text_journal_set_to: "%{label} nastaven na %{value}" + text_journal_deleted: "%{label} smazán (%{old})" + text_journal_added: "%{label} %{value} přidán" text_tip_issue_begin_day: úkol začíná v tento den text_tip_issue_end_day: úkol končí v tento den text_tip_issue_begin_end_day: úkol začíná a končí v tento den @@ -667,6 +874,7 @@ cs: text_tracker_no_workflow: Pro tuto frontu není definován žádný průběh práce text_unallowed_characters: Nepovolené znaky text_comma_separated: Povoleno více hodnot (oddělěné čárkou). + text_line_separated: Více hodnot povoleno (jeden řádek pro každou hodnotu). text_issues_ref_in_commit_messages: Odkazování a opravování úkolů ve zprávách commitů text_issue_added: "Úkol %{id} byl vytvořen uživatelem %{author}." text_issue_updated: "Úkol %{id} byl aktualizován uživatelem %{author}." @@ -678,15 +886,31 @@ cs: text_no_configuration_data: "Role, fronty, stavy úkolů ani průběh práce nebyly zatím nakonfigurovány.\nVelice doporučujeme nahrát výchozí konfiguraci. Po té si můžete vše upravit" text_load_default_configuration: Nahrát výchozí konfiguraci text_status_changed_by_changeset: "Použito v changesetu %{value}." + text_time_logged_by_changeset: Aplikováno v changesetu %{value}. text_issues_destroy_confirmation: 'Opravdu si přejete odstranit všechny zvolené úkoly?' text_select_project_modules: 'Aktivní moduly v tomto projektu:' text_default_administrator_account_changed: Výchozí nastavení administrátorského účtu změněno text_file_repository_writable: Povolen zápis do adresáře ukládání souborů + text_plugin_assets_writable: Možnost zápisu do adresáře plugin assets text_rmagick_available: RMagick k dispozici (volitelné) text_destroy_time_entries_question: "U úkolů, které chcete odstranit je evidováno %{hours} práce. Co chete udělat?" text_destroy_time_entries: Odstranit evidované hodiny. text_assign_time_entries_to_project: Přiřadit evidované hodiny projektu text_reassign_time_entries: 'Přeřadit evidované hodiny k tomuto úkolu:' + text_user_wrote: "%{value} napsal:" + text_enumeration_destroy_question: "Několik (%{count}) objektů je přiřazeno k této hodnotě." + text_enumeration_category_reassign_to: 'Přeřadit je do této:' + text_email_delivery_not_configured: "Doručování e-mailů není nastaveno a odesílání notifikací je zakázáno.\nNastavte Váš SMTP server v souboru config/email.yml a restartujte aplikaci." + text_repository_usernames_mapping: "Vybrat nebo upravit mapování mezi Redmine uživateli a uživatelskými jmény nalezenými v logu repozitáře.\nUživatelé se shodným Redmine uživatelským jménem a uživatelským jménem v repozitáři jsou mapovaní automaticky." + text_diff_truncated: '... Rozdílový soubor je zkrácen, protože jeho délka přesahuje max. limit.' + text_custom_field_possible_values_info: 'Každá hodnota na novém řádku' + text_wiki_page_destroy_question: Tato stránka má %{descendants} podstránek a potomků. Co chcete udělat? + text_wiki_page_nullify_children: Ponechat podstránky jako kořenové stránky + text_wiki_page_destroy_children: Smazat podstránky a všechny jejich potomky + text_wiki_page_reassign_children: Přiřadit podstránky k tomuto rodiči + text_own_membership_delete_confirmation: "Chystáte se odebrat si některá nebo všechny svá oprávnění a potom již nemusíte být schopni upravit tento projekt.\nOpravdu chcete pokračovat?" + text_zoom_in: Přiblížit + text_zoom_out: Oddálit default_role_manager: Manažer default_role_developer: Vývojář @@ -945,3 +1169,13 @@ cs: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/da.yml b/config/locales/da.yml index b2b00e71..bc86533e 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -961,3 +961,13 @@ da: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/de.yml b/config/locales/de.yml index 0592c5a4..5567b6fe 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -962,3 +962,13 @@ de: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/el.yml b/config/locales/el.yml index ee110c63..4b5d6ffa 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -945,3 +945,13 @@ el: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index b0cbef4e..5d5ca653 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1,948 +1,955 @@ -en-GB: - direction: ltr - date: - formats: - # Use the strftime parameters for formats. - # When no format has been given, it uses default. - # You can provide other formats here if you like! - default: "%d/%m/%Y" - short: "%d %b" - long: "%d %B, %Y" - - day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] - abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] - - # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] - # Used in date_select and datime_select. - order: [ :year, :month, :day ] - - time: - formats: - default: "%d/%m/%Y %I:%M %p" - time: "%I:%M %p" - short: "%d %b %H:%M" - long: "%d %B, %Y %H:%M" - am: "am" - pm: "pm" - - datetime: - distance_in_words: - half_a_minute: "half a minute" - less_than_x_seconds: - one: "less than 1 second" - other: "less than %{count} seconds" - x_seconds: - one: "1 second" - other: "%{count} seconds" - less_than_x_minutes: - one: "less than a minute" - other: "less than %{count} minutes" - x_minutes: - one: "1 minute" - other: "%{count} minutes" - about_x_hours: - one: "about 1 hour" - other: "about %{count} hours" - x_days: - one: "1 day" - other: "%{count} days" - about_x_months: - one: "about 1 month" - other: "about %{count} months" - x_months: - one: "1 month" - other: "%{count} months" - about_x_years: - one: "about 1 year" - other: "about %{count} years" - over_x_years: - one: "over 1 year" - other: "over %{count} years" - almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" - - number: - format: - separator: "." - delimiter: " " - precision: 3 - - currency: - format: - format: "%u%n" - unit: "£" - human: - format: - delimiter: "" - precision: 1 - storage_units: - format: "%n %u" - units: - byte: - one: "Byte" - other: "Bytes" - kb: "kB" - mb: "MB" - gb: "GB" - tb: "TB" - - -# Used in array.to_sentence. - support: - array: - sentence_connector: "and" - skip_last_comma: false - - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - messages: - inclusion: "is not included in the list" - exclusion: "is reserved" - invalid: "is invalid" - confirmation: "doesn't match confirmation" - accepted: "must be accepted" - empty: "can't be empty" - blank: "can't be blank" - too_long: "is too long (maximum is %{count} characters)" - too_short: "is too short (minimum is %{count} characters)" - wrong_length: "is the wrong length (should be %{count} characters)" - taken: "has already been taken" - not_a_number: "is not a number" - not_a_date: "is not a valid date" - greater_than: "must be greater than %{count}" - greater_than_or_equal_to: "must be greater than or equal to %{count}" - equal_to: "must be equal to %{count}" - less_than: "must be less than %{count}" - less_than_or_equal_to: "must be less than or equal to %{count}" - odd: "must be odd" - even: "must be even" - greater_than_start_date: "must be greater than start date" - not_same_project: "doesn't belong to the same project" - circular_dependency: "This relation would create a circular dependency" - cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks" - - actionview_instancetag_blank_option: Please select - - general_text_No: 'No' - general_text_Yes: 'Yes' - general_text_no: 'no' - general_text_yes: 'yes' - general_lang_name: 'English (British)' - general_csv_separator: ',' - general_csv_decimal_separator: '.' - general_csv_encoding: ISO-8859-1 - general_pdf_encoding: ISO-8859-1 - general_first_day_of_week: '1' - - notice_account_updated: Account was successfully updated. - notice_account_invalid_creditentials: Invalid user or password - notice_account_password_updated: Password was successfully updated. - notice_account_wrong_password: Wrong password - notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. - notice_account_unknown_email: Unknown user. - notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. - notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. - notice_account_activated: Your account has been activated. You can now log in. - notice_successful_create: Successful creation. - notice_successful_update: Successful update. - notice_successful_delete: Successful deletion. - notice_successful_connection: Successful connection. - notice_file_not_found: The page you were trying to access doesn't exist or has been removed. - notice_locking_conflict: Data has been updated by another user. - notice_not_authorized: You are not authorised to access this page. - notice_email_sent: "An email was sent to %{value}" - notice_email_error: "An error occurred while sending mail (%{value})" - notice_feeds_access_key_reseted: Your RSS access key was reset. - notice_api_access_key_reseted: Your API access key was reset. - notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." - notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." - notice_account_pending: "Your account was created and is now pending administrator approval." - notice_default_data_loaded: Default configuration successfully loaded. - notice_unable_delete_version: Unable to delete version. - notice_issue_done_ratios_updated: Issue done ratios updated. - - error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" - error_scm_not_found: "The entry or revision was not found in the repository." - error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" - error_scm_annotate: "The entry does not exist or can not be annotated." - error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' - error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' - error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived - error_issue_done_ratios_not_updated: "Issue done ratios not updated." - error_workflow_copy_source: 'Please select a source tracker or role' - error_workflow_copy_target: 'Please select target tracker(s) and role(s)' - - warning_attachments_not_saved: "%{count} file(s) could not be saved." - - mail_subject_lost_password: "Your %{value} password" - mail_body_lost_password: 'To change your password, click on the following link:' - mail_subject_register: "Your %{value} account activation" - mail_body_register: 'To activate your account, click on the following link:' - mail_body_account_information_external: "You can use your %{value} account to log in." - mail_body_account_information: Your account information - mail_subject_account_activation_request: "%{value} account activation request" - mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:" - mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" - mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" - mail_subject_wiki_content_added: "'%{id}' wiki page has been added" - mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." - mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" - mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." - - gui_validation_error: 1 error - gui_validation_error_plural: "%{count} errors" - - field_name: Name - field_description: Description - field_summary: Summary - field_is_required: Required - field_firstname: Firstname - field_lastname: Lastname - field_mail: Email - field_filename: File - field_filesize: Size - field_downloads: Downloads - field_author: Author - field_created_on: Created - field_updated_on: Updated - field_field_format: Format - field_is_for_all: For all projects - field_possible_values: Possible values - field_regexp: Regular expression - field_min_length: Minimum length - field_max_length: Maximum length - field_value: Value - field_category: Category - field_title: Title - field_project: Project - field_issue: Issue - field_status: Status - field_notes: Notes - field_is_closed: Issue closed - field_is_default: Default value - field_tracker: Tracker - field_subject: Subject - field_due_date: Due date - field_assigned_to: Assignee - field_priority: Priority - field_fixed_version: Target version - field_user: User - field_role: Role - field_homepage: Homepage - field_is_public: Public - field_parent: Subproject of - field_is_in_roadmap: Issues displayed in roadmap - field_login: Login - field_mail_notification: Email notifications - field_admin: Administrator - field_last_login_on: Last connection - field_language: Language - field_effective_date: Due date - field_password: Password - field_new_password: New password - field_password_confirmation: Confirmation - field_version: Version - field_type: Type - field_host: Host - field_port: Port - field_account: Account - field_base_dn: Base DN - field_attr_login: Login attribute - field_attr_firstname: Firstname attribute - field_attr_lastname: Lastname attribute - field_attr_mail: Email attribute - field_onthefly: On-the-fly user creation - field_start_date: Start date - field_done_ratio: % Done - field_auth_source: Authentication mode - field_hide_mail: Hide my email address - field_comments: Comment - field_url: URL - field_start_page: Start page - field_subproject: Subproject - field_hours: Hours - field_activity: Activity - field_spent_on: Date - field_identifier: Identifier - field_is_filter: Used as a filter - field_issue_to: Related issue - field_delay: Delay - field_assignable: Issues can be assigned to this role - field_redirect_existing_links: Redirect existing links - field_estimated_hours: Estimated time - field_column_names: Columns - field_time_zone: Time zone - field_searchable: Searchable - field_default_value: Default value - field_comments_sorting: Display comments - field_parent_title: Parent page - field_editable: Editable - field_watcher: Watcher - field_identity_url: OpenID URL - field_content: Content - field_group_by: Group results by - field_sharing: Sharing - - setting_app_title: Application title - setting_app_subtitle: Application subtitle - setting_welcome_text: Welcome text - setting_default_language: Default language - setting_login_required: Authentication required - setting_self_registration: Self-registration - setting_attachment_max_size: Attachment max. size - setting_issues_export_limit: Issues export limit - setting_mail_from: Emission email address - setting_bcc_recipients: Blind carbon copy recipients (bcc) - setting_plain_text_mail: Plain text mail (no HTML) - setting_host_name: Host name and path - setting_text_formatting: Text formatting - setting_wiki_compression: Wiki history compression - setting_feeds_limit: Feed content limit - setting_default_projects_public: New projects are public by default - setting_autofetch_changesets: Autofetch commits - setting_sys_api_enabled: Enable WS for repository management - setting_commit_ref_keywords: Referencing keywords - setting_commit_fix_keywords: Fixing keywords - setting_autologin: Autologin - setting_date_format: Date format - setting_time_format: Time format - setting_cross_project_issue_relations: Allow cross-project issue relations - setting_issue_list_default_columns: Default columns displayed on the issue list - setting_repositories_encodings: Repositories encodings - setting_commit_logs_encoding: Commit messages encoding - setting_emails_footer: Emails footer - setting_protocol: Protocol - setting_per_page_options: Objects per page options - setting_user_format: Users display format - setting_activity_days_default: Days displayed on project activity - setting_display_subprojects_issues: Display subprojects issues on main projects by default - setting_enabled_scm: Enabled SCM - setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" - setting_mail_handler_api_enabled: Enable WS for incoming emails - setting_mail_handler_api_key: API key - setting_sequential_project_identifiers: Generate sequential project identifiers - setting_gravatar_enabled: Use Gravatar user icons - setting_gravatar_default: Default Gravatar image - setting_diff_max_lines_displayed: Max number of diff lines displayed - setting_file_max_size_displayed: Max size of text files displayed inline - setting_repository_log_display_limit: Maximum number of revisions displayed on file log - setting_openid: Allow OpenID login and registration - setting_password_min_length: Minimum password length - setting_new_project_user_role_id: Role given to a non-admin user who creates a project - setting_default_projects_modules: Default enabled modules for new projects - setting_issue_done_ratio: Calculate the issue done ratio with - setting_issue_done_ratio_issue_field: Use the issue field - setting_issue_done_ratio_issue_status: Use the issue status - setting_start_of_week: Start calendars on - setting_rest_api_enabled: Enable REST web service - setting_cache_formatted_text: Cache formatted text - - permission_add_project: Create project - permission_add_subprojects: Create subprojects - permission_edit_project: Edit project - permission_select_project_modules: Select project modules - permission_manage_members: Manage members - permission_manage_project_activities: Manage project activities - permission_manage_versions: Manage versions - permission_manage_categories: Manage issue categories - permission_view_issues: View Issues - permission_add_issues: Add issues - permission_edit_issues: Edit issues - permission_manage_issue_relations: Manage issue relations - permission_add_issue_notes: Add notes - permission_edit_issue_notes: Edit notes - permission_edit_own_issue_notes: Edit own notes - permission_move_issues: Move issues - permission_delete_issues: Delete issues - permission_manage_public_queries: Manage public queries - permission_save_queries: Save queries - permission_view_gantt: View gantt chart - permission_view_calendar: View calendar - permission_view_issue_watchers: View watchers list - permission_add_issue_watchers: Add watchers - permission_delete_issue_watchers: Delete watchers - permission_log_time: Log spent time - permission_view_time_entries: View spent time - permission_edit_time_entries: Edit time logs - permission_edit_own_time_entries: Edit own time logs - permission_manage_news: Manage news - permission_comment_news: Comment news - permission_manage_documents: Manage documents - permission_view_documents: View documents - permission_manage_files: Manage files - permission_view_files: View files - permission_manage_wiki: Manage wiki - permission_rename_wiki_pages: Rename wiki pages - permission_delete_wiki_pages: Delete wiki pages - permission_view_wiki_pages: View wiki - permission_view_wiki_edits: View wiki history - permission_edit_wiki_pages: Edit wiki pages - permission_delete_wiki_pages_attachments: Delete attachments - permission_protect_wiki_pages: Protect wiki pages - permission_manage_repository: Manage repository - permission_browse_repository: Browse repository - permission_view_changesets: View changesets - permission_commit_access: Commit access - permission_manage_boards: Manage boards - permission_view_messages: View messages - permission_add_messages: Post messages - permission_edit_messages: Edit messages - permission_edit_own_messages: Edit own messages - permission_delete_messages: Delete messages - permission_delete_own_messages: Delete own messages - permission_export_wiki_pages: Export wiki pages - - project_module_issue_tracking: Issue tracking - project_module_time_tracking: Time tracking - project_module_news: News - project_module_documents: Documents - project_module_files: Files - project_module_wiki: Wiki - project_module_repository: Repository - project_module_boards: Boards - - label_user: User - label_user_plural: Users - label_user_new: New user - label_user_anonymous: Anonymous - label_project: Project - label_project_new: New project - label_project_plural: Projects - label_x_projects: - zero: no projects - one: 1 project - other: "%{count} projects" - label_project_all: All Projects - label_project_latest: Latest projects - label_issue: Issue - label_issue_new: New issue - label_issue_plural: Issues - label_issue_view_all: View all issues - label_issues_by: "Issues by %{value}" - label_issue_added: Issue added - label_issue_updated: Issue updated - label_document: Document - label_document_new: New document - label_document_plural: Documents - label_document_added: Document added - label_role: Role - label_role_plural: Roles - label_role_new: New role - label_role_and_permissions: Roles and permissions - label_member: Member - label_member_new: New member - label_member_plural: Members - label_tracker: Tracker - label_tracker_plural: Trackers - label_tracker_new: New tracker - label_workflow: Workflow - label_issue_status: Issue status - label_issue_status_plural: Issue statuses - label_issue_status_new: New status - label_issue_category: Issue category - label_issue_category_plural: Issue categories - label_issue_category_new: New category - label_custom_field: Custom field - label_custom_field_plural: Custom fields - label_custom_field_new: New custom field - label_enumerations: Enumerations - label_enumeration_new: New value - label_information: Information - label_information_plural: Information - label_please_login: Please log in - label_register: Register - label_login_with_open_id_option: or login with OpenID - label_password_lost: Lost password - label_home: Home - label_my_page: My page - label_my_account: My account - label_my_projects: My projects - label_administration: Administration - label_login: Sign in - label_logout: Sign out - label_help: Help - label_reported_issues: Reported issues - label_assigned_to_me_issues: Issues assigned to me - label_last_login: Last connection - label_registered_on: Registered on - label_activity: Activity - label_overall_activity: Overall activity - label_user_activity: "%{value}'s activity" - label_new: New - label_logged_as: Logged in as - label_environment: Environment - label_authentication: Authentication - label_auth_source: Authentication mode - label_auth_source_new: New authentication mode - label_auth_source_plural: Authentication modes - label_subproject_plural: Subprojects - label_subproject_new: New subproject - label_and_its_subprojects: "%{value} and its subprojects" - label_min_max_length: Min - Max length - label_list: List - label_date: Date - label_integer: Integer - label_float: Float - label_boolean: Boolean - label_string: Text - label_text: Long text - label_attribute: Attribute - label_attribute_plural: Attributes - label_download: "%{count} Download" - label_download_plural: "%{count} Downloads" - label_no_data: No data to display - label_change_status: Change status - label_history: History - label_attachment: File - label_attachment_new: New file - label_attachment_delete: Delete file - label_attachment_plural: Files - label_file_added: File added - label_report: Report - label_report_plural: Reports - label_news: News - label_news_new: Add news - label_news_plural: News - label_news_latest: Latest news - label_news_view_all: View all news - label_news_added: News added - label_settings: Settings - label_overview: Overview - label_version: Version - label_version_new: New version - label_version_plural: Versions - label_close_versions: Close completed versions - label_confirmation: Confirmation - label_export_to: 'Also available in:' - label_read: Read... - label_public_projects: Public projects - label_open_issues: open - label_open_issues_plural: open - label_closed_issues: closed - label_closed_issues_plural: closed - label_x_open_issues_abbr_on_total: - zero: 0 open / %{total} - one: 1 open / %{total} - other: "%{count} open / %{total}" - label_x_open_issues_abbr: - zero: 0 open - one: 1 open - other: "%{count} open" - label_x_closed_issues_abbr: - zero: 0 closed - one: 1 closed - other: "%{count} closed" - label_total: Total - label_permissions: Permissions - label_current_status: Current status - label_new_statuses_allowed: New statuses allowed - label_all: all - label_none: none - label_nobody: nobody - label_next: Next - label_previous: Previous - label_used_by: Used by - label_details: Details - label_add_note: Add a note - label_per_page: Per page - label_calendar: Calendar - label_months_from: months from - label_gantt: Gantt - label_internal: Internal - label_last_changes: "last %{count} changes" - label_change_view_all: View all changes - label_personalize_page: Personalise this page - label_comment: Comment - label_comment_plural: Comments - label_x_comments: - zero: no comments - one: 1 comment - other: "%{count} comments" - label_comment_add: Add a comment - label_comment_added: Comment added - label_comment_delete: Delete comments - label_query: Custom query - label_query_plural: Custom queries - label_query_new: New query - label_filter_add: Add filter - label_filter_plural: Filters - label_equals: is - label_not_equals: is not - label_in_less_than: in less than - label_in_more_than: in more than - label_greater_or_equal: '>=' - label_less_or_equal: '<=' - label_in: in - label_today: today - label_all_time: all time - label_yesterday: yesterday - label_this_week: this week - label_last_week: last week - label_last_n_days: "last %{count} days" - label_this_month: this month - label_last_month: last month - label_this_year: this year - label_date_range: Date range - label_less_than_ago: less than days ago - label_more_than_ago: more than days ago - label_ago: days ago - label_contains: contains - label_not_contains: doesn't contain - label_day_plural: days - label_repository: Repository - label_repository_plural: Repositories - label_browse: Browse - label_modification: "%{count} change" - label_modification_plural: "%{count} changes" - label_branch: Branch - label_tag: Tag - label_revision: Revision - label_revision_plural: Revisions - label_revision_id: "Revision %{value}" - label_associated_revisions: Associated revisions - label_added: added - label_modified: modified - label_copied: copied - label_renamed: renamed - label_deleted: deleted - label_latest_revision: Latest revision - label_latest_revision_plural: Latest revisions - label_view_revisions: View revisions - label_view_all_revisions: View all revisions - label_max_size: Maximum size - label_sort_highest: Move to top - label_sort_higher: Move up - label_sort_lower: Move down - label_sort_lowest: Move to bottom - label_roadmap: Roadmap - label_roadmap_due_in: "Due in %{value}" - label_roadmap_overdue: "%{value} late" - label_roadmap_no_issues: No issues for this version - label_search: Search - label_result_plural: Results - label_all_words: All words - label_wiki: Wiki - label_wiki_edit: Wiki edit - label_wiki_edit_plural: Wiki edits - label_wiki_page: Wiki page - label_wiki_page_plural: Wiki pages - label_index_by_title: Index by title - label_index_by_date: Index by date - label_current_version: Current version - label_preview: Preview - label_feed_plural: Feeds - label_changes_details: Details of all changes - label_issue_tracking: Issue tracking - label_spent_time: Spent time - label_f_hour: "%{value} hour" - label_f_hour_plural: "%{value} hours" - label_time_tracking: Time tracking - label_change_plural: Changes - label_statistics: Statistics - label_commits_per_month: Commits per month - label_commits_per_author: Commits per author - label_view_diff: View differences - label_diff_inline: inline - label_diff_side_by_side: side by side - label_options: Options - label_copy_workflow_from: Copy workflow from - label_permissions_report: Permissions report - label_watched_issues: Watched issues - label_related_issues: Related issues - label_applied_status: Applied status - label_loading: Loading... - label_relation_new: New relation - label_relation_delete: Delete relation - label_relates_to: related to - label_duplicates: duplicates - label_duplicated_by: duplicated by - label_blocks: blocks - label_blocked_by: blocked by - label_precedes: precedes - label_follows: follows - label_end_to_start: end to start - label_end_to_end: end to end - label_start_to_start: start to start - label_start_to_end: start to end - label_stay_logged_in: Stay logged in - label_disabled: disabled - label_show_completed_versions: Show completed versions - label_me: me - label_board: Forum - label_board_new: New forum - label_board_plural: Forums - label_board_locked: Locked - label_board_sticky: Sticky - label_topic_plural: Topics - label_message_plural: Messages - label_message_last: Last message - label_message_new: New message - label_message_posted: Message added - label_reply_plural: Replies - label_send_information: Send account information to the user - label_year: Year - label_month: Month - label_week: Week - label_date_from: From - label_date_to: To - label_language_based: Based on user's language - label_sort_by: "Sort by %{value}" - label_send_test_email: Send a test email - label_feeds_access_key: RSS access key - label_missing_feeds_access_key: Missing a RSS access key - label_feeds_access_key_created_on: "RSS access key created %{value} ago" - label_module_plural: Modules - label_added_time_by: "Added by %{author} %{age} ago" - label_updated_time_by: "Updated by %{author} %{age} ago" - label_updated_time: "Updated %{value} ago" - label_jump_to_a_project: Jump to a project... - label_file_plural: Files - label_changeset_plural: Changesets - label_default_columns: Default columns - label_no_change_option: (No change) - label_bulk_edit_selected_issues: Bulk edit selected issues - label_theme: Theme - label_default: Default - label_search_titles_only: Search titles only - label_user_mail_option_all: "For any event on all my projects" - label_user_mail_option_selected: "For any event on the selected projects only..." - label_user_mail_option_none: "No events" - label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" - label_registration_activation_by_email: account activation by email - label_registration_manual_activation: manual account activation - label_registration_automatic_activation: automatic account activation - label_display_per_page: "Per page: %{value}" - label_age: Age - label_change_properties: Change properties - label_general: General - label_more: More - label_scm: SCM - label_plugins: Plugins - label_ldap_authentication: LDAP authentication - label_downloads_abbr: D/L - label_optional_description: Optional description - label_add_another_file: Add another file - label_preferences: Preferences - label_chronological_order: In chronological order - label_reverse_chronological_order: In reverse chronological order - label_planning: Planning - label_incoming_emails: Incoming emails - label_generate_key: Generate a key - label_issue_watchers: Watchers - label_example: Example - label_display: Display - label_sort: Sort - label_ascending: Ascending - label_descending: Descending - label_date_from_to: From %{start} to %{end} - label_wiki_content_added: Wiki page added - label_wiki_content_updated: Wiki page updated - label_group: Group - label_group_plural: Groups - label_group_new: New group - label_time_entry_plural: Spent time - label_version_sharing_none: Not shared - label_version_sharing_descendants: With subprojects - label_version_sharing_hierarchy: With project hierarchy - label_version_sharing_tree: With project tree - label_version_sharing_system: With all projects - label_update_issue_done_ratios: Update issue done ratios - label_copy_source: Source - label_copy_target: Target - label_copy_same_as_target: Same as target - label_display_used_statuses_only: Only display statuses that are used by this tracker - label_api_access_key: API access key - label_missing_api_access_key: Missing an API access key - label_api_access_key_created_on: "API access key created %{value} ago" - - button_login: Login - button_submit: Submit - button_save: Save - button_check_all: Check all - button_uncheck_all: Uncheck all - button_delete: Delete - button_create: Create - button_create_and_continue: Create and continue - button_test: Test - button_edit: Edit - button_add: Add - button_change: Change - button_apply: Apply - button_clear: Clear - button_lock: Lock - button_unlock: Unlock - button_download: Download - button_list: List - button_view: View - button_move: Move - button_move_and_follow: Move and follow - button_back: Back - button_cancel: Cancel - button_activate: Activate - button_sort: Sort - button_log_time: Log time - button_rollback: Rollback to this version - button_watch: Watch - button_unwatch: Unwatch - button_reply: Reply - button_archive: Archive - button_unarchive: Unarchive - button_reset: Reset - button_rename: Rename - button_change_password: Change password - button_copy: Copy - button_copy_and_follow: Copy and follow - button_annotate: Annotate - button_update: Update - button_configure: Configure - button_quote: Quote - button_duplicate: Duplicate - button_show: Show - - status_active: active - status_registered: registered - status_locked: locked - - version_status_open: open - version_status_locked: locked - version_status_closed: closed - - field_active: Active - - text_select_mail_notifications: Select actions for which email notifications should be sent. - text_regexp_info: eg. ^[A-Z0-9]+$ - text_min_max_length_info: 0 means no restriction - text_project_destroy_confirmation: Are you sure you want to delete this project and related data? - text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." - text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Are you sure? - text_journal_changed: "%{label} changed from %{old} to %{new}" - text_journal_set_to: "%{label} set to %{value}" - text_journal_deleted: "%{label} deleted (%{old})" - text_journal_added: "%{label} %{value} added" - text_tip_issue_begin_day: task beginning this day - text_tip_issue_end_day: task ending this day - text_tip_issue_begin_end_day: task beginning and ending this day - text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier can not be changed.' - text_caracters_maximum: "%{count} characters maximum." - text_caracters_minimum: "Must be at least %{count} characters long." - text_length_between: "Length between %{min} and %{max} characters." - text_tracker_no_workflow: No workflow defined for this tracker - text_unallowed_characters: Unallowed characters - text_comma_separated: Multiple values allowed (comma separated). - text_line_separated: Multiple values allowed (one line for each value). - text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages - text_issue_added: "Issue %{id} has been reported by %{author}." - text_issue_updated: "Issue %{id} has been updated by %{author}." - text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? - text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" - text_issue_category_destroy_assignments: Remove category assignments - text_issue_category_reassign_to: Reassign issues to this category - text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." - text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." - text_load_default_configuration: Load the default configuration - text_status_changed_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' - text_select_project_modules: 'Select modules to enable for this project:' - text_default_administrator_account_changed: Default administrator account changed - text_file_repository_writable: Attachments directory writable - text_plugin_assets_writable: Plugin assets directory writable - text_rmagick_available: RMagick available (optional) - text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" - text_destroy_time_entries: Delete reported hours - text_assign_time_entries_to_project: Assign reported hours to the project - text_reassign_time_entries: 'Reassign reported hours to this issue:' - text_user_wrote: "%{value} wrote:" - text_enumeration_destroy_question: "%{count} objects are assigned to this value." - text_enumeration_category_reassign_to: 'Reassign them to this value:' - text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." - text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." - text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' - text_custom_field_possible_values_info: 'One line for each value' - text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" - text_wiki_page_nullify_children: "Keep child pages as root pages" - text_wiki_page_destroy_children: "Delete child pages and all their descendants" - text_wiki_page_reassign_children: "Reassign child pages to this parent page" - text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" - - default_role_manager: Manager - default_role_developer: Developer - default_role_reporter: Reporter - default_role_non_member: Non member - default_role_anonymous: Anonymous - default_tracker_bug: Bug - default_tracker_feature: Feature - default_tracker_support: Support - default_issue_status_new: New - default_issue_status_in_progress: In Progress - default_issue_status_resolved: Resolved - default_issue_status_feedback: Feedback - default_issue_status_closed: Closed - default_issue_status_rejected: Rejected - default_doc_category_user: User documentation - default_doc_category_tech: Technical documentation - default_priority_low: Low - default_priority_normal: Normal - default_priority_high: High - default_priority_urgent: Urgent - default_priority_immediate: Immediate - default_activity_design: Design - default_activity_development: Development - - enumeration_issue_priorities: Issue priorities - enumeration_doc_categories: Document categories - enumeration_activities: Activities (time tracking) - enumeration_system_activity: System Activity - - notice_unable_delete_time_entry: Unable to delete time log entry. - error_can_not_delete_custom_field: Unable to delete custom field - permission_manage_subtasks: Manage subtasks - label_profile: Profile - error_unable_to_connect: Unable to connect (%{value}) - label_overall_spent_time: Overall spent time - error_can_not_remove_role: This role is in use and can not be deleted. - field_principal: Principal - field_parent_issue: Parent task - label_my_page_block: My page block - text_zoom_out: Zoom out - text_zoom_in: Zoom in - error_unable_delete_issue_status: Unable to delete issue status - label_subtask_plural: Subtasks - error_can_not_delete_tracker: This tracker contains issues and can't be deleted. - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - label_project_copy_notifications: Send email notifications during the project copy - field_time_entries: Log time - project_module_gantt: Gantt - project_module_calendar: Calendar - field_member_of_group: Member of Group - field_assigned_to_role: Member of Role - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - text_powered_by: "Powered by %{link}" +en-GB: + direction: ltr + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%d/%m/%Y" + short: "%d %b" + long: "%d %B, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datime_select. + order: [ :year, :month, :day ] + + time: + formats: + default: "%d/%m/%Y %I:%M %p" + time: "%I:%M %p" + short: "%d %b %H:%M" + long: "%d %B, %Y %H:%M" + am: "am" + pm: "pm" + + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + + number: + format: + separator: "." + delimiter: " " + precision: 3 + + currency: + format: + format: "%u%n" + unit: "£" + + human: + format: + delimiter: "" + precision: 1 + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "kB" + mb: "MB" + gb: "GB" + tb: "TB" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "and" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 error prohibited this %{model} from being saved" + other: "%{count} errors prohibited this %{model} from being saved" + messages: + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match confirmation" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + too_long: "is too long (maximum is %{count} characters)" + too_short: "is too short (minimum is %{count} characters)" + wrong_length: "is the wrong length (should be %{count} characters)" + taken: "has already been taken" + not_a_number: "is not a number" + not_a_date: "is not a valid date" + greater_than: "must be greater than %{count}" + greater_than_or_equal_to: "must be greater than or equal to %{count}" + equal_to: "must be equal to %{count}" + less_than: "must be less than %{count}" + less_than_or_equal_to: "must be less than or equal to %{count}" + odd: "must be odd" + even: "must be even" + greater_than_start_date: "must be greater than start date" + not_same_project: "doesn't belong to the same project" + circular_dependency: "This relation would create a circular dependency" + cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks" + + actionview_instancetag_blank_option: Please select + + general_text_No: 'No' + general_text_Yes: 'Yes' + general_text_no: 'no' + general_text_yes: 'yes' + general_lang_name: 'English (British)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: ISO-8859-1 + general_pdf_encoding: ISO-8859-1 + general_first_day_of_week: '1' + + notice_account_updated: Account was successfully updated. + notice_account_invalid_creditentials: Invalid user or password + notice_account_password_updated: Password was successfully updated. + notice_account_wrong_password: Wrong password + notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you. + notice_account_unknown_email: Unknown user. + notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password. + notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you. + notice_account_activated: Your account has been activated. You can now log in. + notice_successful_create: Successful creation. + notice_successful_update: Successful update. + notice_successful_delete: Successful deletion. + notice_successful_connection: Successful connection. + notice_file_not_found: The page you were trying to access doesn't exist or has been removed. + notice_locking_conflict: Data has been updated by another user. + notice_not_authorized: You are not authorised to access this page. + notice_not_authorized_archived_project: The project you're trying to access has been archived. + notice_email_sent: "An email was sent to %{value}" + notice_email_error: "An error occurred while sending mail (%{value})" + notice_feeds_access_key_reseted: Your RSS access key was reset. + notice_api_access_key_reseted: Your API access key was reset. + notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}." + notice_failed_to_save_members: "Failed to save member(s): %{errors}." + notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit." + notice_account_pending: "Your account was created and is now pending administrator approval." + notice_default_data_loaded: Default configuration successfully loaded. + notice_unable_delete_version: Unable to delete version. + notice_unable_delete_time_entry: Unable to delete time log entry. + notice_issue_done_ratios_updated: Issue done ratios updated. + notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})" + + error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" + error_scm_not_found: "The entry or revision was not found in the repository." + error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" + error_scm_annotate: "The entry does not exist or cannot be annotated." + error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' + error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' + error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' + error_can_not_delete_custom_field: Unable to delete custom field + error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." + error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' + error_can_not_archive_project: This project cannot be archived + error_issue_done_ratios_not_updated: "Issue done ratios not updated." + error_workflow_copy_source: 'Please select a source tracker or role' + error_workflow_copy_target: 'Please select target tracker(s) and role(s)' + error_unable_delete_issue_status: 'Unable to delete issue status' + error_unable_to_connect: "Unable to connect (%{value})" + warning_attachments_not_saved: "%{count} file(s) could not be saved." + + mail_subject_lost_password: "Your %{value} password" + mail_body_lost_password: 'To change your password, click on the following link:' + mail_subject_register: "Your %{value} account activation" + mail_body_register: 'To activate your account, click on the following link:' + mail_body_account_information_external: "You can use your %{value} account to log in." + mail_body_account_information: Your account information + mail_subject_account_activation_request: "%{value} account activation request" + mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:" + mail_subject_reminder: "%{count} issue(s) due in the next %{days} days" + mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:" + mail_subject_wiki_content_added: "'%{id}' wiki page has been added" + mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}." + mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated" + mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}." + + gui_validation_error: 1 error + gui_validation_error_plural: "%{count} errors" + + field_name: Name + field_description: Description + field_summary: Summary + field_is_required: Required + field_firstname: First name + field_lastname: Last name + field_mail: Email + field_filename: File + field_filesize: Size + field_downloads: Downloads + field_author: Author + field_created_on: Created + field_updated_on: Updated + field_field_format: Format + field_is_for_all: For all projects + field_possible_values: Possible values + field_regexp: Regular expression + field_min_length: Minimum length + field_max_length: Maximum length + field_value: Value + field_category: Category + field_title: Title + field_project: Project + field_issue: Issue + field_status: Status + field_notes: Notes + field_is_closed: Issue closed + field_is_default: Default value + field_tracker: Tracker + field_subject: Subject + field_due_date: Due date + field_assigned_to: Assignee + field_priority: Priority + field_fixed_version: Target version + field_user: User + field_principal: Principal + field_role: Role + field_homepage: Homepage + field_is_public: Public + field_parent: Subproject of + field_is_in_roadmap: Issues displayed in roadmap + field_login: Login + field_mail_notification: Email notifications + field_admin: Administrator + field_last_login_on: Last connection + field_language: Language + field_effective_date: Due date + field_password: Password + field_new_password: New password + field_password_confirmation: Confirmation + field_version: Version + field_type: Type + field_host: Host + field_port: Port + field_account: Account + field_base_dn: Base DN + field_attr_login: Login attribute + field_attr_firstname: Firstname attribute + field_attr_lastname: Lastname attribute + field_attr_mail: Email attribute + field_onthefly: On-the-fly user creation + field_start_date: Start date + field_done_ratio: % Done + field_auth_source: Authentication mode + field_hide_mail: Hide my email address + field_comments: Comment + field_url: URL + field_start_page: Start page + field_subproject: Subproject + field_hours: Hours + field_activity: Activity + field_spent_on: Date + field_identifier: Identifier + field_is_filter: Used as a filter + field_issue_to: Related issue + field_delay: Delay + field_assignable: Issues can be assigned to this role + field_redirect_existing_links: Redirect existing links + field_estimated_hours: Estimated time + field_column_names: Columns + field_time_entries: Log time + field_time_zone: Time zone + field_searchable: Searchable + field_default_value: Default value + field_comments_sorting: Display comments + field_parent_title: Parent page + field_editable: Editable + field_watcher: Watcher + field_identity_url: OpenID URL + field_content: Content + field_group_by: Group results by + field_sharing: Sharing + field_parent_issue: Parent task + field_member_of_group: "Assignee's group" + field_assigned_to_role: "Assignee's role" + field_text: Text field + field_visible: Visible + field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" + + setting_app_title: Application title + setting_app_subtitle: Application subtitle + setting_welcome_text: Welcome text + setting_default_language: Default language + setting_login_required: Authentication required + setting_self_registration: Self-registration + setting_attachment_max_size: Attachment max. size + setting_issues_export_limit: Issues export limit + setting_mail_from: Emission email address + setting_bcc_recipients: Blind carbon copy recipients (bcc) + setting_plain_text_mail: Plain text mail (no HTML) + setting_host_name: Host name and path + setting_text_formatting: Text formatting + setting_wiki_compression: Wiki history compression + setting_feeds_limit: Feed content limit + setting_default_projects_public: New projects are public by default + setting_autofetch_changesets: Autofetch commits + setting_sys_api_enabled: Enable WS for repository management + setting_commit_ref_keywords: Referencing keywords + setting_commit_fix_keywords: Fixing keywords + setting_autologin: Autologin + setting_date_format: Date format + setting_time_format: Time format + setting_cross_project_issue_relations: Allow cross-project issue relations + setting_issue_list_default_columns: Default columns displayed on the issue list + setting_repositories_encodings: Repositories encodings + setting_commit_logs_encoding: Commit messages encoding + setting_emails_header: Emails header + setting_emails_footer: Emails footer + setting_protocol: Protocol + setting_per_page_options: Objects per page options + setting_user_format: Users display format + setting_activity_days_default: Days displayed on project activity + setting_display_subprojects_issues: Display subprojects issues on main projects by default + setting_enabled_scm: Enabled SCM + setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" + setting_mail_handler_api_enabled: Enable WS for incoming emails + setting_mail_handler_api_key: API key + setting_sequential_project_identifiers: Generate sequential project identifiers + setting_gravatar_enabled: Use Gravatar user icons + setting_gravatar_default: Default Gravatar image + setting_diff_max_lines_displayed: Max number of diff lines displayed + setting_file_max_size_displayed: Max size of text files displayed inline + setting_repository_log_display_limit: Maximum number of revisions displayed on file log + setting_openid: Allow OpenID login and registration + setting_password_min_length: Minimum password length + setting_new_project_user_role_id: Role given to a non-admin user who creates a project + setting_default_projects_modules: Default enabled modules for new projects + setting_issue_done_ratio: Calculate the issue done ratio with + setting_issue_done_ratio_issue_field: Use the issue field + setting_issue_done_ratio_issue_status: Use the issue status + setting_start_of_week: Start calendars on + setting_rest_api_enabled: Enable REST web service + setting_cache_formatted_text: Cache formatted text + setting_default_notification_option: Default notification option + setting_commit_logtime_enabled: Enable time logging + setting_commit_logtime_activity_id: Activity for logged time + setting_gantt_items_limit: Maximum number of items displayed on the gantt chart + + permission_add_project: Create project + permission_add_subprojects: Create subprojects + permission_edit_project: Edit project + permission_select_project_modules: Select project modules + permission_manage_members: Manage members + permission_manage_project_activities: Manage project activities + permission_manage_versions: Manage versions + permission_manage_categories: Manage issue categories + permission_view_issues: View Issues + permission_add_issues: Add issues + permission_edit_issues: Edit issues + permission_manage_issue_relations: Manage issue relations + permission_add_issue_notes: Add notes + permission_edit_issue_notes: Edit notes + permission_edit_own_issue_notes: Edit own notes + permission_move_issues: Move issues + permission_delete_issues: Delete issues + permission_manage_public_queries: Manage public queries + permission_save_queries: Save queries + permission_view_gantt: View gantt chart + permission_view_calendar: View calendar + permission_view_issue_watchers: View watchers list + permission_add_issue_watchers: Add watchers + permission_delete_issue_watchers: Delete watchers + permission_log_time: Log spent time + permission_view_time_entries: View spent time + permission_edit_time_entries: Edit time logs + permission_edit_own_time_entries: Edit own time logs + permission_manage_news: Manage news + permission_comment_news: Comment news + permission_manage_documents: Manage documents + permission_view_documents: View documents + permission_manage_files: Manage files + permission_view_files: View files + permission_manage_wiki: Manage wiki + permission_rename_wiki_pages: Rename wiki pages + permission_delete_wiki_pages: Delete wiki pages + permission_view_wiki_pages: View wiki + permission_view_wiki_edits: View wiki history + permission_edit_wiki_pages: Edit wiki pages + permission_delete_wiki_pages_attachments: Delete attachments + permission_protect_wiki_pages: Protect wiki pages + permission_manage_repository: Manage repository + permission_browse_repository: Browse repository + permission_view_changesets: View changesets + permission_commit_access: Commit access + permission_manage_boards: Manage forums + permission_view_messages: View messages + permission_add_messages: Post messages + permission_edit_messages: Edit messages + permission_edit_own_messages: Edit own messages + permission_delete_messages: Delete messages + permission_delete_own_messages: Delete own messages + permission_export_wiki_pages: Export wiki pages + permission_manage_subtasks: Manage subtasks + + project_module_issue_tracking: Issue tracking + project_module_time_tracking: Time tracking + project_module_news: News + project_module_documents: Documents + project_module_files: Files + project_module_wiki: Wiki + project_module_repository: Repository + project_module_boards: Forums + project_module_calendar: Calendar + project_module_gantt: Gantt + + label_user: User + label_user_plural: Users + label_user_new: New user + label_user_anonymous: Anonymous + label_project: Project + label_project_new: New project + label_project_plural: Projects + label_x_projects: + zero: no projects + one: 1 project + other: "%{count} projects" + label_project_all: All Projects + label_project_latest: Latest projects + label_issue: Issue + label_issue_new: New issue + label_issue_plural: Issues + label_issue_view_all: View all issues + label_issues_by: "Issues by %{value}" + label_issue_added: Issue added + label_issue_updated: Issue updated + label_document: Document + label_document_new: New document + label_document_plural: Documents + label_document_added: Document added + label_role: Role + label_role_plural: Roles + label_role_new: New role + label_role_and_permissions: Roles and permissions + label_member: Member + label_member_new: New member + label_member_plural: Members + label_tracker: Tracker + label_tracker_plural: Trackers + label_tracker_new: New tracker + label_workflow: Workflow + label_issue_status: Issue status + label_issue_status_plural: Issue statuses + label_issue_status_new: New status + label_issue_category: Issue category + label_issue_category_plural: Issue categories + label_issue_category_new: New category + label_custom_field: Custom field + label_custom_field_plural: Custom fields + label_custom_field_new: New custom field + label_enumerations: Enumerations + label_enumeration_new: New value + label_information: Information + label_information_plural: Information + label_please_login: Please log in + label_register: Register + label_login_with_open_id_option: or login with OpenID + label_password_lost: Lost password + label_home: Home + label_my_page: My page + label_my_account: My account + label_my_projects: My projects + label_my_page_block: My page block + label_administration: Administration + label_login: Sign in + label_logout: Sign out + label_help: Help + label_reported_issues: Reported issues + label_assigned_to_me_issues: Issues assigned to me + label_last_login: Last connection + label_registered_on: Registered on + label_activity: Activity + label_overall_activity: Overall activity + label_user_activity: "%{value}'s activity" + label_new: New + label_logged_as: Logged in as + label_environment: Environment + label_authentication: Authentication + label_auth_source: Authentication mode + label_auth_source_new: New authentication mode + label_auth_source_plural: Authentication modes + label_subproject_plural: Subprojects + label_subproject_new: New subproject + label_and_its_subprojects: "%{value} and its subprojects" + label_min_max_length: Min - Max length + label_list: List + label_date: Date + label_integer: Integer + label_float: Float + label_boolean: Boolean + label_string: Text + label_text: Long text + label_attribute: Attribute + label_attribute_plural: Attributes + label_download: "%{count} Download" + label_download_plural: "%{count} Downloads" + label_no_data: No data to display + label_change_status: Change status + label_history: History + label_attachment: File + label_attachment_new: New file + label_attachment_delete: Delete file + label_attachment_plural: Files + label_file_added: File added + label_report: Report + label_report_plural: Reports + label_news: News + label_news_new: Add news + label_news_plural: News + label_news_latest: Latest news + label_news_view_all: View all news + label_news_added: News added + label_news_comment_added: Comment added to a news + label_settings: Settings + label_overview: Overview + label_version: Version + label_version_new: New version + label_version_plural: Versions + label_close_versions: Close completed versions + label_confirmation: Confirmation + label_export_to: 'Also available in:' + label_read: Read... + label_public_projects: Public projects + label_open_issues: open + label_open_issues_plural: open + label_closed_issues: closed + label_closed_issues_plural: closed + label_x_open_issues_abbr_on_total: + zero: 0 open / %{total} + one: 1 open / %{total} + other: "%{count} open / %{total}" + label_x_open_issues_abbr: + zero: 0 open + one: 1 open + other: "%{count} open" + label_x_closed_issues_abbr: + zero: 0 closed + one: 1 closed + other: "%{count} closed" + label_total: Total + label_permissions: Permissions + label_current_status: Current status + label_new_statuses_allowed: New statuses allowed + label_all: all + label_none: none + label_nobody: nobody + label_next: Next + label_previous: Previous + label_used_by: Used by + label_details: Details + label_add_note: Add a note + label_per_page: Per page + label_calendar: Calendar + label_months_from: months from + label_gantt: Gantt + label_internal: Internal + label_last_changes: "last %{count} changes" + label_change_view_all: View all changes + label_personalize_page: Personalise this page + label_comment: Comment + label_comment_plural: Comments + label_x_comments: + zero: no comments + one: 1 comment + other: "%{count} comments" + label_comment_add: Add a comment + label_comment_added: Comment added + label_comment_delete: Delete comments + label_query: Custom query + label_query_plural: Custom queries + label_query_new: New query + label_my_queries: My custom queries + label_filter_add: Add filter + label_filter_plural: Filters + label_equals: is + label_not_equals: is not + label_in_less_than: in less than + label_in_more_than: in more than + label_greater_or_equal: '>=' + label_less_or_equal: '<=' + label_in: in + label_today: today + label_all_time: all time + label_yesterday: yesterday + label_this_week: this week + label_last_week: last week + label_last_n_days: "last %{count} days" + label_this_month: this month + label_last_month: last month + label_this_year: this year + label_date_range: Date range + label_less_than_ago: less than days ago + label_more_than_ago: more than days ago + label_ago: days ago + label_contains: contains + label_not_contains: doesn't contain + label_day_plural: days + label_repository: Repository + label_repository_plural: Repositories + label_browse: Browse + label_modification: "%{count} change" + label_modification_plural: "%{count} changes" + label_branch: Branch + label_tag: Tag + label_revision: Revision + label_revision_plural: Revisions + label_revision_id: "Revision %{value}" + label_associated_revisions: Associated revisions + label_added: added + label_modified: modified + label_copied: copied + label_renamed: renamed + label_deleted: deleted + label_latest_revision: Latest revision + label_latest_revision_plural: Latest revisions + label_view_revisions: View revisions + label_view_all_revisions: View all revisions + label_max_size: Maximum size + label_sort_highest: Move to top + label_sort_higher: Move up + label_sort_lower: Move down + label_sort_lowest: Move to bottom + label_roadmap: Roadmap + label_roadmap_due_in: "Due in %{value}" + label_roadmap_overdue: "%{value} late" + label_roadmap_no_issues: No issues for this version + label_search: Search + label_result_plural: Results + label_all_words: All words + label_wiki: Wiki + label_wiki_edit: Wiki edit + label_wiki_edit_plural: Wiki edits + label_wiki_page: Wiki page + label_wiki_page_plural: Wiki pages + label_index_by_title: Index by title + label_index_by_date: Index by date + label_current_version: Current version + label_preview: Preview + label_feed_plural: Feeds + label_changes_details: Details of all changes + label_issue_tracking: Issue tracking + label_spent_time: Spent time + label_overall_spent_time: Overall spent time + label_f_hour: "%{value} hour" + label_f_hour_plural: "%{value} hours" + label_time_tracking: Time tracking + label_change_plural: Changes + label_statistics: Statistics + label_commits_per_month: Commits per month + label_commits_per_author: Commits per author + label_view_diff: View differences + label_diff_inline: inline + label_diff_side_by_side: side by side + label_options: Options + label_copy_workflow_from: Copy workflow from + label_permissions_report: Permissions report + label_watched_issues: Watched issues + label_related_issues: Related issues + label_applied_status: Applied status + label_loading: Loading... + label_relation_new: New relation + label_relation_delete: Delete relation + label_relates_to: related to + label_duplicates: duplicates + label_duplicated_by: duplicated by + label_blocks: blocks + label_blocked_by: blocked by + label_precedes: precedes + label_follows: follows + label_end_to_start: end to start + label_end_to_end: end to end + label_start_to_start: start to start + label_start_to_end: start to end + label_stay_logged_in: Stay logged in + label_disabled: disabled + label_show_completed_versions: Show completed versions + label_me: me + label_board: Forum + label_board_new: New forum + label_board_plural: Forums + label_board_locked: Locked + label_board_sticky: Sticky + label_topic_plural: Topics + label_message_plural: Messages + label_message_last: Last message + label_message_new: New message + label_message_posted: Message added + label_reply_plural: Replies + label_send_information: Send account information to the user + label_year: Year + label_month: Month + label_week: Week + label_date_from: From + label_date_to: To + label_language_based: Based on user's language + label_sort_by: "Sort by %{value}" + label_send_test_email: Send a test email + label_feeds_access_key: RSS access key + label_missing_feeds_access_key: Missing a RSS access key + label_feeds_access_key_created_on: "RSS access key created %{value} ago" + label_module_plural: Modules + label_added_time_by: "Added by %{author} %{age} ago" + label_updated_time_by: "Updated by %{author} %{age} ago" + label_updated_time: "Updated %{value} ago" + label_jump_to_a_project: Jump to a project... + label_file_plural: Files + label_changeset_plural: Changesets + label_default_columns: Default columns + label_no_change_option: (No change) + label_bulk_edit_selected_issues: Bulk edit selected issues + label_theme: Theme + label_default: Default + label_search_titles_only: Search titles only + label_user_mail_option_all: "For any event on all my projects" + label_user_mail_option_selected: "For any event on the selected projects only..." + label_user_mail_option_none: "No events" + label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in" + label_user_mail_option_only_assigned: "Only for things I am assigned to" + label_user_mail_option_only_owner: "Only for things I am the owner of" + label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself" + label_registration_activation_by_email: account activation by email + label_registration_manual_activation: manual account activation + label_registration_automatic_activation: automatic account activation + label_display_per_page: "Per page: %{value}" + label_age: Age + label_change_properties: Change properties + label_general: General + label_more: More + label_scm: SCM + label_plugins: Plugins + label_ldap_authentication: LDAP authentication + label_downloads_abbr: D/L + label_optional_description: Optional description + label_add_another_file: Add another file + label_preferences: Preferences + label_chronological_order: In chronological order + label_reverse_chronological_order: In reverse chronological order + label_planning: Planning + label_incoming_emails: Incoming emails + label_generate_key: Generate a key + label_issue_watchers: Watchers + label_example: Example + label_display: Display + label_sort: Sort + label_ascending: Ascending + label_descending: Descending + label_date_from_to: From %{start} to %{end} + label_wiki_content_added: Wiki page added + label_wiki_content_updated: Wiki page updated + label_group: Group + label_group_plural: Groups + label_group_new: New group + label_time_entry_plural: Spent time + label_version_sharing_none: Not shared + label_version_sharing_descendants: With subprojects + label_version_sharing_hierarchy: With project hierarchy + label_version_sharing_tree: With project tree + label_version_sharing_system: With all projects + label_update_issue_done_ratios: Update issue done ratios + label_copy_source: Source + label_copy_target: Target + label_copy_same_as_target: Same as target + label_display_used_statuses_only: Only display statuses that are used by this tracker + label_api_access_key: API access key + label_missing_api_access_key: Missing an API access key + label_api_access_key_created_on: "API access key created %{value} ago" + label_profile: Profile + label_subtask_plural: Subtasks + label_project_copy_notifications: Send email notifications during the project copy + label_principal_search: "Search for user or group:" + label_user_search: "Search for user:" + + button_login: Login + button_submit: Submit + button_save: Save + button_check_all: Check all + button_uncheck_all: Uncheck all + button_collapse_all: Collapse all + button_expand_all: Expand all + button_delete: Delete + button_create: Create + button_create_and_continue: Create and continue + button_test: Test + button_edit: Edit + button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" + button_add: Add + button_change: Change + button_apply: Apply + button_clear: Clear + button_lock: Lock + button_unlock: Unlock + button_download: Download + button_list: List + button_view: View + button_move: Move + button_move_and_follow: Move and follow + button_back: Back + button_cancel: Cancel + button_activate: Activate + button_sort: Sort + button_log_time: Log time + button_rollback: Rollback to this version + button_watch: Watch + button_unwatch: Unwatch + button_reply: Reply + button_archive: Archive + button_unarchive: Unarchive + button_reset: Reset + button_rename: Rename + button_change_password: Change password + button_copy: Copy + button_copy_and_follow: Copy and follow + button_annotate: Annotate + button_update: Update + button_configure: Configure + button_quote: Quote + button_duplicate: Duplicate + button_show: Show + + status_active: active + status_registered: registered + status_locked: locked + + version_status_open: open + version_status_locked: locked + version_status_closed: closed + + field_active: Active + + text_select_mail_notifications: Select actions for which email notifications should be sent. + text_regexp_info: eg. ^[A-Z0-9]+$ + text_min_max_length_info: 0 means no restriction + text_project_destroy_confirmation: Are you sure you want to delete this project and related data? + text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." + text_workflow_edit: Select a role and a tracker to edit the workflow + text_are_you_sure: Are you sure? + text_are_you_sure_with_children: Delete issue and all child issues? + text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_changed_no_detail: "%{label} updated" + text_journal_set_to: "%{label} set to %{value}" + text_journal_deleted: "%{label} deleted (%{old})" + text_journal_added: "%{label} %{value} added" + text_tip_issue_begin_day: task beginning this day + text_tip_issue_end_day: task ending this day + text_tip_issue_begin_end_day: task beginning and ending this day + text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed.' + text_caracters_maximum: "%{count} characters maximum." + text_caracters_minimum: "Must be at least %{count} characters long." + text_length_between: "Length between %{min} and %{max} characters." + text_tracker_no_workflow: No workflow defined for this tracker + text_unallowed_characters: Unallowed characters + text_comma_separated: Multiple values allowed (comma separated). + text_line_separated: Multiple values allowed (one line for each value). + text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages + text_issue_added: "Issue %{id} has been reported by %{author}." + text_issue_updated: "Issue %{id} has been updated by %{author}." + text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? + text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" + text_issue_category_destroy_assignments: Remove category assignments + text_issue_category_reassign_to: Reassign issues to this category + text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." + text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded." + text_load_default_configuration: Load the default configuration + text_status_changed_by_changeset: "Applied in changeset %{value}." + text_time_logged_by_changeset: "Applied in changeset %{value}." + text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' + text_select_project_modules: 'Select modules to enable for this project:' + text_default_administrator_account_changed: Default administrator account changed + text_file_repository_writable: Attachments directory writable + text_plugin_assets_writable: Plugin assets directory writable + text_rmagick_available: RMagick available (optional) + text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" + text_destroy_time_entries: Delete reported hours + text_assign_time_entries_to_project: Assign reported hours to the project + text_reassign_time_entries: 'Reassign reported hours to this issue:' + text_user_wrote: "%{value} wrote:" + text_enumeration_destroy_question: "%{count} objects are assigned to this value." + text_enumeration_category_reassign_to: 'Reassign them to this value:' + text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them." + text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped." + text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.' + text_custom_field_possible_values_info: 'One line for each value' + text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?" + text_wiki_page_nullify_children: "Keep child pages as root pages" + text_wiki_page_destroy_children: "Delete child pages and all their descendants" + text_wiki_page_reassign_children: "Reassign child pages to this parent page" + text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?" + text_zoom_in: Zoom in + text_zoom_out: Zoom out + text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." + + default_role_manager: Manager + default_role_developer: Developer + default_role_reporter: Reporter + default_role_non_member: Non member + default_role_anonymous: Anonymous + default_tracker_bug: Bug + default_tracker_feature: Feature + default_tracker_support: Support + default_issue_status_new: New + default_issue_status_in_progress: In Progress + default_issue_status_resolved: Resolved + default_issue_status_feedback: Feedback + default_issue_status_closed: Closed + default_issue_status_rejected: Rejected + default_doc_category_user: User documentation + default_doc_category_tech: Technical documentation + default_priority_low: Low + default_priority_normal: Normal + default_priority_high: High + default_priority_urgent: Urgent + default_priority_immediate: Immediate + default_activity_design: Design + default_activity_development: Development + + enumeration_issue_priorities: Issue priorities + enumeration_doc_categories: Document categories + enumeration_activities: Activities (time tracking) + enumeration_system_activity: System Activity + + text_powered_by: "Powered by %{link}" label_cvs_module: Module label_filesystem_path: Root directory label_darcs_path: Root directory @@ -950,3 +957,5 @@ en-GB: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author diff --git a/config/locales/en.yml b/config/locales/en.yml index ede28c4f..5ddc0b71 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -66,11 +66,11 @@ en: other: "almost %{count} years" number: - # Default format for numbers format: separator: "." delimiter: "" precision: 3 + human: format: delimiter: "" @@ -123,7 +123,7 @@ en: greater_than_start_date: "must be greater than start date" not_same_project: "doesn't belong to the same project" circular_dependency: "This relation would create a 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: "An issue cannot be linked to one of its subtasks" actionview_instancetag_blank_option: Please select @@ -172,15 +172,15 @@ en: error_can_t_load_default_data: "Default configuration could not be loaded: %{value}" error_scm_not_found: "The entry or revision was not found in the repository." error_scm_command_failed: "An error occurred when trying to access the repository: %{value}" - error_scm_annotate: "The entry does not exist or can not be annotated." + error_scm_annotate: "The entry does not exist or cannot be annotated." error_issue_not_found_in_project: 'The issue was not found or does not belong to this project' error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.' error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").' error_can_not_delete_custom_field: Unable to delete custom field - error_can_not_delete_tracker: "This tracker contains issues and can't be deleted." - error_can_not_remove_role: "This role is in use and can not be deleted." - error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version can not be reopened' - error_can_not_archive_project: This project can not be archived + error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted." + error_can_not_remove_role: "This role is in use and cannot be deleted." + error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened' + error_can_not_archive_project: This project cannot be archived error_issue_done_ratios_not_updated: "Issue done ratios not updated." error_workflow_copy_source: 'Please select a source tracker or role' error_workflow_copy_target: 'Please select target tracker(s) and role(s)' @@ -210,8 +210,8 @@ en: field_description: Description field_summary: Summary field_is_required: Required - field_firstname: Firstname - field_lastname: Lastname + field_firstname: First name + field_lastname: Last name field_mail: Email field_filename: File field_filesize: Size @@ -303,6 +303,7 @@ en: field_assigned_to_role: "Assignee's role" field_text: Text field field_visible: Visible + field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text" setting_app_title: Application title setting_app_subtitle: Application subtitle @@ -409,7 +410,7 @@ en: permission_browse_repository: Browse repository permission_view_changesets: View changesets permission_commit_access: Commit access - permission_manage_boards: Manage boards + permission_manage_boards: Manage forums permission_view_messages: View messages permission_add_messages: Post messages permission_edit_messages: Edit messages @@ -426,7 +427,7 @@ en: project_module_files: Files project_module_wiki: Wiki project_module_repository: Repository - project_module_boards: Boards + project_module_boards: Forums project_module_calendar: Calendar project_module_gantt: Gantt @@ -536,6 +537,7 @@ en: label_news_latest: Latest news label_news_view_all: View all news label_news_added: News added + label_news_comment_added: Comment added to a news label_settings: Settings label_overview: Overview label_version: Version @@ -594,6 +596,7 @@ en: label_query: Custom query label_query_plural: Custom queries label_query_new: New query + label_my_queries: My custom queries label_filter_add: Add filter label_filter_plural: Filters label_equals: is @@ -800,12 +803,16 @@ en: label_cvs_module: Module label_bazaar_path: Root directory label_filesystem_path: Root directory + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author button_login: Login button_submit: Submit button_save: Save button_check_all: Check all button_uncheck_all: Uncheck all + button_collapse_all: Collapse all + button_expand_all: Expand all button_delete: Delete button_create: Create button_create_and_continue: Create and continue @@ -859,19 +866,20 @@ en: text_select_mail_notifications: Select actions for which email notifications should be sent. text_regexp_info: eg. ^[A-Z0-9]+$ text_min_max_length_info: 0 means no restriction - text_project_destroy_confirmation: Are you sure you want to delete this project and related data ? + text_project_destroy_confirmation: Are you sure you want to delete this project and related data? text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted." text_workflow_edit: Select a role and a tracker to edit the workflow - text_are_you_sure: Are you sure ? + text_are_you_sure: Are you sure? text_are_you_sure_with_children: "Delete issue and all child issues?" text_journal_changed: "%{label} changed from %{old} to %{new}" + text_journal_changed_no_detail: "%{label} updated" text_journal_set_to: "%{label} set to %{value}" text_journal_deleted: "%{label} deleted (%{old})" text_journal_added: "%{label} %{value} added" text_tip_issue_begin_day: issue beginning this day text_tip_issue_end_day: issue ending this day text_tip_issue_begin_end_day: issue beginning and ending this day - text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier can not be changed.' + text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier cannot be changed.' text_caracters_maximum: "%{count} characters maximum." text_caracters_minimum: "Must be at least %{count} characters long." text_length_between: "Length between %{min} and %{max} characters." @@ -882,8 +890,8 @@ en: text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages text_issue_added: "Issue %{id} has been reported by %{author}." text_issue_updated: "Issue %{id} has been updated by %{author}." - text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ? - text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do ?" + text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content? + text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?" text_issue_category_destroy_assignments: Remove category assignments text_issue_category_reassign_to: Reassign issues to this category text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)." @@ -891,13 +899,13 @@ en: text_load_default_configuration: Load the default configuration text_status_changed_by_changeset: "Applied in changeset %{value}." text_time_logged_by_changeset: "Applied in changeset %{value}." - text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?' + text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?' text_select_project_modules: 'Select modules to enable for this project:' text_default_administrator_account_changed: Default administrator account changed text_file_repository_writable: Attachments directory writable text_plugin_assets_writable: Plugin assets directory writable text_rmagick_available: RMagick available (optional) - text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do ?" + text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?" text_destroy_time_entries: Delete reported hours text_assign_time_entries_to_project: Assign reported hours to the project text_reassign_time_entries: 'Reassign reported hours to this issue:' @@ -916,6 +924,7 @@ en: text_zoom_in: Zoom in text_zoom_out: Zoom out text_powered_by: "Powered by %{link}" + text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page." default_role_manager: Manager default_role_developer: Developer @@ -945,4 +954,3 @@ en: enumeration_doc_categories: Document categories enumeration_activities: Activities (time tracking) enumeration_system_activity: System Activity - diff --git a/config/locales/es.yml b/config/locales/es.yml index 3f153a6d..6ad49263 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -982,3 +982,13 @@ es: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 5e036801..3a5085c2 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -99,8 +99,8 @@ eu: errors: template: header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" + one: "Errore batek %{model} hau godetzea galarazi du." + other: "%{count} errorek %{model} hau gordetzea galarazi dute." messages: inclusion: "ez dago zerrendan" exclusion: "erreserbatuta dago" @@ -125,7 +125,7 @@ eu: greater_than_start_date: "hasiera data baino handiagoa izan behar du" not_same_project: "ez dago proiektu berdinean" circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke" - 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: "Zeregin bat ezin da bere azpiataza batekin estekatu." actionview_instancetag_blank_option: Hautatu mesedez @@ -276,7 +276,7 @@ eu: field_issue_to: Erlazionatutako zereginak field_delay: Atzerapena field_assignable: Arazoak rol honetara esleitu daitezke - field_redirect_existing_links: Existitzen diren estelak berbideratu + field_redirect_existing_links: Existitzen diren estekak berbideratu field_estimated_hours: Estimatutako denbora field_column_names: Zutabeak field_time_zone: Ordu zonaldea @@ -317,7 +317,7 @@ eu: setting_cross_project_issue_relations: Zereginak proiektuen artean erlazionatzea baimendu setting_issue_list_default_columns: Zereginen zerrendan defektuz ikusten diren zutabeak setting_repositories_encodings: Biltegien kodeketak - setting_commit_logs_encoding: Commit-en mezuen kodetzea + setting_commit_logs_encoding: Commit-en egunkarien kodetzea setting_emails_footer: Eposten oina setting_protocol: Protokoloa setting_per_page_options: Orriko objektuen aukerak @@ -362,7 +362,7 @@ eu: permission_delete_issues: Zereginak ezabatu permission_manage_public_queries: Galdera publikoak kudeatu permission_save_queries: Galderak gorde - permission_view_gantt: Gantt diagrama ikusi + permission_view_gantt: Gantt grafikoa ikusi permission_view_calendar: Egutegia ikusi permission_view_issue_watchers: Behatzaileen zerrenda ikusi permission_add_issue_watchers: Behatzaileak gehitu @@ -440,7 +440,7 @@ eu: label_tracker: Aztarnaria label_tracker_plural: Aztarnariak label_tracker_new: Aztarnari berria - label_workflow: Workflow + label_workflow: Lan-fluxua label_issue_status: Zeregin egoera label_issue_status_plural: Zeregin egoerak label_issue_status_new: Egoera berria @@ -505,7 +505,7 @@ eu: label_file_added: Fitxategia gehituta label_report: Berri ematea label_report_plural: Berri emateak - label_news: Beria + label_news: Berria label_news_new: Berria gehitu label_news_plural: Berriak label_news_latest: Azken berriak @@ -552,7 +552,7 @@ eu: label_add_note: Oharra gehitu label_per_page: Orriko label_calendar: Egutegia - label_months_from: months from + label_months_from: hilabete noiztik label_gantt: Gantt label_internal: Barnekoa label_last_changes: "azken %{count} aldaketak" @@ -783,7 +783,7 @@ eu: button_cancel: Ezeztatu button_activate: Gahitu button_sort: Ordenatu - button_log_time: Denbora apuntatu + button_log_time: Denbora erregistratu button_rollback: Itzuli bertsio honetara button_watch: Behatu button_unwatch: Behatzen utzi @@ -823,9 +823,9 @@ eu: text_journal_set_to: "%{label}-k %{value} balioa hartu du" text_journal_deleted: "%{label} ezabatuta (%{old})" text_journal_added: "%{label} %{value} gehituta" - text_tip_issue_begin_day: gaur hasten diren atazak - text_tip_issue_end_day: gaur bukatzen diren atazak - text_tip_issue_begin_end_day: gaur hasi eta bukatzen diren atazak + text_tip_issue_begin_day: gaur hasten diren zereginak + text_tip_issue_end_day: gaur bukatzen diren zereginak + text_tip_issue_begin_end_day: gaur hasi eta bukatzen diren zereginak text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.
Once saved, the identifier can not be changed.' text_caracters_maximum: "%{count} karaktere gehienez." text_caracters_minimum: "Gutxienez %{count} karaktereetako luzerakoa izan behar du." @@ -896,7 +896,7 @@ eu: enumeration_doc_categories: Dokumentu kategoriak enumeration_activities: Jarduerak (denbora kontrola)) enumeration_system_activity: Sistemako Jarduera - label_board_sticky: Itxaskorra + label_board_sticky: Itsaskorra label_board_locked: Blokeatuta permission_export_wiki_pages: Wiki orriak esportatu setting_cache_formatted_text: Formatudun testua katxeatu @@ -904,43 +904,22 @@ eu: error_unable_delete_issue_status: Ezine da zereginaren egoera ezabatu label_profile: Profila permission_manage_subtasks: Azpiatazak kudeatu - field_parent_issue: Guraso ataza + field_parent_issue: Zeregin gurasoa label_subtask_plural: Azpiatazak label_project_copy_notifications: Proiektua kopiatzen den bitartean eposta jakinarazpenak bidali - error_can_not_delete_custom_field: Ezin da eremu pertsonallizatua ezabatu + error_can_not_delete_custom_field: Ezin da eremu pertsonalizatua ezabatu error_unable_to_connect: Ezin da konektatu (%{value}) error_can_not_remove_role: Rol hau erabiltzen hari da eta ezin da ezabatu. error_can_not_delete_tracker: Aztarnari honek zereginak ditu eta ezin da ezabatu. - field_principal: Principal - label_my_page_block: My page block - notice_failed_to_save_members: "Failed to save member(s): %{errors}." - text_zoom_out: Zoom out - text_zoom_in: Zoom in - notice_unable_delete_time_entry: Unable to delete time log entry. - label_overall_spent_time: Overall spent time - field_time_entries: Log time + field_principal: Ekintzaile + label_my_page_block: "Nire orriko blokea" + notice_failed_to_save_members: "Kidea(k) gordetzean errorea: %{errors}." + text_zoom_out: Zooma txikiagotu + text_zoom_in: Zooma handiagotu + notice_unable_delete_time_entry: "Ezin da hautatutako denbora erregistroa ezabatu." + label_overall_spent_time: Igarotako denbora guztira + field_time_entries: "Denbora erregistratu" project_module_gantt: Gantt - project_module_calendar: Calendar - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart text_powered_by: Powered by %{link} label_cvs_module: Module label_filesystem_path: Root directory @@ -949,3 +928,34 @@ eu: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + project_module_calendar: Egutegia + button_edit_associated_wikipage: "Esleitutako wiki orria editatu: %{page_title}" + text_are_you_sure_with_children: "Zeregina eta azpi zeregin guztiak ezabatu?" + field_text: Testu eremua + label_user_mail_option_only_owner: "Jabea naizen gauzetarako barrarik" + setting_default_notification_option: "Lehenetsitako ohartarazpen aukera" + label_user_mail_option_only_my_events: "Behatzen ditudan edo partaide naizen gauzetarako bakarrik" + label_user_mail_option_only_assigned: "Niri esleitutako gauzentzat bakarrik" + label_user_mail_option_none: "Gertakaririk ez" + field_member_of_group: "Esleituta duenaren taldea" + field_assigned_to_role: "Esleituta duenaren rola" + notice_not_authorized_archived_project: "Atzitu nahi duzun proiektua artxibatua izan da." + label_principal_search: "Bilatu erabiltzaile edo taldea:" + label_user_search: "Erabiltzailea bilatu:" + field_visible: Ikusgai + setting_emails_header: "Eposten goiburua" + setting_commit_logtime_activity_id: "Erregistratutako denboraren jarduera" + text_time_logged_by_changeset: "%{value} aldaketan egindakoa." + setting_commit_logtime_enabled: "Erregistrutako denbora gaitu" + notice_gantt_chart_truncated: Grafikoa moztu da bistara daitekeen elementuen kopuru maximoa gainditu delako (%{max}) + setting_gantt_items_limit: "Gantt grafikoan bistara daitekeen elementu kopuru maximoa" + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/fa.yml b/config/locales/fa.yml new file mode 100644 index 00000000..19bcdb78 --- /dev/null +++ b/config/locales/fa.yml @@ -0,0 +1,960 @@ +fa: + # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl) + direction: rtl + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y/%m/%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [یک‌شنبه, دوشنبه, سه‌شنبه, چهارشنبه, پنج‌شنبه, آدینه, شنبه] + abbr_day_names: [یک, دو, سه, چهار, پنج, آدینه, شنبه] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, ژانویه, فوریه, مارس, آوریل, مه, ژوئن, ژوئیه, اوت, سپتامبر, اکتبر, نوامبر, دسامبر] + abbr_month_names: [~, ژان, فور, مار, آور, مه, ژوئن, ژوئیه, اوت, سپت, اکت, نوا, دسا] + # Used in date_select and datime_select. + order: [ :year, :month, :day ] + + time: + formats: + default: "%Y/%m/%d %p %I:%M" + time: "%p %I:%M" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "ق.ظ" + pm: "ب.ظ" + + datetime: + distance_in_words: + half_a_minute: "نیم دقیقه" + less_than_x_seconds: + one: "کمتر از 1 ثانیه" + other: "کمتر از %{count} ثانیه" + x_seconds: + one: "1 ثانیه" + other: "%{count} ثانیه" + less_than_x_minutes: + one: "کمتر از 1 دقیقه" + other: "کمتر از %{count} دقیقه" + x_minutes: + one: "1 دقیقه" + other: "%{count} دقیقه" + about_x_hours: + one: "نزدیک 1 ساعت" + other: "نزدیک %{count} ساعت" + x_days: + one: "1 روز" + other: "%{count} روز" + about_x_months: + one: "نزدیک 1 ماه" + other: "نزدیک %{count} ماه" + x_months: + one: "1 ماه" + other: "%{count} ماه" + about_x_years: + one: "نزدیک 1 سال" + other: "نزدیک %{count} سال" + over_x_years: + one: "بیش از 1 سال" + other: "بیش از %{count} سال" + almost_x_years: + one: "نزدیک 1 سال" + other: "نزدیک %{count} سال" + + number: + # Default format for numbers + format: + separator: "٫" + delimiter: "" + precision: 3 + human: + format: + delimiter: "" + precision: 1 + storage_units: + format: "%n %u" + units: + byte: + one: "بایت" + other: "بایت" + kb: "کیلوبایت" + mb: "مگابایت" + gb: "گیگابایت" + tb: "ترابایت" + + +# Used in array.to_sentence. + support: + array: + sentence_connector: "و" + skip_last_comma: false + + activerecord: + errors: + template: + header: + one: "1 ایراد از ذخیره سازی این %{model} جلوگیری کرد" + other: "%{count} ایراد از ذخیره سازی این %{model} جلوگیری کرد" + messages: + inclusion: "در فهرست نیامده است" + exclusion: "رزرو شده است" + invalid: "نادرست است" + confirmation: "با بررسی سازگاری ندارد" + accepted: "باید پذیرفته شود" + empty: "نمی‌تواند تهی باشد" + blank: "نمی‌تواند تهی باشد" + too_long: "خیلی بلند است (بیشترین اندازه %{count} نویسه است)" + too_short: "خیلی کوتاه است (کمترین اندازه %{count} نویسه است)" + wrong_length: "اندازه نادرست است (باید %{count} نویسه باشد)" + taken: "پیش از این گرفته شده است" + not_a_number: "شماره درستی نیست" + not_a_date: "تاریخ درستی نیست" + greater_than: "باید بزرگتر از %{count} باشد" + greater_than_or_equal_to: "باید بزرگتر از یا برابر با %{count} باشد" + equal_to: "باید برابر با %{count} باشد" + less_than: "باید کمتر از %{count} باشد" + less_than_or_equal_to: "باید کمتر از یا برابر با %{count} باشد" + odd: "باید فرد باشد" + even: "باید زوج باشد" + greater_than_start_date: "باید از تاریخ آغاز بزرگتر باشد" + not_same_project: "به همان پروژه وابسته نیست" + circular_dependency: "این وابستگی یک وابستگی دایره وار خواهد ساخت" + cant_link_an_issue_with_a_descendant: "یک پیامد نمی‌تواند به یکی از زیر کارهایش پیوند بخورد" + + actionview_instancetag_blank_option: گزینش کنید + + general_text_No: 'خیر' + general_text_Yes: 'آری' + general_text_no: 'خیر' + general_text_yes: 'آری' + general_lang_name: 'Persian (پارسی)' + general_csv_separator: ',' + general_csv_decimal_separator: '.' + general_csv_encoding: UTF-8 + general_pdf_encoding: UTF-8 + general_first_day_of_week: '6' + + notice_account_updated: حساب شما بروز شد. + notice_account_invalid_creditentials: نام کاربری یا گذرواژه نادرست است + notice_account_password_updated: گذرواژه بروز شد + notice_account_wrong_password: گذرواژه نادرست است + notice_account_register_done: حساب ساخته شد. برای فعال نمودن آن، روی پیوندی که به شما ایمیل شده کلیک کنید. + notice_account_unknown_email: کاربر شناخته نشد. + notice_can_t_change_password: این حساب یک روش شناسایی بیرونی را به کار گرفته است. گذرواژه را نمی‌توان جایگزین کرد. + notice_account_lost_email_sent: یک ایمیل با راهنمایی درباره گزینش گذرواژه تازه برای شما فرستاده شد. + notice_account_activated: حساب شما فعال شده است. اکنون می‌توانید وارد شوید. + notice_successful_create: با موفقیت ساخته شد. + notice_successful_update: با موفقیت بروز شد. + notice_successful_delete: با موفقیت برداشته شد. + notice_successful_connection: با موفقیت متصل شد. + notice_file_not_found: برگه درخواستی شما در دسترس نیست یا پاک شده است. + notice_locking_conflict: داده‌ها را کاربر دیگری بروز کرده است. + notice_not_authorized: شما به این برگه دسترسی ندارید. + notice_not_authorized_archived_project: پروژه درخواستی شما بایگانی شده است. + notice_email_sent: "یک ایمیل به %{value} فرستاده شد." + notice_email_error: "یک ایراد در فرستادن ایمیل پیش آمد (%{value})." + notice_feeds_access_key_reseted: کلید دسترسی RSS شما بازنشانی شد. + notice_api_access_key_reseted: کلید دسترسی API شما بازنشانی شد. + notice_failed_to_save_issues: "ذخیره سازی %{count} پیامد از %{total} پیامد گزینش شده شکست خورد: %{ids}." + notice_failed_to_save_members: "ذخیره سازی اعضا شکست خورد: %{errors}." + notice_no_issue_selected: "هیچ پیامدی برگزیده نشده است! پیامدهایی که می‌خواهید ویرایش کنید را برگزینید." + notice_account_pending: "حساب شما ساخته شد و اکنون چشم به راه روادید سرپرست است." + notice_default_data_loaded: پیکربندی پیش‌گزیده با موفقیت بار شد. + notice_unable_delete_version: نگارش را نمی‌توان پاک کرد. + notice_unable_delete_time_entry: زمان گزارش شده را نمی‌توان پاک کرد. + notice_issue_done_ratios_updated: اندازه انجام شده پیامد بروز شد. + notice_gantt_chart_truncated: "نمودار بریده شد چون از بیشترین شماری که می‌توان نشان داد بزگتر است (%{max})." + + error_can_t_load_default_data: "پیکربندی پیش‌گزیده نمی‌تواند بار شود: %{value}" + error_scm_not_found: "بخش یا نگارش در انباره پیدا نشد." + error_scm_command_failed: "ایرادی در دسترسی به انباره پیش آمد: %{value}" + error_scm_annotate: "بخش پیدا نشد یا نمی‌توان برای آن یادداشت نوشت." + error_issue_not_found_in_project: 'پیامد پیدا نشد یا به این پروژه وابسته نیست.' + error_no_tracker_in_project: 'هیچ پیگردی به این پروژه پیوسته نشده است. پیکربندی پروژه را بررسی کنید.' + error_no_default_issue_status: 'هیچ وضعیت پیامد پیش‌گزیده‌ای مشخص نشده است. پیکربندی را بررسی کنید (به «پیکربندی -> وضعیت‌های پیامد» بروید).' + error_can_not_delete_custom_field: فیلد سفارشی را نمی‌توان پاک کرد. + error_can_not_delete_tracker: "این پیگرد دارای پیامد است و نمی‌توان آن را پاک کرد." + error_can_not_remove_role: "این نقش به کار گرفته شده است و نمی‌توان آن را پاک کرد." + error_can_not_reopen_issue_on_closed_version: 'یک پیامد که به یک نگارش بسته شده وابسته است را نمی‌توان باز کرد.' + error_can_not_archive_project: این پروژه را نمی‌توان بایگانی کرد. + error_issue_done_ratios_not_updated: "اندازه انجام شده پیامد بروز نشد." + error_workflow_copy_source: 'یک پیگرد یا نقش منبع را برگزینید.' + error_workflow_copy_target: 'پیگردها یا نقش‌های مقصد را برگزینید.' + error_unable_delete_issue_status: 'وضعیت پیامد را نمی‌توان پاک کرد.' + error_unable_to_connect: "نمی‌توان متصل شد (%{value})" + warning_attachments_not_saved: "%{count} پرونده ذخیره نشد." + + mail_subject_lost_password: "گذرواژه حساب %{value} شما" + mail_body_lost_password: 'برای جایگزینی گذرواژه خود، بر روی پیوند زیر کلیک کنید:' + mail_subject_register: "فعالسازی حساب %{value} شما" + mail_body_register: 'برای فعالسازی حساب خود، بر روی پیوند زیر کلیک کنید:' + mail_body_account_information_external: "شما می‌توانید حساب %{value} خود را برای ورود به کار برید." + mail_body_account_information: داده‌های حساب شما + mail_subject_account_activation_request: "درخواست فعالسازی حساب %{value}" + mail_body_account_activation_request: "یک کاربر تازه (%{value}) نامنویسی کرده است. این حساب چشم به راه روادید شماست:" + mail_subject_reminder: "زمان رسیدگی به %{count} پیامد در %{days} روز آینده سر می‌رسد" + mail_body_reminder: "زمان رسیدگی به %{count} پیامد که به شما واگذار شده است، در %{days} روز آینده سر می‌رسد:" + mail_subject_wiki_content_added: "برگه ویکی «%{id}» افزوده شد" + mail_body_wiki_content_added: "برگه ویکی «%{id}» به دست %{author} افزوده شد." + mail_subject_wiki_content_updated: "برگه ویکی «%{id}» بروز شد" + mail_body_wiki_content_updated: "برگه ویکی «%{id}» به دست %{author} بروز شد." + + gui_validation_error: 1 ایراد + gui_validation_error_plural: "%{count} ایراد" + + field_name: نام + field_description: توضیح + field_summary: خلاصه + field_is_required: الزامی + field_firstname: نام کوچک + field_lastname: نام خانوادگی + field_mail: ایمیل + field_filename: پرونده + field_filesize: اندازه + field_downloads: دریافت‌ها + field_author: نویسنده + field_created_on: ساخته شده در + field_updated_on: بروز شده در + field_field_format: قالب + field_is_for_all: برای همه پروژه‌ها + field_possible_values: مقادیر ممکن + field_regexp: عبارت منظم + field_min_length: کمترین اندازه + field_max_length: بیشترین اندازه + field_value: مقدار + field_category: دسته + field_title: عنوان + field_project: پروژه + field_issue: پیامد + field_status: وضعیت + field_notes: یادداشت‌ها + field_is_closed: پیامد بسته شده + field_is_default: مقدار پیش‌گزیده + field_tracker: پیگرد + field_subject: موضوع + field_due_date: زمان سررسید + field_assigned_to: واگذار شده به + field_priority: برتری + field_fixed_version: نگارش هدف + field_user: کاربر + field_principal: دستور دهنده + field_role: نقش + field_homepage: برگه خانه + field_is_public: همگانی + field_parent: پروژه پدر + field_is_in_roadmap: این پیامدها در نقشه راه نشان داده شوند + field_login: ورود + field_mail_notification: آگاه سازی‌های ایمیلی + field_admin: سرپرست + field_last_login_on: آخرین ورود + field_language: زبان + field_effective_date: تاریخ + field_password: گذرواژه + field_new_password: گذرواژه تازه + field_password_confirmation: بررسی گذرواژه + field_version: نگارش + field_type: گونه + field_host: میزبان + field_port: درگاه + field_account: حساب + field_base_dn: DN پایه + field_attr_login: نشانه ورود + field_attr_firstname: نشانه نام کوچک + field_attr_lastname: نشانه نام خانوادگی + field_attr_mail: نشانه ایمیل + field_onthefly: ساخت کاربر بیدرنگ + field_start_date: تاریخ آغاز + field_done_ratio: ٪ انجام شده + field_auth_source: روش شناسایی + field_hide_mail: ایمیل من پنهان شود + field_comments: دیدگاه + field_url: نشانی + field_start_page: برگه آغاز + field_subproject: زیر پروژه + field_hours: ساعت‌ + field_activity: فعالیت + field_spent_on: در تاریخ + field_identifier: شناسه + field_is_filter: پالایش پذیر + field_issue_to: پیامد وابسته + field_delay: دیرکرد + field_assignable: پیامدها می‌توانند به این نقش واگذار شوند + field_redirect_existing_links: پیوندهای پیشین به پیوند تازه راهنمایی شوند + field_estimated_hours: زمان برآورد شده + field_column_names: ستون‌ها + field_time_entries: زمان نوشتن + field_time_zone: پهنه زمانی + field_searchable: جستجو پذیر + field_default_value: مقدار پیش‌گزیده + field_comments_sorting: نمایش دیدگاه‌ها + field_parent_title: برگه پدر + field_editable: ویرایش پذیر + field_watcher: دیده‌بان + field_identity_url: نشانی OpenID + field_content: محتوا + field_group_by: دسته بندی با + field_sharing: اشتراک گذاری + field_parent_issue: کار پدر + field_member_of_group: "دسته واگذار شونده" + field_assigned_to_role: "نقش واگذار شونده" + field_text: فیلد متنی + field_visible: آشکار + + setting_app_title: نام برنامه + setting_app_subtitle: زیرنام برنامه + setting_welcome_text: نوشتار خوش‌آمد گویی + setting_default_language: زبان پیش‌گزیده + setting_login_required: الزامی بودن ورود + setting_self_registration: خود نام نویسی + setting_attachment_max_size: بیشترین اندازه پیوست + setting_issues_export_limit: کرانه صدور پییامدها + setting_mail_from: نشانی فرستنده ایمیل + setting_bcc_recipients: گیرندگان ایمیل دیده نشوند (bcc) + setting_plain_text_mail: ایمیل نوشته ساده (بدون HTML) + setting_host_name: نام میزبان و نشانی + setting_text_formatting: قالب بندی نوشته + setting_wiki_compression: فشرده‌سازی پیشینه ویکی + setting_feeds_limit: کرانه محتوای خوراک + setting_default_projects_public: حالت پیش‌گزیده پروژه‌های تازه، همگانی است + setting_autofetch_changesets: دریافت خودکار تغییرات + setting_sys_api_enabled: فعال سازی وب سرویس برای سرپرستی انباره + setting_commit_ref_keywords: کلیدواژه‌های نشانه + setting_commit_fix_keywords: کلیدواژه‌های انجام + setting_autologin: ورود خودکار + setting_date_format: قالب تاریخ + setting_time_format: قالب زمان + setting_cross_project_issue_relations: توانایی وابستگی میان پروژه‌ای پیامدها + setting_issue_list_default_columns: ستون‌های پیش‌گزیده نمایش داده شده در فهرست پیامدها + setting_repositories_encodings: کدگذاری انباره‌ها + setting_commit_logs_encoding: کدگذاری پیام‌های انباره + setting_emails_header: سرنویس ایمیل‌ها + setting_emails_footer: پانویس ایمیل‌ها + setting_protocol: پیوندنامه + setting_per_page_options: گزینه‌های اندازه داده‌های هر برگ + setting_user_format: قالب نمایشی کاربران + setting_activity_days_default: روزهای نمایش داده شده در فعالیت پروژه + setting_display_subprojects_issues: پیش‌گزیده نمایش پیامدهای زیرپروژه در پروژه پدر + setting_enabled_scm: فعالسازی SCM + setting_mail_handler_body_delimiters: "بریدن ایمیل‌ها پس از یکی از این ردیف‌ها" + setting_mail_handler_api_enabled: فعالسازی وب سرویس برای ایمیل‌های آمده + setting_mail_handler_api_key: کلید API + setting_sequential_project_identifiers: ساخت پشت سر هم شناسه پروژه + setting_gravatar_enabled: کاربرد Gravatar برای عکس کاربر + setting_gravatar_default: عکس Gravatar پیش‌گزیده + setting_diff_max_lines_displayed: بیشترین اندازه ردیف‌های تفاوت نشان داده شده + setting_file_max_size_displayed: بیشترین اندازه پرونده‌های نمایش داده شده درون خطی + setting_repository_log_display_limit: بیشترین شمار نگارش‌های نمایش داده شده در گزارش پرونده + setting_openid: پذیرش ورود و نام نویسی با OpenID + setting_password_min_length: کمترین اندازه گذرواژه + setting_new_project_user_role_id: نقش داده شده به کاربری که سرپرست نیست و پروژه می‌سازد + setting_default_projects_modules: پیمانه‌های پیش‌گزیده فعال برای پروژه‌های تازه + setting_issue_done_ratio: برآورد اندازه انجام شده پیامد با + setting_issue_done_ratio_issue_field: کاربرد فیلد پیامد + setting_issue_done_ratio_issue_status: کاربرد وضعیت پیامد + setting_start_of_week: آغاز گاهشمار از + setting_rest_api_enabled: فعالسازی وب سرویس‌های REST + setting_cache_formatted_text: نهان سازی نوشته‌های قالب بندی شده + setting_default_notification_option: آگاه سازی پیش‌گزیده + setting_commit_logtime_enabled: فعالسازی زمان گذاشته شده + setting_commit_logtime_activity_id: فعالیت زمان گذاشته شده + setting_gantt_items_limit: بیشترین شمار بخش‌های نمایش داده شده در نمودار گانت + + permission_add_project: ساخت پروژه + permission_add_subprojects: ساخت زیرپروژه + permission_edit_project: ویرایش پروژه + permission_select_project_modules: گزینش پیمانه‌های پروژه + permission_manage_members: سرپرستی اعضا + permission_manage_project_activities: سرپرستی فعالیت‌های پروژه + permission_manage_versions: سرپرستی نگارش‌ها + permission_manage_categories: سرپرستی دسته‌های پیامد + permission_view_issues: دیدن پیامدها + permission_add_issues: افزودن پیامدها + permission_edit_issues: ویرایش پیامدها + permission_manage_issue_relations: سرپرستی وابستگی پیامدها + permission_add_issue_notes: افزودن یادداشت‌ها + permission_edit_issue_notes: ویرایش یادداشت‌ها + permission_edit_own_issue_notes: ویرایش یادداشت خود + permission_move_issues: جابجایی پیامدها + permission_delete_issues: پاک کردن پیامدها + permission_manage_public_queries: سرپرستی پرس‌وجوهای همگانی + permission_save_queries: ذخیره سازی پرس‌وجوها + permission_view_gantt: دیدن نمودار گانت + permission_view_calendar: دیدن گاهشمار + permission_view_issue_watchers: دیدن فهرست دیده‌بان‌ها + permission_add_issue_watchers: افزودن دیده‌بان‌ها + permission_delete_issue_watchers: پاک کردن دیده‌بان‌ها + permission_log_time: نوشتن زمان گذاشته شده + permission_view_time_entries: دیدن زمان گذاشته شده + permission_edit_time_entries: ویرایش زمان گذاشته شده + permission_edit_own_time_entries: ویرایش زمان گذاشته شده خود + permission_manage_news: سرپرستی رویدادها + permission_comment_news: گذاشتن دیدگاه روی رویدادها + permission_manage_documents: سرپرستی نوشتارها + permission_view_documents: دیدن نوشتارها + permission_manage_files: سرپرستی پرونده‌ها + permission_view_files: دیدن پرونده‌ها + permission_manage_wiki: سرپرستی ویکی + permission_rename_wiki_pages: نامگذاری برگه ویکی + permission_delete_wiki_pages: پاک کردن برگه ویکی + permission_view_wiki_pages: دیدن ویکی + permission_view_wiki_edits: دیدن پیشینه ویکی + permission_edit_wiki_pages: ویرایش برگه‌های ویکی + permission_delete_wiki_pages_attachments: پاک کردن پیوست‌های برگه ویکی + permission_protect_wiki_pages: نگه‌داری برگه‌های ویکی + permission_manage_repository: سرپرستی انباره + permission_browse_repository: چریدن در انباره + permission_view_changesets: دیدن تغییرات + permission_commit_access: دسترسی تغییر انباره + permission_manage_boards: سرپرستی انجمن‌ها + permission_view_messages: دیدن پیام‌ها + permission_add_messages: فرستادن پیام‌ها + permission_edit_messages: ویرایش پیام‌ها + permission_edit_own_messages: ویرایش پیام خود + permission_delete_messages: پاک کردن پیام‌ها + permission_delete_own_messages: پاک کردن پیام خود + permission_export_wiki_pages: صدور برگه‌های ویکی + permission_manage_subtasks: سرپرستی زیرکارها + + project_module_issue_tracking: پیگیری پیامدها + project_module_time_tracking: پیگیری زمان + project_module_news: رویدادها + project_module_documents: نوشتارها + project_module_files: پرونده‌ها + project_module_wiki: ویکی + project_module_repository: انباره + project_module_boards: انجمن‌ها + project_module_calendar: گاهشمار + project_module_gantt: گانت + + label_user: کاربر + label_user_plural: کاربر + label_user_new: کاربر تازه + label_user_anonymous: ناشناس + label_project: پروژه + label_project_new: پروژه تازه + label_project_plural: پروژه + label_x_projects: + zero: بدون پروژه + one: "1 پروژه" + other: "%{count} پروژه" + label_project_all: همه پروژه‌ها + label_project_latest: آخرین پروژه‌ها + label_issue: پیامد + label_issue_new: پیامد تازه + label_issue_plural: پیامد + label_issue_view_all: دیدن همه پیامدها + label_issues_by: "پیامدهای دست%{value}" + label_issue_added: پیامد افزوده شد + label_issue_updated: پیامد بروز شد + label_document: نوشتار + label_document_new: نوشتار تازه + label_document_plural: نوشتار + label_document_added: نوشتار افزوده شد + label_role: نقش + label_role_plural: نقش + label_role_new: نقش تازه + label_role_and_permissions: نقش‌ها و پروانه‌ها + label_member: عضو + label_member_new: عضو تازه + label_member_plural: عضو + label_tracker: پیگرد + label_tracker_plural: پیگرد + label_tracker_new: پیگرد تازه + label_workflow: گردش کار + label_issue_status: وضعیت پیامد + label_issue_status_plural: وضعیت پیامد + label_issue_status_new: وضعیت تازه + label_issue_category: دسته پیامد + label_issue_category_plural: دسته پیامد + label_issue_category_new: دسته تازه + label_custom_field: فیلد سفارشی + label_custom_field_plural: فیلد سفارشی + label_custom_field_new: فیلد سفارشی تازه + label_enumerations: برشمردنی‌ها + label_enumeration_new: مقدار تازه + label_information: داده + label_information_plural: داده + label_please_login: وارد شوید + label_register: نام نویسی کنید + label_login_with_open_id_option: یا با OpenID وارد شوید + label_password_lost: بازیافت گذرواژه + label_home: سرآغاز + label_my_page: برگه من + label_my_account: حساب من + label_my_projects: پروژه‌های من + label_my_page_block: بخش برگه من + label_administration: سرپرستی + label_login: ورود + label_logout: خروج + label_help: راهنما + label_reported_issues: پیامدهای گزارش شده + label_assigned_to_me_issues: پیامدهای واگذار شده به من + label_last_login: آخرین ورود + label_registered_on: نام نویسی شده در + label_activity: فعالیت + label_overall_activity: فعالیت روی هم رفته + label_user_activity: "فعالیت %{value}" + label_new: تازه + label_logged_as: "نام کاربری:" + label_environment: محیط + label_authentication: شناسایی + label_auth_source: روش شناسایی + label_auth_source_new: روش شناسایی تازه + label_auth_source_plural: روش شناسایی + label_subproject_plural: زیرپروژه + label_subproject_new: زیرپروژه تازه + label_and_its_subprojects: "%{value} و زیرپروژه‌هایش" + label_min_max_length: کمترین و بیشترین اندازه + label_list: فهرست + label_date: تاریخ + label_integer: شماره درست + label_float: شماره شناور + label_boolean: درست/نادرست + label_string: نوشته + label_text: نوشته بلند + label_attribute: نشانه + label_attribute_plural: نشانه + label_download: "%{count} بار دریافت شده" + label_download_plural: "%{count} بار دریافت شده" + label_no_data: هیچ داده‌ای برای نمایش نیست + label_change_status: جایگزینی وضعیت + label_history: پیشینه + label_attachment: پرونده + label_attachment_new: پرونده تازه + label_attachment_delete: پاک کردن پرونده + label_attachment_plural: پرونده + label_file_added: پرونده افزوده شد + label_report: گزارش + label_report_plural: گزارش + label_news: رویداد + label_news_new: افزودن رویداد + label_news_plural: رویداد + label_news_latest: آخرین رویدادها + label_news_view_all: دیدن همه رویدادها + label_news_added: رویداد افزوده شد + label_settings: پیکربندی + label_overview: دورنما + label_version: نگارش + label_version_new: نگارش تازه + label_version_plural: نگارش + label_close_versions: بستن نگارش‌های انجام شده + label_confirmation: بررسی + label_export_to: 'قالب‌های دیگر:' + label_read: خواندن... + label_public_projects: پروژه‌های همگانی + label_open_issues: باز + label_open_issues_plural: باز + label_closed_issues: بسته + label_closed_issues_plural: بسته + label_x_open_issues_abbr_on_total: + zero: 0 باز از %{total} + one: 1 باز از %{total} + other: "%{count} باز از %{total}" + label_x_open_issues_abbr: + zero: 0 باز + one: 1 باز + other: "%{count} باز" + label_x_closed_issues_abbr: + zero: 0 بسته + one: 1 بسته + other: "%{count} بسته" + label_total: جمله + label_permissions: پروانه‌ها + label_current_status: وضعیت کنونی + label_new_statuses_allowed: وضعیت‌های پذیرفتنی تازه + label_all: همه + label_none: هیچ + label_nobody: هیچکس + label_next: پسین + label_previous: پیشین + label_used_by: به کار رفته در + label_details: ریزه‌کاری + label_add_note: افزودن یادداشت + label_per_page: ردیف‌ها در هر برگه + label_calendar: گاهشمار + label_months_from: از ماه + label_gantt: گانت + label_internal: درونی + label_last_changes: "%{count} تغییر آخر" + label_change_view_all: دیدن همه تغییرات + label_personalize_page: سفارشی نمودن این برگه + label_comment: دیدگاه + label_comment_plural: دیدگاه + label_x_comments: + zero: بدون دیدگاه + one: 1 دیدگاه + other: "%{count} دیدگاه" + label_comment_add: افزودن دیدگاه + label_comment_added: دیدگاه افزوده شد + label_comment_delete: پاک کردن دیدگاه‌ها + label_query: پرس‌وجوی سفارشی + label_query_plural: پرس‌وجوی سفارشی + label_query_new: پرس‌وجوی تازه + label_filter_add: افزودن پالایه + label_filter_plural: پالایه + label_equals: برابر است با + label_not_equals: برابر نیست با + label_in_less_than: کمتر است از + label_in_more_than: بیشتر است از + label_greater_or_equal: بیشتر یا برابر است با + label_less_or_equal: کمتر یا برابر است با + label_in: در + label_today: امروز + label_all_time: همیشه + label_yesterday: دیروز + label_this_week: این هفته + label_last_week: هفته پیشین + label_last_n_days: "%{count} روز گذشته" + label_this_month: این ماه + label_last_month: ماه پیشین + label_this_year: امسال + label_date_range: بازه تاریخ + label_less_than_ago: کمتر از چند روز پیشین + label_more_than_ago: بیشتر از چند روز پیشین + label_ago: روز پیشین + label_contains: دارد + label_not_contains: ندارد + label_day_plural: روز + label_repository: انباره + label_repository_plural: انباره + label_browse: چریدن + label_modification: "%{count} جایگذاری" + label_modification_plural: "%{count} جایگذاری" + label_branch: شاخه + label_tag: برچسب + label_revision: بازبینی + label_revision_plural: بازبینی + label_revision_id: "بازبینی %{value}" + label_associated_revisions: بازبینی‌های وابسته + label_added: افزوده شده + label_modified: پیراسته شده + label_copied: رونویسی شده + label_renamed: نامگذاری شده + label_deleted: پاکسازی شده + label_latest_revision: آخرین بازبینی + label_latest_revision_plural: آخرین بازبینی + label_view_revisions: دیدن بازبینی‌ها + label_view_all_revisions: دیدن همه بازبینی‌ها + label_max_size: بیشترین اندازه + label_sort_highest: بردن به آغاز + label_sort_higher: بردن به بالا + label_sort_lower: بردن به پایین + label_sort_lowest: بردن به پایان + label_roadmap: نقشه راه + label_roadmap_due_in: "سررسید در %{value}" + label_roadmap_overdue: "%{value} دیرکرد" + label_roadmap_no_issues: هیچ پیامدی برای این نگارش نیست + label_search: جستجو + label_result_plural: دست‌آورد + label_all_words: همه واژه‌ها + label_wiki: ویکی + label_wiki_edit: ویرایش ویکی + label_wiki_edit_plural: ویرایش ویکی + label_wiki_page: برگه ویکی + label_wiki_page_plural: برگه ویکی + label_index_by_title: شاخص بر اساس نام + label_index_by_date: شاخص بر اساس تاریخ + label_current_version: نگارش کنونی + label_preview: پیش‌نمایش + label_feed_plural: خوراک + label_changes_details: ریز همه جایگذاری‌ها + label_issue_tracking: پیگیری پیامد + label_spent_time: زمان گذاشته شده + label_overall_spent_time: زمان گذاشته شده روی هم + label_f_hour: "%{value} ساعت" + label_f_hour_plural: "%{value} ساعت" + label_time_tracking: پیگیری زمان + label_change_plural: جایگذاری + label_statistics: سرشماری + label_commits_per_month: تغییر در هر ماه + label_commits_per_author: تغییر هر نویسنده + label_view_diff: دیدن تفاوت‌ها + label_diff_inline: همراستا + label_diff_side_by_side: کنار به کنار + label_options: گزینه‌ها + label_copy_workflow_from: رونویسی گردش کار از روی + label_permissions_report: گزارش پروانه‌ها + label_watched_issues: پیامدهای دیده‌بانی شده + label_related_issues: پیامدهای وابسته + label_applied_status: وضعیت به کار رفته + label_loading: بار گذاری... + label_relation_new: وابستگی تازه + label_relation_delete: پاک کردن وابستگی + label_relates_to: وابسته به + label_duplicates: نگارش دیگری از + label_duplicated_by: نگارشی دیگر در + label_blocks: بازداشت‌ها + label_blocked_by: بازداشت به دست + label_precedes: جلوتر است از + label_follows: پستر است از + label_end_to_start: پایان به آغاز + label_end_to_end: پایان به پایان + label_start_to_start: آغاز به آغاز + label_start_to_end: آغاز به پایان + label_stay_logged_in: وارد شده بمانید + label_disabled: غیرفعال + label_show_completed_versions: نمایش نگارش‌های انجام شده + label_me: من + label_board: انجمن + label_board_new: انجمن تازه + label_board_plural: انجمن + label_board_locked: قفل شده + label_board_sticky: چسبناک + label_topic_plural: سرفصل + label_message_plural: پیام + label_message_last: آخرین پیام + label_message_new: پیام تازه + label_message_posted: پیام افزوده شد + label_reply_plural: پاسخ + label_send_information: فرستادن داده‌های حساب به کاربر + label_year: سال + label_month: ماه + label_week: هفته + label_date_from: از + label_date_to: تا + label_language_based: بر اساس زبان کاربر + label_sort_by: "جور کرد با %{value}" + label_send_test_email: فرستادن ایمیل آزمایشی + label_feeds_access_key: کلید دسترسی RSS + label_missing_feeds_access_key: کلید دسترسی RSS در دسترس نیست + label_feeds_access_key_created_on: "کلید دسترسی RSS %{value} پیش ساخته شده است" + label_module_plural: پیمانه + label_added_time_by: "افزوده شده به دست %{author} در %{age} پیش" + label_updated_time_by: "بروز شده به دست %{author} در %{age} پیش" + label_updated_time: "بروز شده در %{value} پیش" + label_jump_to_a_project: پرش به یک پروژه... + label_file_plural: پرونده + label_changeset_plural: تغییر + label_default_columns: ستون‌های پیش‌گزیده + label_no_change_option: (بدون تغییر) + label_bulk_edit_selected_issues: ویرایش دسته‌ای پیامدهای گزینش شده + label_theme: پوسته + label_default: پیش‌گزیده + label_search_titles_only: تنها نام‌ها جستجو شود + label_user_mail_option_all: "برای هر رویداد در همه پروژه‌ها" + label_user_mail_option_selected: "برای هر رویداد تنها در پروژه‌های گزینش شده..." + label_user_mail_option_none: "هیچ رویدادی" + label_user_mail_option_only_my_events: "تنها برای چیزهایی که دیده‌بان هستم یا در آن‌ها درگیر هستم" + label_user_mail_option_only_assigned: "تنها برای چیزهایی که به من واگذار شده" + label_user_mail_option_only_owner: "تنها برای چیزهایی که من دارنده آن‌ها هستم" + label_user_mail_no_self_notified: "نمی‌خواهم از تغییراتی که خودم می‌دهم آگاه شوم" + label_registration_activation_by_email: فعالسازی حساب با ایمیل + label_registration_manual_activation: فعالسازی حساب دستی + label_registration_automatic_activation: فعالسازی حساب خودکار + label_display_per_page: "ردیف‌ها در هر برگه: %{value}" + label_age: سن + label_change_properties: ویرایش ویژگی‌ها + label_general: همگانی + label_more: بیشتر + label_scm: SCM + label_plugins: افزونه‌ها + label_ldap_authentication: شناساییLDAP + label_downloads_abbr: دریافت + label_optional_description: توضیح اختیاری + label_add_another_file: افزودن پرونده دیگر + label_preferences: پسندها + label_chronological_order: به ترتیب تاریخ + label_reverse_chronological_order: برعکس ترتیب تاریخ + label_planning: برنامه ریزی + label_incoming_emails: ایمیل‌های آمده + label_generate_key: ساخت کلید + label_issue_watchers: دیده‌بان‌ها + label_example: نمونه + label_display: نمایش + label_sort: جور کرد + label_ascending: افزایشی + label_descending: کاهشی + label_date_from_to: از %{start} تا %{end} + label_wiki_content_added: برگه ویکی افزوده شد + label_wiki_content_updated: برگه ویکی بروز شد + label_group: دسته + label_group_plural: دسته + label_group_new: دسته تازه + label_time_entry_plural: زمان گذاشته شده + label_version_sharing_none: بدون اشتراک + label_version_sharing_descendants: با زیر پروژه‌ها + label_version_sharing_hierarchy: با رشته پروژه‌ها + label_version_sharing_tree: با درخت پروژه + label_version_sharing_system: با همه پروژه‌ها + label_update_issue_done_ratios: بروز رسانی اندازه انجام شده پیامد + label_copy_source: منبع + label_copy_target: مقصد + label_copy_same_as_target: مانند مقصد + label_display_used_statuses_only: تنها وضعیت‌هایی نشان داده شوند که در این پیگرد به کار رفته‌اند + label_api_access_key: کلید دسترسی API + label_missing_api_access_key: کلید دسترسی API در دسترس نیست + label_api_access_key_created_on: "کلید دسترسی API %{value} پیش ساخته شده است" + label_profile: نمایه + label_subtask_plural: زیرکار + label_project_copy_notifications: در هنگام رونویسی پروژه ایمیل‌های آگاه‌سازی را بفرست + label_principal_search: "جستجو برای کاربر یا دسته:" + label_user_search: "جستجو برای کاربر:" + + button_login: ورود + button_submit: واگذاری + button_save: نگهداری + button_check_all: گزینش همه + button_uncheck_all: گزینش هیچ + button_delete: پاک + button_create: ساخت + button_create_and_continue: ساخت و ادامه + button_test: آزمایش + button_edit: ویرایش + button_edit_associated_wikipage: "ویرایش برگه ویکی وابسته: %{page_title}" + button_add: افزودن + button_change: ویرایش + button_apply: انجام + button_clear: پاک + button_lock: گذاشتن قفل + button_unlock: برداشتن قفل + button_download: دریافت + button_list: فهرست + button_view: دیدن + button_move: جابجایی + button_move_and_follow: جابجایی و ادامه + button_back: برگشت + button_cancel: بازگشت + button_activate: فعالسازی + button_sort: جور کرد + button_log_time: زمان‌نویسی + button_rollback: برگرد به این نگارش + button_watch: دیده‌بانی + button_unwatch: نا‌دیده‌بانی + button_reply: پاسخ + button_archive: بایگانی + button_unarchive: برگشت از بایگانی + button_reset: بازنشانی + button_rename: نامگذاری + button_change_password: جایگزینی گذرواژه + button_copy: رونوشت + button_copy_and_follow: رونوشت و ادامه + button_annotate: یادداشت + button_update: بروز رسانی + button_configure: پیکربندی + button_quote: نقل قول + button_duplicate: نگارش دیگر + button_show: نمایش + + status_active: فعال + status_registered: نام‌نویسی شده + status_locked: قفل + + version_status_open: باز + version_status_locked: قفل + version_status_closed: بسته + + field_active: فعال + + text_select_mail_notifications: فرمان‌هایی که برای آن‌ها باید ایمیل فرستاده شود را برگزینید. + text_regexp_info: برای نمونه ^[A-Z0-9]+$ + text_min_max_length_info: 0 یعنی بدون کران + text_project_destroy_confirmation: آیا براستی می‌خواهید این پروژه و همه داده‌های آن را پاک کنید؟ + text_subprojects_destroy_warning: "زیرپروژه‌های آن: %{value} هم پاک خواهند شد." + text_workflow_edit: یک نقش و یک پیگرد را برای ویرایش گردش کار برگزینید + text_are_you_sure: آیا این کار انجام شود؟ + text_are_you_sure_with_children: "آیا پیامد و همه زیرپیامدهای آن پاک شوند؟" + text_journal_changed: "«%{label}» از «%{old}» به «%{new}» جایگزین شد" + text_journal_set_to: "«%{label}» به «%{value}» نشانده شد" + text_journal_deleted: "«%{label}» پاک شد (%{old})" + text_journal_added: "«%{label}»، «%{value}» را افزود" + text_tip_task_begin_day: روز آغاز پیامد + text_tip_task_end_day: روز پایان پیامد + text_tip_task_begin_end_day: روز آغاز و پایان پیامد + text_project_identifier_info: 'تنها نویسه‌های کوچک (a-z)، شماره‌ها و خط تیره پذیرفتنی است.
پس از ذخیره سازی، شناسه نمی‌تواند جایگزین شود.' + text_caracters_maximum: "بیشترین اندازه %{count} است." + text_caracters_minimum: "کمترین اندازه %{count} است." + text_length_between: "باید میان %{min} و %{max} نویسه باشد." + text_tracker_no_workflow: هیچ گردش کاری برای این پیگرد مشخص نشده است + text_unallowed_characters: نویسه‌های ناپسند + text_comma_separated: چند مقدار پذیرفتنی است (با «,» از هم جدا شوند). + text_line_separated: چند مقدار پذیرفتنی است (هر مقدار در یک خط). + text_issues_ref_in_commit_messages: نشانه روی و بستن پیامدها در پیام‌های انباره + text_issue_added: "پیامد %{id} به دست %{author} گزارش شد." + text_issue_updated: "پیامد %{id} به دست %{author} بروز شد." + text_wiki_destroy_confirmation: آیا براستی می‌خواهید این ویکی و همه محتوای آن را پاک کنید؟ + text_issue_category_destroy_question: "برخی پیامدها (%{count}) به این دسته واگذار شده‌اند. می‌خواهید چه کنید؟" + text_issue_category_destroy_assignments: پاک کردن واگذاری به دسته + text_issue_category_reassign_to: واگذاری دوباره پیامدها به این دسته + text_user_mail_option: "برای پروژه‌های گزینش نشده، تنها ایمیل‌هایی درباره چیزهایی که دیده‌بان یا درگیر آن‌ها هستید دریافت خواهید کرد (مانند پیامدهایی که نویسنده آن‌ها هستید یا به شما واگذار شده‌اند)." + text_no_configuration_data: "نقش‌ها، پیگردها، وضعیت‌های پیامد و گردش کار هنوز پیکربندی نشده‌اند. \nبه سختی پیشنهاد می‌شود که پیکربندی پیش‌گزیده را بار کنید. سپس می‌توانید آن را ویرایش کنید." + text_load_default_configuration: بارگذاری پیکربندی پیش‌گزیده + text_status_changed_by_changeset: "در تغییر %{value} بروز شده است." + text_time_logged_by_changeset: "در تغییر %{value} نوشته شده است." + text_issues_destroy_confirmation: 'آیا براستی می‌خواهید پیامدهای گزینش شده را پاک کنید؟' + text_select_project_modules: 'پیمانه‌هایی که باید برای این پروژه فعال شوند را برگزینید:' + text_default_administrator_account_changed: حساب سرپرستی پیش‌گزیده جایگزین شد + text_file_repository_writable: پوشه پیوست‌ها نوشتنی است + text_plugin_assets_writable: پوشه دارایی‌های افزونه‌ها نوشتنی است + text_rmagick_available: RMagick در دسترس است (اختیاری) + text_destroy_time_entries_question: "%{hours} ساعت روی پیامدهایی که می‌خواهید پاک کنید کار گزارش شده است. می‌خواهید چه کنید؟" + text_destroy_time_entries: ساعت‌های گزارش شده پاک شوند + text_assign_time_entries_to_project: ساعت‌های گزارش شده به پروژه واگذار شوند + text_reassign_time_entries: 'ساعت‌های گزارش شده به این پیامد واگذار شوند:' + text_user_wrote: "%{value} نوشت:" + text_enumeration_destroy_question: "%{count} داده به این برشمردنی وابسته شده‌اند." + text_enumeration_category_reassign_to: 'به این برشمردنی وابسته شوند:' + text_email_delivery_not_configured: "دریافت ایمیل پیکربندی نشده است و آگاه‌سازی‌ها غیر فعال هستند.\nکارگزار SMTP خود را در config/email.yml پیکربندی کنید و برنامه را بازنشانی کنید تا فعال شوند." + text_repository_usernames_mapping: "کاربر Redmine که به هر نام کاربری پیام‌های انباره نگاشت می‌شود را برگزینید.\nکاربرانی که نام کاربری یا ایمیل همسان دارند، خود به خود نگاشت می‌شوند." + text_diff_truncated: '... این تفاوت بریده شده چون بیشتر از بیشترین اندازه نمایش دادنی است.' + text_custom_field_possible_values_info: 'یک خط برای هر مقدار' + text_wiki_page_destroy_question: "این برگه %{descendants} زیربرگه دارد.می‌خواهید چه کنید؟" + text_wiki_page_nullify_children: "زیربرگه‌ها برگه ریشه شوند" + text_wiki_page_destroy_children: "زیربرگه‌ها و زیربرگه‌های آن‌ها پاک شوند" + text_wiki_page_reassign_children: "زیربرگه‌ها به زیر این برگه پدر بروند" + text_own_membership_delete_confirmation: "شما دارید برخی یا همه پروانه‌های خود را برمی‌دارید و شاید پس از این دیگر نتوانید این پروژه را ویرایش کنید.\nآیا می‌خواهید این کار را بکنید؟" + text_zoom_in: درشتنمایی + text_zoom_out: ریزنمایی + + default_role_manager: سرپرست + default_role_developer: برنامه‌نویس + default_role_reporter: گزارش‌دهنده + default_tracker_bug: ایراد + default_tracker_feature: ویژگی + default_tracker_support: پشتیبانی + default_issue_status_new: تازه + default_issue_status_in_progress: در گردش + default_issue_status_resolved: درست شده + default_issue_status_feedback: بازخورد + default_issue_status_closed: بسته + default_issue_status_rejected: برگشت خورده + default_doc_category_user: نوشتار کاربر + default_doc_category_tech: نوشتار فنی + default_priority_low: پایین + default_priority_normal: میانه + default_priority_high: بالا + default_priority_urgent: زود + default_priority_immediate: بیدرنگ + default_activity_design: طراحی + default_activity_development: ساخت + + enumeration_issue_priorities: برتری‌های پیامد + enumeration_doc_categories: دسته‌های نوشتار + enumeration_activities: فعالیت‌ها (پیگیری زمان) + enumeration_system_activity: فعالیت سامانه + + text_tip_issue_begin_day: issue beginning this day + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_tip_issue_begin_end_day: issue beginning and ending this day + text_tip_issue_end_day: issue ending this day + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. + label_my_queries: My custom queries + label_cvs_module: Module + label_filesystem_path: Root directory + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_darcs_path: Root directory + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + label_news_comment_added: Comment added to a news + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + default_role_anonymous: Anonymous + text_powered_by: Powered by %{link} + default_role_non_member: Non member + label_mercurial_path: Root directory diff --git a/config/locales/fi.yml b/config/locales/fi.yml index a71a1959..d2bf6b3c 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -966,3 +966,13 @@ fi: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 583ef731..f21fcc2f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -306,6 +306,7 @@ fr: field_active: Actif field_parent_issue: Tâche parente field_visible: Visible + field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardé" setting_app_title: Titre de l'application setting_app_subtitle: Sous-titre de l'application @@ -538,6 +539,7 @@ fr: label_news_latest: Dernières annonces label_news_view_all: Voir toutes les annonces label_news_added: Annonce ajoutée + label_news_comment_added: Commentaire ajouté à une annonce label_settings: Configuration label_overview: Aperçu label_version: Version @@ -595,6 +597,7 @@ fr: label_query: Rapport personnalisé label_query_plural: Rapports personnalisés label_query_new: Nouveau rapport + label_my_queries: Mes rapports personnalisés label_filter_add: "Ajouter le filtre " label_filter_plural: Filtres label_equals: égal @@ -783,12 +786,16 @@ fr: label_project_copy_notifications: Envoyer les notifications durant la copie du projet label_principal_search: "Rechercher un utilisateur ou un groupe :" label_user_search: "Rechercher un utilisateur :" + label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande + label_additional_workflow_transitions_for_assignee: Autorisations supplémentaires lorsque la demande est assignée à l'utilisateur button_login: Connexion button_submit: Soumettre button_save: Sauvegarder button_check_all: Tout cocher button_uncheck_all: Tout décocher + button_collapse_all: Plier tout + button_expand_all: Déplier tout button_delete: Supprimer button_create: Créer button_create_and_continue: Créer et continuer @@ -888,6 +895,7 @@ fr: text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes" text_wiki_page_reassign_children: "Réaffecter les sous-pages à cette page" text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-être plus autorisé à modifier ce projet.\nEtes-vous sûr de vouloir continuer ?" + text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardé qui sera perdu si vous quittez la page." default_role_manager: "Manager " default_role_developer: "Développeur " @@ -924,6 +932,7 @@ fr: error_no_tracker_in_project: "Aucun tracker n'est associé à ce projet. Vérifier la configuration du projet." error_no_default_issue_status: "Aucun statut de demande n'est défini par défaut. Vérifier votre configuration (Administration -> Statuts de demandes)." text_journal_changed: "%{label} changé de %{old} à %{new}" + text_journal_changed_no_detail: "%{label} mis à jour" text_journal_set_to: "%{label} mis à %{value}" text_journal_deleted: "%{label} %{old} supprimé" text_journal_added: "%{label} %{value} ajouté" @@ -954,7 +963,7 @@ fr: label_user_mail_option_none: Aucune notification field_member_of_group: Groupe de l'assigné field_assigned_to_role: Rôle de l'assigné - setting_emails_header: Emails header + setting_emails_header: En-tête des emails text_powered_by: Powered by %{link} label_cvs_module: Module label_filesystem_path: Root directory @@ -963,3 +972,4 @@ fr: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + field_effective_date: Due date diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 67335357..8f6b9bb4 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -957,3 +957,13 @@ gl: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/he.yml b/config/locales/he.yml index 0fe8f0cf..e45a7e0a 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -950,3 +950,13 @@ he: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/hr.yml b/config/locales/hr.yml index ba853797..4e086faa 100644 --- a/config/locales/hr.yml +++ b/config/locales/hr.yml @@ -952,3 +952,13 @@ hr: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 4cfa9d26..c951c5da 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -964,3 +964,13 @@ label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/id.yml b/config/locales/id.yml index a9970701..747ced09 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -953,3 +953,13 @@ id: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/it.yml b/config/locales/it.yml index ee751d4c..7d66fa0e 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -946,3 +946,13 @@ it: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/ja.yml b/config/locales/ja.yml index fd55d952..b05c5164 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -324,6 +324,7 @@ ja: field_assigned_to_role: 担当者のロール field_text: テキスト field_visible: 表示 + field_warn_on_leaving_unsaved: セーブされていないページから移動するときに警告する setting_app_title: アプリケーションのタイトル setting_app_subtitle: アプリケーションのサブタイトル @@ -558,6 +559,7 @@ ja: label_news_latest: 最新ニュース label_news_view_all: 全てのニュースを見る label_news_added: ニュースが追加されました + label_news_comment_added: ニュースにコメントが追加されました label_settings: 設定 label_overview: 概要 label_version: バージョン @@ -616,6 +618,7 @@ ja: label_query: カスタムクエリ label_query_plural: カスタムクエリ label_query_new: 新しいクエリ + label_my_queries: マイカスタムクエリ label_filter_add: フィルタ追加 label_filter_plural: フィルタ label_equals: 等しい @@ -820,6 +823,8 @@ ja: button_save: 保存 button_check_all: 全てにチェックをつける button_uncheck_all: 全てのチェックを外す + button_expand_all: 展開 + button_collapse_all: 折りたたみ button_delete: 削除 button_create: 作成 button_create_and_continue: 連続作成 @@ -879,6 +884,7 @@ ja: text_are_you_sure: よろしいですか? text_are_you_sure_with_children: チケットとその子チケット全てを削除しますか? text_journal_changed: "%{label} を %{old} から %{new} に変更" + text_journal_changed_no_detail: "%{label} を更新" text_journal_set_to: "%{label} を %{value} にセット" text_journal_deleted: "%{label} を削除 (%{old})" text_journal_added: "%{label} %{value} を追加" @@ -929,6 +935,7 @@ ja: text_own_membership_delete_confirmation: "いくつかまたはすべての権限をあなた自身から剥奪しようとしているため、このプロジェクトを編集できなくなるかもしれません。\n本当に続けてもよろしいですか?" text_zoom_in: 拡大 text_zoom_out: 縮小 + text_warn_on_leaving_unsaved: このページから移動すると、セーブされていないデータが失われます。 default_role_manager: 管理者 default_role_developer: 開発者 @@ -966,3 +973,7 @@ ja: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date diff --git a/config/locales/ko.yml b/config/locales/ko.yml index aafb8ec4..3f2401f4 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -997,3 +997,13 @@ ko: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 7a55c54e..d310fb7b 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -1005,3 +1005,13 @@ lt: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/lv.yml b/config/locales/lv.yml index f5a6a440..0640637e 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -940,3 +940,13 @@ lv: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/mk.yml b/config/locales/mk.yml index cd41c837..e46c7c88 100644 --- a/config/locales/mk.yml +++ b/config/locales/mk.yml @@ -945,3 +945,13 @@ mk: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/mn.yml b/config/locales/mn.yml index 520114a4..31004e0c 100644 --- a/config/locales/mn.yml +++ b/config/locales/mn.yml @@ -946,3 +946,13 @@ mn: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/nl.yml b/config/locales/nl.yml index f0787221..9f215c3e 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -5,25 +5,25 @@ nl: # Use the strftime parameters for formats. # When no format has been given, it uses default. # You can provide other formats here if you like! - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" + default: "%d-%m-%Y" + short: "%e %b" + long: "%d %B, %Y" - day_names: [Zondag, Maandag, Dinsdag, Woensdag, Donderdag, Vrijdag, Zaterdag] - abbr_day_names: [Zo, Ma, Di, Woe, Do, Vr, Zat] + day_names: [zondag, maandag, dinsdag, woensdag, donderdag, vrijdag, zaterdag] + abbr_day_names: [zo, ma, di, wo, do, vr, za] # Don't forget the nil at the beginning; there's no such thing as a 0th month - month_names: [~, Januari, Februari, Maart, April, Mei, Juni, Juli, Augustus, September, Oktober, November, December] - abbr_month_names: [~, Jan, Feb, Mar, Apr, Mei, Jun, Jul, Aug, Sep, Okt, Nov, Dec] + month_names: [~, januari, februari, maart, april, mei, juni, juli, augustus, september, oktober, november, december] + abbr_month_names: [~, jan, feb, mar, apr, mei, jun, jul, aug, sep, okt, nov, dec] # Used in date_select and datime_select. - order: [ :year, :month, :day ] + order: [ :day, :month, :year ] time: formats: default: "%a, %d %b %Y %H:%M:%S %z" time: "%H:%M" - short: "%d %b %H:%M" - long: "%B %d, %Y %H:%M" + short: "%e %b %H:%M" + long: "%d %B, %Y %H:%M" am: "am" pm: "pm" @@ -32,7 +32,7 @@ nl: half_a_minute: "halve minuut" less_than_x_seconds: one: "minder dan een seconde" - other: "mindera dan %{count} seconden" + other: "minder dan %{count} seconden" x_seconds: one: "1 seconde" other: "%{count} seconden" @@ -56,13 +56,13 @@ nl: other: "%{count} maanden" about_x_years: one: "ongeveer 1 jaar" - other: "ongeveer %{count} jaren" + other: "ongeveer %{count} jaar" over_x_years: - one: "over 1 jaar" - other: "over %{count} jaren" + one: "meer dan 1 jaar" + other: "meer dan %{count} jaar" almost_x_years: - one: "almost 1 year" - other: "almost %{count} years" + one: "bijna 1 jaar" + other: "bijna %{count} jaar" number: format: @@ -94,8 +94,8 @@ nl: errors: template: header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" + one: "Door een fout kon dit %{model} niet worden opgeslagen" + other: "Door %{count} fouten kon dit %{model} niet worden opgeslagen" messages: inclusion: "staat niet in de lijst" exclusion: "is gereserveerd" @@ -143,7 +143,7 @@ nl: button_edit: Bewerk button_list: Lijst button_lock: Sluit - button_log_time: Log tijd + button_log_time: Registreer tijd button_login: Inloggen button_move: Verplaatsen button_quote: Citaat @@ -514,9 +514,9 @@ nl: one: 1 project other: "%{count} projects" label_public_projects: Publieke projecten - label_query: Eigen zoekvraag - label_query_new: Nieuwe zoekvraag - label_query_plural: Eigen zoekvragen + label_query: Eigen zoekopdracht + label_query_new: Nieuwe zoekopdracht + label_query_plural: Eigen zoekopdrachten label_read: Lees... label_register: Registreer label_registered_on: Geregistreerd op @@ -582,7 +582,7 @@ nl: label_used_by: Gebruikt door label_user: Gebruiker label_user_activity: "%{value}'s activiteit" - label_user_mail_no_self_notified: "Ik wil niet op de hoogte gehouden worden van wijzigingen die ik zelf maak." + label_user_mail_no_self_notified: Ik wil niet op de hoogte gehouden worden van mijn eigen wijzigingen label_user_mail_option_all: "Bij elk gebeurtenis in al mijn projecten..." label_user_mail_option_selected: "Enkel bij elke gebeurtenis op het geselecteerde project..." label_user_new: Nieuwe gebruiker @@ -719,7 +719,7 @@ nl: setting_mail_from: Afzender e-mail adres setting_mail_handler_api_enabled: Schakel WS in voor inkomende mail. setting_mail_handler_api_key: API sleutel - setting_per_page_options: Objects per pagina-opties + setting_per_page_options: Aantal objecten per pagina (opties) setting_plain_text_mail: platte tekst (geen HTML) setting_protocol: Protocol setting_repositories_encodings: Repositories coderingen @@ -743,7 +743,7 @@ nl: text_destroy_time_entries: Verwijder gerapporteerde uren text_destroy_time_entries_question: "%{hours} uren werden gerapporteerd op de issue(s) die u wilde verwijderen. Wat wil u doen?" text_diff_truncated: '... Deze diff werd afgekort omdat het de maximale weer te geven karakters overschreed.' - text_email_delivery_not_configured: "E-mailbezorging is niet geconfigureerd. Notificaties zijn uitgeschakeld.\nConfigureer uw SMTP server in config/configuration.yml en herstart de applicatie om dit te activeren." + text_email_delivery_not_configured: "E-mailbezorging is niet geconfigureerd. Mededelingen zijn uitgeschakeld.\nConfigureer uw SMTP server in config/configuration.yml en herstart de applicatie om dit te activeren." text_enumeration_category_reassign_to: 'Wijs de volgende waarde toe:' text_enumeration_destroy_question: "%{count} objecten zijn toegewezen aan deze waarde." text_file_repository_writable: Bestandsrepository beschrijfbaar @@ -774,7 +774,7 @@ nl: text_tip_issue_end_day: issue die op deze dag eindigt text_tracker_no_workflow: Geen workflow gedefinieerd voor deze tracker text_unallowed_characters: Niet toegestane tekens - text_user_mail_option: "Bij niet-geselecteerde projecten zult u enkel notificaties ontvangen voor issues die u monitort of waar u bij betrokken bent (als auteur of toegewezen persoon)." + text_user_mail_option: "Bij niet-geselecteerde projecten zult u enkel mededelingen ontvangen voor issues die u monitort of waar u bij betrokken bent (als auteur of toegewezen persoon)." text_user_wrote: "%{value} schreef:" text_wiki_destroy_confirmation: Weet u zeker dat u deze wiki en zijn inhoud wenst te verwijderen? text_workflow_edit: Selecteer een rol en een tracker om de workflow te wijzigen @@ -796,7 +796,7 @@ nl: label_date_from_to: Van %{start} tot %{end} label_greater_or_equal: ">=" label_less_or_equal: <= - text_wiki_page_destroy_question: Deze pagina heeft %{descendants} subpagina's en onderliggende pagina's?. Wil wil je ermee doen? + text_wiki_page_destroy_question: Deze pagina heeft %{descendants} subpagina's en onderliggende pagina's?. Wat wilt u hiermee doen? text_wiki_page_reassign_children: Alle subpagina's toewijzen aan deze hoofdpagina text_wiki_page_nullify_children: Behoud subpagina's als hoofdpagina's text_wiki_page_destroy_children: Verwijder alle subpagina's en onderliggende pagina's @@ -805,9 +805,9 @@ nl: mail_subject_wiki_content_updated: "'%{id}' wiki pagina is bijgewerkt" label_wiki_content_added: Wiki pagina toegevoegd mail_subject_wiki_content_added: "'%{id}' wiki pagina is toegevoegd" - mail_body_wiki_content_added: The '%{id}' wiki pagina is toegevoegd door %{author}. + mail_body_wiki_content_added: De '%{id}' wiki pagina is toegevoegd door %{author}. label_wiki_content_updated: Wiki pagina bijgewerkt - mail_body_wiki_content_updated: The '%{id}' wiki pagina is bijgewerkt door %{author}. + mail_body_wiki_content_updated: De '%{id}' wiki pagina is bijgewerkt door %{author}. permission_add_project: Maak project setting_new_project_user_role_id: Rol van gebruiker die een project maakt label_view_all_revisions: Bekijk alle revisies @@ -896,34 +896,44 @@ nl: text_zoom_in: Zoom in notice_unable_delete_time_entry: Verwijderen niet mogelijk van tijd log invoer. label_overall_spent_time: Totaal bestede tijd - field_time_entries: Log tijd + field_time_entries: Registreer tijd project_module_gantt: Gantt project_module_calendar: Kalender - button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}" - text_are_you_sure_with_children: Delete issue and all child issues? - field_text: Text field - label_user_mail_option_only_owner: Only for things I am the owner of - setting_default_notification_option: Default notification option - label_user_mail_option_only_my_events: Only for things I watch or I'm involved in - label_user_mail_option_only_assigned: Only for things I am assigned to - label_user_mail_option_none: No events + button_edit_associated_wikipage: "Bewerk bijbehorende wiki pagina: %{page_title}" + text_are_you_sure_with_children: Verwijder issue en alle onderliggende issues? + field_text: Tekst veld + label_user_mail_option_only_owner: Alleen voor dingen waarvan ik de auteur ben + setting_default_notification_option: Standaard instelling voor mededelingen + label_user_mail_option_only_my_events: Alleen voor dingen die ik volg of bij betrokken ben + label_user_mail_option_only_assigned: Alleen voor dingen die aan mij zijn toegewezen + label_user_mail_option_none: Bij geen enkele gebeurtenis field_member_of_group: Assignee's group field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible + notice_not_authorized_archived_project: Het project dat u wilt bezoeken is gearchiveerd. + label_principal_search: "Zoek naar gebruiker of groep:" + label_user_search: "Zoek naar gebruiker:" + field_visible: Zichtbaar setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - text_powered_by: Powered by %{link} + setting_commit_logtime_activity_id: Standaard activiteit voor tijdregistratie + text_time_logged_by_changeset: Toegepast in changeset %{value}. + setting_commit_logtime_enabled: Activeer tijdregistratie + notice_gantt_chart_truncated: De gantt chart is ingekort omdat het meer objecten bevat dan kan worden weergegeven, (%{max}) + setting_gantt_items_limit: Max. aantal objecten op gantt chart + field_warn_on_leaving_unsaved: Waarschuw me wanneer ik een pagina verlaat waarvan de tekst niet opgeslagen is + text_warn_on_leaving_unsaved: De huidige pagina bevat tekst die niet is opgeslagen en dit zal verloren gaan als u deze pagina nu verlaat. + label_my_queries: Mijn aangepaste zoekopdrachten + label_news_comment_added: Commentaar toegevoegd aan een nieuwsitem + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author label_cvs_module: Module label_filesystem_path: Root directory label_darcs_path: Root directory + text_journal_changed_no_detail: "%{label} updated" + field_effective_date: Due date label_bazaar_path: Root directory label_cvs_path: CVSROOT label_git_path: Path to .git directory + text_powered_by: Powered by %{link} label_mercurial_path: Root directory diff --git a/config/locales/no.yml b/config/locales/no.yml index 242ef875..73c42c61 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -932,3 +932,13 @@ label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/pl.yml b/config/locales/pl.yml index c4fc3460..31724818 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -295,9 +295,9 @@ pl: field_version: Wersja field_vf_personnel: Personel field_vf_watcher: Obserwator - general_csv_decimal_separator: '.' + general_csv_decimal_separator: ',' general_csv_encoding: UTF-8 - general_csv_separator: ',' + general_csv_separator: ';' general_first_day_of_week: '1' general_lang_name: 'Polski' general_pdf_encoding: UTF-8 @@ -962,3 +962,13 @@ pl: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 6f321f60..aa438e75 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -314,7 +314,7 @@ pt-BR: setting_issue_list_default_columns: Colunas padrão visíveis na lista de tarefas setting_repositories_encodings: Codificação dos repositórios setting_commit_logs_encoding: Codificação das mensagens de commit - setting_emails_footer: Rodapé dos e-mails + setting_emails_footer: Rodapé do e-mail setting_protocol: Protocolo setting_per_page_options: Número de itens exibidos por página setting_user_format: Formato de exibição de nome de usuário @@ -353,6 +353,9 @@ pt-BR: label_issues_by: "Tarefas por %{value}" label_issue_added: Tarefa adicionada label_issue_updated: Tarefa atualizada + label_issue_note_added: Nota adicionada + label_issue_status_updated: Situação atualizada + label_issue_priority_updated: Prioridade atualizada label_document: Documento label_document_new: Novo documento label_document_plural: Documentos @@ -556,7 +559,7 @@ pt-BR: label_spent_time: Tempo gasto label_f_hour: "%{value} hora" label_f_hour_plural: "%{value} horas" - label_time_tracking: Controle de horas + label_time_tracking: Registro de horas label_change_plural: Alterações label_statistics: Estatísticas label_commits_per_month: Commits por mês @@ -755,7 +758,7 @@ pt-BR: enumeration_issue_priorities: Prioridade das tarefas enumeration_doc_categories: Categorias de documento - enumeration_activities: Atividades (time tracking) + enumeration_activities: Atividades (registro de horas) notice_unable_delete_version: Não foi possível excluir a versão label_renamed: renomeado label_copied: copiado @@ -944,24 +947,34 @@ pt-BR: setting_default_notification_option: Opção padrão de notificação label_user_mail_option_only_my_events: Somente para as coisas que eu esteja observando ou esteja envolvido label_user_mail_option_only_assigned: Somente para as coisas que estejam atribuídas a mim - label_user_mail_option_none: No events - field_member_of_group: Assignee's group - field_assigned_to_role: Assignee's role - notice_not_authorized_archived_project: The project you're trying to access has been archived. - label_principal_search: "Search for user or group:" - label_user_search: "Search for user:" - field_visible: Visible - setting_emails_header: Emails header - setting_commit_logtime_activity_id: Activity for logged time - text_time_logged_by_changeset: Applied in changeset %{value}. - setting_commit_logtime_enabled: Enable time logging - notice_gantt_chart_truncated: The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max}) - setting_gantt_items_limit: Maximum number of items displayed on the gantt chart - text_powered_by: Powered by %{link} + label_user_mail_option_none: Sem eventos + field_member_of_group: Grupo do responsável + field_assigned_to_role: Papel do responsável + notice_not_authorized_archived_project: O projeto que você está tentando acessar foi arquivado. + label_principal_search: "Pesquisar por usuários ou grupos:" + label_user_search: "Pesquisar por usuário:" + field_visible: Visível + setting_emails_header: Cabeçalho do e-mail + setting_commit_logtime_activity_id: Atividade para registrar horas + text_time_logged_by_changeset: Aplicado no changeset %{value}. + setting_commit_logtime_enabled: Habilitar registro de horas + notice_gantt_chart_truncated: O gráfico foi cortado por exceder o tamanho máximo de linhas que podem ser exibidas (%{max}) + setting_gantt_items_limit: Número máximo de itens exibidos no gráfico gatt + field_warn_on_leaving_unsaved: Alertar-me ao sarir de uma página sem salvar o texto + text_warn_on_leaving_unsaved: A página atual contem texto que não foi salvo e será perdido se você sair desta página. + label_my_queries: Minhas consultas personalizadas + text_journal_changed_no_detail: "%{label} atualizado(a)" + label_news_comment_added: Notícia recebeu um comentário + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author label_cvs_module: Module label_filesystem_path: Root directory label_darcs_path: Root directory + field_effective_date: Due date label_bazaar_path: Root directory label_cvs_path: CVSROOT label_git_path: Path to .git directory + text_powered_by: Powered by %{link} label_mercurial_path: Root directory diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 0fc086bb..787a3a94 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -949,3 +949,13 @@ pt: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 004c17a9..874e4e30 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -938,3 +938,13 @@ ro: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 11c44b3c..46f75303 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1058,3 +1058,13 @@ ru: label_cvs_path: CVSROOT label_git_path: Путь к каталогу .git label_mercurial_path: Каталог + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/sk.yml b/config/locales/sk.yml index e71d5b8c..f50585d3 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -940,3 +940,13 @@ sk: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/sl.yml b/config/locales/sl.yml index fef33bfe..7003abef 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -132,7 +132,7 @@ sl: general_csv_separator: ',' general_csv_decimal_separator: '.' general_csv_encoding: UTF-8 - general_pdf_encoding: UFT-8 + general_pdf_encoding: UTF-8 general_first_day_of_week: '1' notice_account_updated: Račun je bil uspešno posodobljen. @@ -941,3 +941,13 @@ sl: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml index c5eb04e3..b0e13450 100644 --- a/config/locales/sr-YU.yml +++ b/config/locales/sr-YU.yml @@ -945,3 +945,13 @@ sr-YU: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 4cacef55..14dbfaef 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -946,3 +946,13 @@ sr: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 0a68eb7c..a65a54e3 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -344,6 +344,7 @@ sv: field_assigned_to_role: "Tilldelad användares roll" field_text: Textfält field_visible: Synlig + field_warn_on_leaving_unsaved: Varna om jag lämnar en sida med osparad text setting_app_title: Applikationsrubrik setting_app_subtitle: Applikationsunderrubrik @@ -635,6 +636,7 @@ sv: label_query: Användardefinerad fråga label_query_plural: Användardefinerade frågor label_query_new: Ny fråga + label_my_queries: Mina egna frågor label_filter_add: Lägg till filter label_filter_plural: Filter label_equals: är @@ -899,6 +901,7 @@ sv: text_are_you_sure: Är du säker ? text_are_you_sure_with_children: Ta bort ärende och alla underärenden? text_journal_changed: "%{label} ändrad från %{old} till %{new}" + text_journal_changed_no_detail: "%{label} uppdaterad" text_journal_set_to: "%{label} satt till %{value}" text_journal_deleted: "%{label} borttagen (%{old})" text_journal_added: "%{label} %{value} tillagd" @@ -949,6 +952,7 @@ sv: text_own_membership_delete_confirmation: "Några av, eller alla, dina behörigheter kommer att tas bort och du kanske inte längre kommer kunna göra ändringar i det här projektet.\nVill du verkligen fortsätta?" text_zoom_out: Zooma ut text_zoom_in: Zooma in + text_warn_on_leaving_unsaved: Nuvarande sida innehåller osparad text som kommer försvinna om du lämnar sidan. default_role_manager: Projektledare default_role_developer: Utvecklare @@ -986,3 +990,10 @@ sv: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news diff --git a/config/locales/th.yml b/config/locales/th.yml index 110d7044..2362f715 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -942,3 +942,13 @@ th: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 9c039584..a56bc229 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -964,3 +964,13 @@ tr: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/uk.yml b/config/locales/uk.yml index efcc6632..83e0e2d9 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -941,3 +941,13 @@ uk: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/vi.yml b/config/locales/vi.yml index b163a85b..354ea263 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -996,3 +996,13 @@ vi: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 6de2ce73..664db7ea 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -384,6 +384,7 @@ field_assigned_to_role: "被指派者的角色" field_text: 內容文字 field_visible: 可被看見 + field_warn_on_leaving_unsaved: "提醒我將要離開的頁面中尚有未儲存的資料" setting_app_title: 標題 setting_app_subtitle: 副標題 @@ -675,6 +676,7 @@ label_query: 自訂查詢 label_query_plural: 自訂查詢 label_query_new: 建立新查詢 + label_my_queries: 我的自訂查詢 label_filter_add: 加入新篩選條件 label_filter_plural: 篩選條件 label_equals: 等於 @@ -939,6 +941,7 @@ text_are_you_sure: 確定執行? text_are_you_sure_with_children: "確定刪除此工作項目及其子項目?" text_journal_changed: "%{label} 從 %{old} 變更為 %{new}" + text_journal_changed_no_detail: "%{label} 已更新" text_journal_set_to: "%{label} 設定為 %{value}" text_journal_deleted: "%{label} 已刪除 (%{old})" text_journal_added: "%{label} %{value} 已新增" @@ -989,6 +992,7 @@ text_own_membership_delete_confirmation: "您在專案中,所擁有的部分或全部權限即將被移除,在這之後可能無法再次編輯此專案。\n您確定要繼續執行這個動作?" text_zoom_in: 放大 text_zoom_out: 縮小 + text_warn_on_leaving_unsaved: "若您離開這個頁面,此頁面所包含的未儲存資料將會遺失。" default_role_manager: 管理人員 default_role_developer: 開發人員 @@ -1026,3 +1030,10 @@ label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 4a2de541..4b2b4004 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -959,3 +959,13 @@ zh: label_cvs_path: CVSROOT label_git_path: Path to .git directory label_mercurial_path: Root directory + label_my_queries: My custom queries + label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee + text_journal_changed_no_detail: "%{label} updated" + button_expand_all: Expand all + button_collapse_all: Collapse all + label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author + field_effective_date: Due date + label_news_comment_added: Comment added to a news + field_warn_on_leaving_unsaved: Warn me when leaving a page with unsaved text + text_warn_on_leaving_unsaved: The current page contains unsaved text that will be lost if you leave this page. diff --git a/config/routes.rb b/config/routes.rb index 3b37b515..378fdb0b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,14 +14,15 @@ ActionController::Routing::Routes.draw do |map| map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow' map.connect 'help/:ctrl/:page', :controller => 'help' - map.connect 'projects/:project_id/time_entries/report', :controller => 'time_entry_reports', :action => 'report' map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report| + time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report' + time_report.connect 'projects/:project_id/issues/:issue_id/time_entries/report.:format' + time_report.connect 'projects/:project_id/time_entries/report' + time_report.connect 'projects/:project_id/time_entries/report.:format' time_report.connect 'time_entries/report' time_report.connect 'time_entries/report.:format' - time_report.connect 'projects/:project_id/time_entries/report.:format' end - # TODO: wasteful since this is also nested under issues, projects, and projects/issues map.resources :time_entries, :controller => 'timelog' map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post} diff --git a/db/migrate/20110220160626_add_workflows_assignee_and_author.rb b/db/migrate/20110220160626_add_workflows_assignee_and_author.rb new file mode 100644 index 00000000..73ccb4e0 --- /dev/null +++ b/db/migrate/20110220160626_add_workflows_assignee_and_author.rb @@ -0,0 +1,13 @@ +class AddWorkflowsAssigneeAndAuthor < ActiveRecord::Migration + def self.up + add_column :workflows, :assignee, :boolean, :null => false, :default => false + add_column :workflows, :author, :boolean, :null => false, :default => false + Workflow.update_all("assignee = #{Workflow.connection.quoted_false}") + Workflow.update_all("author = #{Workflow.connection.quoted_false}") + end + + def self.down + remove_column :workflows, :assignee + remove_column :workflows, :author + end +end diff --git a/db/migrate/20110223180944_add_users_salt.rb b/db/migrate/20110223180944_add_users_salt.rb new file mode 100644 index 00000000..f1cf6483 --- /dev/null +++ b/db/migrate/20110223180944_add_users_salt.rb @@ -0,0 +1,9 @@ +class AddUsersSalt < ActiveRecord::Migration + def self.up + add_column :users, :salt, :string, :limit => 64 + end + + def self.down + remove_column :users, :salt + end +end diff --git a/db/migrate/20110223180953_salt_user_passwords.rb b/db/migrate/20110223180953_salt_user_passwords.rb new file mode 100644 index 00000000..9f017db9 --- /dev/null +++ b/db/migrate/20110223180953_salt_user_passwords.rb @@ -0,0 +1,13 @@ +class SaltUserPasswords < ActiveRecord::Migration + + def self.up + say_with_time "Salting user passwords, this may take some time..." do + User.salt_unsalted_passwords! + end + end + + def self.down + # Unsalted passwords can not be restored + raise ActiveRecord::IrreversibleMigration, "Can't decypher salted passwords. This migration can not be rollback'ed." + end +end diff --git a/db/migrate/20110224000000_add_repositories_path_encoding.rb b/db/migrate/20110224000000_add_repositories_path_encoding.rb new file mode 100644 index 00000000..253d7a66 --- /dev/null +++ b/db/migrate/20110224000000_add_repositories_path_encoding.rb @@ -0,0 +1,9 @@ +class AddRepositoriesPathEncoding < ActiveRecord::Migration + def self.up + add_column :repositories, :path_encoding, :string, :limit => 64, :default => nil + end + + def self.down + remove_column :repositories, :path_encoding + end +end diff --git a/db/migrate/20110226120112_change_repositories_password_limit.rb b/db/migrate/20110226120112_change_repositories_password_limit.rb new file mode 100644 index 00000000..1ad937c7 --- /dev/null +++ b/db/migrate/20110226120112_change_repositories_password_limit.rb @@ -0,0 +1,9 @@ +class ChangeRepositoriesPasswordLimit < ActiveRecord::Migration + def self.up + change_column :repositories, :password, :string, :limit => nil, :default => '' + end + + def self.down + change_column :repositories, :password, :string, :limit => 60, :default => '' + end +end diff --git a/db/migrate/20110226120132_change_auth_sources_account_password_limit.rb b/db/migrate/20110226120132_change_auth_sources_account_password_limit.rb new file mode 100644 index 00000000..b1cd80aa --- /dev/null +++ b/db/migrate/20110226120132_change_auth_sources_account_password_limit.rb @@ -0,0 +1,9 @@ +class ChangeAuthSourcesAccountPasswordLimit < ActiveRecord::Migration + def self.up + change_column :auth_sources, :account_password, :string, :limit => nil, :default => '' + end + + def self.down + change_column :auth_sources, :account_password, :string, :limit => 60, :default => '' + end +end diff --git a/db/migrate/20110227125750_change_journal_details_values_to_text.rb b/db/migrate/20110227125750_change_journal_details_values_to_text.rb new file mode 100644 index 00000000..25886575 --- /dev/null +++ b/db/migrate/20110227125750_change_journal_details_values_to_text.rb @@ -0,0 +1,11 @@ +class ChangeJournalDetailsValuesToText < ActiveRecord::Migration + def self.up + change_column :journal_details, :old_value, :text + change_column :journal_details, :value, :text + end + + def self.down + change_column :journal_details, :old_value, :string + change_column :journal_details, :value, :string + end +end diff --git a/db/migrate/20110228000000_add_repositories_log_encoding.rb b/db/migrate/20110228000000_add_repositories_log_encoding.rb new file mode 100644 index 00000000..85cadafc --- /dev/null +++ b/db/migrate/20110228000000_add_repositories_log_encoding.rb @@ -0,0 +1,9 @@ +class AddRepositoriesLogEncoding < ActiveRecord::Migration + def self.up + add_column :repositories, :log_encoding, :string, :limit => 64, :default => nil + end + + def self.down + remove_column :repositories, :log_encoding + end +end diff --git a/db/migrate/20110228000100_copy_repositories_log_encoding.rb b/db/migrate/20110228000100_copy_repositories_log_encoding.rb new file mode 100644 index 00000000..4d975f80 --- /dev/null +++ b/db/migrate/20110228000100_copy_repositories_log_encoding.rb @@ -0,0 +1,18 @@ +class CopyRepositoriesLogEncoding < ActiveRecord::Migration + def self.up + encoding = Setting.commit_logs_encoding.to_s.strip + encoding = encoding.blank? ? 'UTF-8' : encoding + Repository.find(:all).each do |repo| + scm = repo.scm_name + case scm + when 'Subversion', 'Mercurial', 'Git', 'Filesystem' + repo.update_attribute(:log_encoding, nil) + else + repo.update_attribute(:log_encoding, encoding) + end + end + end + + def self.down + end +end diff --git a/db/migrate/20110401192910_add_index_to_users_type.rb b/db/migrate/20110401192910_add_index_to_users_type.rb new file mode 100644 index 00000000..b9f50112 --- /dev/null +++ b/db/migrate/20110401192910_add_index_to_users_type.rb @@ -0,0 +1,9 @@ +class AddIndexToUsersType < ActiveRecord::Migration + def self.up + add_index :users, :type + end + + def self.down + remove_index :users, :type + end +end diff --git a/doc/CHANGELOG.rdoc b/doc/CHANGELOG.rdoc index 193f15a6..ee26324d 100644 --- a/doc/CHANGELOG.rdoc +++ b/doc/CHANGELOG.rdoc @@ -1,5 +1,24 @@ = ChiliProject changelog +== TBD v2.0.0 + +* From Redmine v1.1.2 +* Defect #3132: Bulk editing menu non-functional in Opera browser +* Defect #6090: Most binary files become corrupted when downloading from CVS repository browser when Redmine is running on a Windows server +* Defect #7280: Issues subjects wrap in Gantt +* Defect #7288: Non ASCII filename downloaded from repo is broken on Internet Explorer. +* Defect #7317: Gantt tab gives internal error due to nil avatar icon +* Defect #7497: Aptana Studio .project file added to version 1.1.1-stable +* Defect #7611: Workflow summary shows X icon for workflow with exactly 1 status transition +* Defect #7625: Syntax highlighting unavailable from board new topic or topic edit preview +* Defect #7630: Spent time in commits not recognized +* Defect #7656: MySQL SQL Syntax Error when filtering issues by Assignee's Group +* Defect #7718: Minutes logged in commit message are converted to hours +* Defect #7763: Email notification are sent to watchers even if 'No events' setting is chosen +* Feature #7608: Add "retro" gravatars +* Patch #7598: Extensible MailHandler +* Patch #7795: Internal server error at journals#index with custom fields + == 2011-03-27 v1.2.0 * Bug #209: Don't hardcode user viewable labels (like "Path to .git repository") diff --git a/extra/svn/Redmine.pm b/extra/svn/Redmine.pm index e1e1e171..a9968cc5 100644 --- a/extra/svn/Redmine.pm +++ b/extra/svn/Redmine.pm @@ -231,7 +231,7 @@ sub RedmineDSN { my ($self, $parms, $arg) = @_; $self->{RedmineDSN} = $arg; my $query = "SELECT - hashed_password, auth_source_id, permissions + hashed_password, salt, auth_source_id, permissions FROM members, projects, users, roles, member_roles WHERE projects.id=members.project_id @@ -426,11 +426,12 @@ sub is_member { $sth->execute($redmine_user, $project_id); my $ret; - while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) { + while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) { unless ($auth_source_id) { - my $method = $r->method; - if ($hashed_password eq $pass_digest && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { + my $method = $r->method; + my $salted_password = Digest::SHA1::sha1_hex($salt.$pass_digest); + if ($hashed_password eq $salted_password && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { $ret = 1; last; } diff --git a/extra/svn/reposman.rb b/extra/svn/reposman.rb index c9b9b0f5..fa4ef82f 100755 --- a/extra/svn/reposman.rb +++ b/extra/svn/reposman.rb @@ -205,7 +205,7 @@ end log("retrieved #{projects.size} projects", :level => 1) def set_owner_and_rights(project, repos_path, &block) - if RUBY_PLATFORM =~ /mswin/ + if mswin? yield if block_given? else uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid) diff --git a/lib/redcloth3.rb b/lib/redcloth3.rb index 7c9df072..f4c62443 100644 --- a/lib/redcloth3.rb +++ b/lib/redcloth3.rb @@ -296,11 +296,11 @@ class RedCloth3 < String rip_offtags text no_textile text escape_html_tags text + # need to do this before #hard_break and #blocks + block_textile_quotes text unless @lite_mode hard_break text unless @lite_mode refs text - # need to do this before text is split by #blocks - block_textile_quotes text blocks text end inline text @@ -707,11 +707,13 @@ class RedCloth3 < String atts = pba( atts ) # pass to prefix handler + replacement = nil if respond_to? "textile_#{ tag }", true - text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) ) + replacement = method( "textile_#{ tag }" ).call( tag, atts, cite, content ) elsif respond_to? "textile_#{ tagpre }_", true - text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) ) + replacement = method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) end + text.gsub!( $& ) { replacement } if replacement end end diff --git a/lib/redmine.rb b/lib/redmine.rb index 7fcd7647..631f1094 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -41,6 +41,8 @@ Redmine::CustomFieldFormat.map do |fields| fields.register Redmine::CustomFieldFormat.new('list', :label => :label_list, :order => 5) fields.register Redmine::CustomFieldFormat.new('date', :label => :label_date, :order => 6) fields.register Redmine::CustomFieldFormat.new('bool', :label => :label_boolean, :order => 7) + fields.register Redmine::CustomFieldFormat.new('user', :label => :label_user, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 8) + fields.register Redmine::CustomFieldFormat.new('version', :label => :label_version, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 9) end # Permissions @@ -62,7 +64,7 @@ Redmine::AccessControl.map do |map| :auto_complete => [:issues], :context_menus => [:issues], :versions => [:index, :show, :status_by], - :journals => :index, + :journals => [:index, :diff], :queries => :index, :reports => [:issue_report, :issue_report_details]} map.permission :add_issues, {:issues => [:new, :create, :update_form]} diff --git a/lib/redmine/ciphering.rb b/lib/redmine/ciphering.rb new file mode 100644 index 00000000..8efe37c2 --- /dev/null +++ b/lib/redmine/ciphering.rb @@ -0,0 +1,95 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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. + +module Redmine + module Ciphering + def self.included(base) + base.extend ClassMethods + end + + class << self + def encrypt_text(text) + if cipher_key.blank? + text + else + c = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + iv = c.random_iv + c.encrypt + c.key = cipher_key + c.iv = iv + e = c.update(text.to_s) + e << c.final + "aes-256-cbc:" + [e, iv].map {|v| Base64.encode64(v).strip}.join('--') + end + end + + def decrypt_text(text) + if text && match = text.match(/\Aaes-256-cbc:(.+)\Z/) + text = match[1] + c = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + e, iv = text.split("--").map {|s| Base64.decode64(s)} + c.decrypt + c.key = cipher_key + c.iv = iv + d = c.update(e) + d << c.final + else + text + end + end + + def cipher_key + key = Redmine::Configuration['database_cipher_key'].to_s + key.blank? ? nil : Digest::SHA256.hexdigest(key) + end + end + + module ClassMethods + def encrypt_all(attribute) + transaction do + all.each do |object| + clear = object.send(attribute) + object.send "#{attribute}=", clear + raise(ActiveRecord::Rollback) unless object.save(false) + end + end ? true : false + end + + def decrypt_all(attribute) + transaction do + all.each do |object| + clear = object.send(attribute) + object.write_attribute attribute, clear + raise(ActiveRecord::Rollback) unless object.save(false) + end + end + end ? true : false + end + + private + + # Returns the value of the given ciphered attribute + def read_ciphered_attribute(attribute) + Redmine::Ciphering.decrypt_text(read_attribute(attribute)) + end + + # Sets the value of the given ciphered attribute + def write_ciphered_attribute(attribute, value) + write_attribute(attribute, Redmine::Ciphering.encrypt_text(value)) + end + end +end diff --git a/lib/redmine/configuration.rb b/lib/redmine/configuration.rb index d4832272..8b2a4c12 100644 --- a/lib/redmine/configuration.rb +++ b/lib/redmine/configuration.rb @@ -70,6 +70,16 @@ module Redmine @config[name] end + # Yields a block with the specified hash configuration settings + def with(settings) + settings.stringify_keys! + load unless @config + was = settings.keys.inject({}) {|h,v| h[v] = @config[v]; h} + @config.merge! settings + yield if block_given? + @config.merge! was + end + private def load_from_yaml(filename, env) diff --git a/lib/redmine/custom_field_format.rb b/lib/redmine/custom_field_format.rb index 2f12397d..7c6364f7 100644 --- a/lib/redmine/custom_field_format.rb +++ b/lib/redmine/custom_field_format.rb @@ -22,12 +22,14 @@ module Redmine cattr_accessor :available @@available = {} - attr_accessor :name, :order, :label + attr_accessor :name, :order, :label, :edit_as, :class_names def initialize(name, options={}) self.name = name self.label = options[:label] self.order = options[:order] + self.edit_as = options[:edit_as] || name + self.class_names = options[:only] end def format(value) @@ -47,12 +49,11 @@ module Redmine return value } end - - # Allow displaying the edit type of another field_format - # - # Example: display a custom field as a list - def edit_as - name + + ['user', 'version'].each do |name| + define_method("format_as_#{name}") {|value| + return value.blank? ? "" : name.classify.constantize.find_by_id(value.to_i).to_s + } end class << self @@ -79,8 +80,10 @@ module Redmine end # Return an array of custom field formats which can be used in select_tag - def as_select - @@available.values.sort {|a,b| + def as_select(class_name=nil) + fields = @@available.values + fields = fields.select {|field| field.class_names.nil? || field.class_names.include?(class_name)} + fields.sort {|a,b| a.order <=> b.order }.collect {|custom_field_format| [ l(custom_field_format.label), custom_field_format.name ] diff --git a/lib/redmine/export/pdf.rb b/lib/redmine/export/pdf.rb index c98b25ff..baf553ca 100644 --- a/lib/redmine/export/pdf.rb +++ b/lib/redmine/export/pdf.rb @@ -19,7 +19,9 @@ require 'iconv' require 'rfpdf/fpdf' -require 'rfpdf/chinese' +require 'fpdf/chinese' +require 'fpdf/japanese' +require 'fpdf/korean' module Redmine module Export @@ -27,38 +29,15 @@ module Redmine include ActionView::Helpers::TextHelper include ActionView::Helpers::NumberHelper - class IFPDF < FPDF + class ITCPDF < TCPDF include Redmine::I18n attr_accessor :footer_date def initialize(lang) super() set_language_if_valid lang - case current_language.to_s.downcase - when 'ko' - extend(PDF_Korean) - AddUHCFont() - @font_for_content = 'UHC' - @font_for_footer = 'UHC' - when 'ja' - extend(PDF_Japanese) - AddSJISFont() - @font_for_content = 'SJIS' - @font_for_footer = 'SJIS' - when 'zh' - extend(PDF_Chinese) - AddGBFont() - @font_for_content = 'GB' - @font_for_footer = 'GB' - when 'zh-tw' - extend(PDF_Chinese) - AddBig5Font() - @font_for_content = 'Big5' - @font_for_footer = 'Big5' - else - @font_for_content = 'Arial' - @font_for_footer = 'Helvetica' - end + @font_for_content = 'FreeSans' + @font_for_footer = 'FreeSans' SetCreator(Redmine::Info.app_name) SetFont(@font_for_content) end @@ -87,8 +66,83 @@ module Redmine return '('+escape(s)+')' end end - - def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='') + + alias RDMCell Cell + alias RDMMultiCell MultiCell + + def Footer + SetFont(@font_for_footer, 'I', 8) + SetY(-15) + SetX(15) + RDMCell(0, 5, @footer_date, 0, 0, 'L') + SetY(-15) + SetX(-30) + RDMCell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C') + end + end + + class IFPDF < FPDF + include Redmine::I18n + attr_accessor :footer_date + + def initialize(lang) + super() + set_language_if_valid lang + case current_language.to_s.downcase + when 'ko' + extend(PDF_Korean) + AddUHCFont() + @font_for_content = 'UHC' + @font_for_footer = 'UHC' + when 'ja' + extend(PDF_Japanese) + AddSJISFont() + @font_for_content = 'SJIS' + @font_for_footer = 'SJIS' + when 'zh' + extend(PDF_Chinese) + AddGBFont() + @font_for_content = 'GB' + @font_for_footer = 'GB' + when 'zh-tw' + extend(PDF_Chinese) + AddBig5Font() + @font_for_content = 'Big5' + @font_for_footer = 'Big5' + else + @font_for_content = 'Arial' + @font_for_footer = 'Helvetica' + end + SetCreator(Redmine::Info.app_name) + SetFont(@font_for_content) + end + + def SetFontStyle(style, size) + SetFont(@font_for_content, style, size) + end + + def SetTitle(txt) + txt = begin + utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt) + hextxt = "" + rescue + txt + end || '' + super(txt) + end + + def textstring(s) + # Format a text string + if s =~ /^ [:user, :details], :order => "#{Journal.table_name}.created_on ASC") pdf.SetFontStyle('B',8) - pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name) + pdf.RDMCell(190,5, format_time(journal.created_on) + " - " + journal.user.name) pdf.Ln pdf.SetFontStyle('I',8) for detail in journal.details - pdf.Cell(190,5, "- " + show_detail(detail, true)) + pdf.RDMCell(190,5, "- " + show_detail(detail, true)) pdf.Ln end if journal.notes? pdf.SetFontStyle('',8) - pdf.MultiCell(190,5, journal.notes.to_s) + pdf.RDMMultiCell(190,5, journal.notes.to_s) end pdf.Ln end if issue.attachments.any? pdf.SetFontStyle('B',9) - pdf.Cell(190,5, l(:label_attachment_plural), "B") + pdf.RDMCell(190,5, l(:label_attachment_plural), "B") pdf.Ln for attachment in issue.attachments pdf.SetFontStyle('',8) - pdf.Cell(80,5, attachment.filename) - pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R") - pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R") - pdf.Cell(65,5, attachment.author.name,0,0,"R") + pdf.RDMCell(80,5, attachment.filename) + pdf.RDMCell(20,5, number_to_human_size(attachment.filesize),0,0,"R") + pdf.RDMCell(25,5, format_date(attachment.created_on),0,0,"R") + pdf.RDMCell(65,5, attachment.author.name,0,0,"R") pdf.Ln end end diff --git a/lib/redmine/helpers/calendar.rb b/lib/redmine/helpers/calendar.rb index ec474025..9b3c0e81 100644 --- a/lib/redmine/helpers/calendar.rb +++ b/lib/redmine/helpers/calendar.rb @@ -68,6 +68,8 @@ module Redmine case Setting.start_of_week.to_i when 1 @first_dow ||= (1 - 1)%7 + 1 + when 6 + @first_dow ||= (6 - 1)%7 + 1 when 7 @first_dow ||= (7 - 1)%7 + 1 else diff --git a/lib/redmine/helpers/diff.rb b/lib/redmine/helpers/diff.rb new file mode 100644 index 00000000..b634d5f1 --- /dev/null +++ b/lib/redmine/helpers/diff.rb @@ -0,0 +1,72 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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. + +module Redmine + module Helpers + class Diff + include ERB::Util + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + attr_reader :diff, :words + + def initialize(content_to, content_from) + @words = content_to.to_s.split(/(\s+)/) + @words = @words.select {|word| word != ' '} + words_from = content_from.to_s.split(/(\s+)/) + words_from = words_from.select {|word| word != ' '} + @diff = words_from.diff @words + end + + def to_html + words = self.words.collect{|word| h(word)} + words_add = 0 + words_del = 0 + dels = 0 + del_off = 0 + diff.diffs.each do |diff| + add_at = nil + add_to = nil + del_at = nil + deleted = "" + diff.each do |change| + pos = change[1] + if change[0] == "+" + add_at = pos + dels unless add_at + add_to = pos + dels + words_add += 1 + else + del_at = pos unless del_at + deleted << ' ' + h(change[2]) + words_del += 1 + end + end + if add_at + words[add_at] = '' + words[add_at] + words[add_to] = words[add_to] + '' + end + if del_at + words.insert del_at - del_off + dels + words_add, '' + deleted + '' + dels += 1 + del_off += words_del + words_del = 0 + end + end + words.join(' ') + end + end + end +end diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index b68534b2..6b891999 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -98,18 +98,11 @@ module Redmine common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months }) end - ### Extracted from the HTML view/helpers # Returns the number of rows that will be rendered on the Gantt chart def number_of_rows return @number_of_rows if @number_of_rows - rows = if @project - number_of_rows_on_project(@project) - else - Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project| - total += number_of_rows_on_project(project) - end - end + rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)} if @max_rows.present? rows > @max_rows ? @max_rows : rows @@ -121,29 +114,10 @@ module Redmine # Returns the number of rows that will be used to list a project on # the Gantt chart. This will recurse for each subproject. def number_of_rows_on_project(project) - # Remove the project requirement for Versions because it will - # restrict issues to only be on the current project. This - # ends up missing issues which are assigned to shared versions. - @query.project = nil if @query.project - - # One Root project + return 0 unless projects.include?(project) count = 1 - # Issues without a Version - count += project.issues.for_gantt.without_version.with_query(@query).count - - # Versions - count += project.versions.count - - # Issues on the Versions - project.versions.each do |version| - count += version.fixed_issues.for_gantt.with_query(@query).count - end - - # Subprojects - project.children.visible.has_module('issue_tracking').each do |subproject| - count += number_of_rows_on_project(subproject) - end - + count += project_issues(project).size + count += project_versions(project).size count end @@ -159,20 +133,60 @@ module Redmine @lines end + # Returns issues that will be rendered + def issues + @issues ||= @query.issues( + :include => [:assigned_to, :tracker, :priority, :category, :fixed_version], + :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC", + :limit => @max_rows + ) + end + + # Return all the project nodes that will be displayed + def projects + return @projects if @projects + + ids = issues.collect(&:project).uniq.collect(&:id) + if ids.any? + # All issues projects and their visible ancestors + @projects = Project.visible.all( + :joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt", + :conditions => ["child.id IN (?)", ids], + :order => "#{Project.table_name}.lft ASC" + ).uniq + else + @projects = [] + end + end + + # Returns the issues that belong to +project+ + def project_issues(project) + @issues_by_project ||= issues.group_by(&:project) + @issues_by_project[project] || [] + end + + # Returns the distinct versions of the issues that belong to +project+ + def project_versions(project) + project_issues(project).collect(&:fixed_version).compact.uniq + end + + # Returns the issues that belong to +project+ and are assigned to +version+ + def version_issues(project, version) + project_issues(project).select {|issue| issue.fixed_version == version} + end + def render(options={}) - options = {:indent => 4, :render => :subject, :format => :html}.merge(options) + options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options) + indent = options[:indent] || 4 @subjects = '' unless options[:only] == :lines @lines = '' unless options[:only] == :subjects @number_of_rows = 0 - if @project - render_project(@project, options) - else - Project.roots.visible.has_module('issue_tracking').each do |project| - render_project(project, options) - break if abort? - end + Project.project_tree(projects) do |project, level| + options[:indent] = indent + level * options[:indent_increment] + render_project(project, options) + break if abort? end @subjects_rendered = true unless options[:only] == :lines @@ -182,10 +196,6 @@ module Redmine end def render_project(project, options={}) - options[:top] = 0 unless options.key? :top - options[:indent_increment] = 20 unless options.key? :indent_increment - options[:top_increment] = 20 unless options.key? :top_increment - subject_for_project(project, options) unless options[:only] == :lines line_for_project(project, options) unless options[:only] == :subjects @@ -194,26 +204,18 @@ module Redmine @number_of_rows += 1 return if abort? - # Second, Issues without a version - issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit) + issues = project_issues(project).select {|i| i.fixed_version.nil?} sort_issues!(issues) if issues render_issues(issues, options) return if abort? end - - # Third, Versions - project.versions.sort.each do |version| - render_version(version, options) - return if abort? + + versions = project_versions(project) + versions.each do |version| + render_version(project, version, options) end - # Fourth, subprojects - project.children.visible.has_module('issue_tracking').each do |project| - render_project(project, options) - return if abort? - end unless project.leaf? - # Remove indent to hit the next sibling options[:indent] -= options[:indent_increment] end @@ -233,7 +235,7 @@ module Redmine options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) end - def render_version(version, options={}) + def render_version(project, version, options={}) # Version header subject_for_version(version, options) unless options[:only] == :lines line_for_version(version, options) unless options[:only] == :subjects @@ -242,12 +244,7 @@ module Redmine @number_of_rows += 1 return if abort? - # Remove the project requirement for Versions because it will - # restrict issues to only be on the current project. This - # ends up missing issues which are assigned to shared versions. - @query.project = nil if @query.project - - issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit) + issues = version_issues(project, version) if issues sort_issues!(issues) # Indent issues @@ -357,11 +354,11 @@ module Redmine subject = "" if issue.assigned_to.present? assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name - subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string) + subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string).to_s end subject << view.link_to_issue(issue) subject << '' - html_subject(options, subject, :css => "issue-subject") + "\n" + html_subject(options, subject, :css => "issue-subject", :title => issue.subject) + "\n" when :image image_subject(options, issue.subject) when :pdf @@ -514,14 +511,22 @@ module Redmine end if Object.const_defined?(:Magick) def to_pdf - pdf = ::Redmine::Export::PDF::IFPDF.new(current_language) + if ( current_language.to_s.downcase == 'ko' || + current_language.to_s.downcase == 'ja' || + current_language.to_s.downcase == 'zh' || + current_language.to_s.downcase == 'zh-tw' || + current_language.to_s.downcase == 'th' ) + pdf = ::Redmine::Export::PDF::IFPDF.new(current_language) + else + pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language) + end pdf.SetTitle("#{l(:label_gantt)} #{project}") - pdf.AliasNbPages + pdf.alias_nb_pages pdf.footer_date = format_date(Date.today) pdf.AddPage("L") pdf.SetFontStyle('B',12) pdf.SetX(15) - pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s) + pdf.RDMCell(PDF::LeftPaneWidth, 20, project.to_s) pdf.Ln pdf.SetFontStyle('B',9) @@ -556,7 +561,7 @@ module Redmine width = ((month_f >> 1) - month_f) * zoom pdf.SetY(y_start) pdf.SetX(left) - pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") + pdf.RDMCell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C") left = left + width month_f = month_f >> 1 end @@ -574,14 +579,14 @@ module Redmine width = (7 - self.date_from.cwday + 1) * zoom-1 pdf.SetY(y_start + header_heigth) pdf.SetX(left) - pdf.Cell(width + 1, height, "", "LTR") + pdf.RDMCell(width + 1, height, "", "LTR") left = left + width+1 end while week_f <= self.date_to width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom pdf.SetY(y_start + header_heigth) pdf.SetX(left) - pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") + pdf.RDMCell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C") left = left + width week_f = week_f+7 end @@ -597,7 +602,7 @@ module Redmine width = zoom pdf.SetY(y_start + 2 * header_heigth) pdf.SetX(left) - pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C") + pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C") left = left + width wday = wday + 1 wday = 1 if wday > 7 @@ -606,7 +611,7 @@ module Redmine pdf.SetY(y_start) pdf.SetX(15) - pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1) + pdf.RDMCell(subject_width+g_width-15, headers_heigth, "", 1) # Tasks top = headers_heigth + y_start @@ -646,10 +651,10 @@ module Redmine end if progress - progress_date = start_date + (end_date - start_date) * (progress / 100.0) + progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0) if progress_date > self.date_from && progress_date > start_date if progress_date < self.date_to - coords[:bar_progress_end] = progress_date - self.date_from + 1 + coords[:bar_progress_end] = progress_date - self.date_from else coords[:bar_progress_end] = self.date_to - self.date_from + 1 end @@ -713,9 +718,10 @@ module Redmine end def html_subject(params, subject, options={}) - output = "
" - output << subject - output << "
" + style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" + style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] + + output = view.content_tag 'div', subject, :class => options[:css], :style => style, :title => options[:title] @subjects << output output end @@ -725,11 +731,11 @@ module Redmine params[:pdf].SetX(15) char_limit = PDF::MaxCharactorsForSubject - params[:indent] - params[:pdf].Cell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") + params[:pdf].RDMCell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR") params[:pdf].SetY(params[:top]) params[:pdf].SetX(params[:subject_width]) - params[:pdf].Cell(params[:g_width], 5, "", "LR") + params[:pdf].RDMCell(params[:g_width], 5, "", "LR") end def image_subject(params, subject, options={}) @@ -786,19 +792,19 @@ module Redmine params[:pdf].SetY(params[:top]+1.5) params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) params[:pdf].SetFillColor(200,200,200) - params[:pdf].Cell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1) + params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1) if coords[:bar_late_end] params[:pdf].SetY(params[:top]+1.5) params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) params[:pdf].SetFillColor(255,100,100) - params[:pdf].Cell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1) + params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1) end if coords[:bar_progress_end] params[:pdf].SetY(params[:top]+1.5) params[:pdf].SetX(params[:subject_width] + coords[:bar_start]) params[:pdf].SetFillColor(90,200,90) - params[:pdf].Cell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1) + params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1) end end # Renders the markers @@ -807,19 +813,19 @@ module Redmine params[:pdf].SetY(params[:top] + 1) params[:pdf].SetX(params[:subject_width] + coords[:start] - 1) params[:pdf].SetFillColor(50,50,200) - params[:pdf].Cell(2, 2, "", 0, 0, "", 1) + params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) end if coords[:end] params[:pdf].SetY(params[:top] + 1) params[:pdf].SetX(params[:subject_width] + coords[:end] - 1) params[:pdf].SetFillColor(50,50,200) - params[:pdf].Cell(2, 2, "", 0, 0, "", 1) + params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1) end end # Renders the label on the right if options[:label] params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5) - params[:pdf].Cell(30, 2, options[:label]) + params[:pdf].RDMCell(30, 2, options[:label]) end end diff --git a/lib/redmine/i18n.rb b/lib/redmine/i18n.rb index 1bfca81e..f0cc529b 100644 --- a/lib/redmine/i18n.rb +++ b/lib/redmine/i18n.rb @@ -45,8 +45,8 @@ module Redmine time = time.to_time if time.is_a?(String) zone = User.current.time_zone local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time) - Setting.time_format.blank? ? ::I18n.l(local, :format => (include_date ? :default : :time)) : - ((include_date ? "#{format_date(time)} " : "") + "#{local.strftime(Setting.time_format)}") + (include_date ? "#{format_date(local)} " : "") + + (Setting.time_format.blank? ? ::I18n.l(local, :format => :time) : local.strftime(Setting.time_format)) end def day_name(day) diff --git a/lib/redmine/mime_type.rb b/lib/redmine/mime_type.rb index c85ebdcb..56618ee7 100644 --- a/lib/redmine/mime_type.rb +++ b/lib/redmine/mime_type.rb @@ -37,6 +37,7 @@ module Redmine 'text/xml' => 'xml,xsd,mxml', 'text/yaml' => 'yml,yaml', 'text/csv' => 'csv', + 'text/x-po' => 'po', 'image/gif' => 'gif', 'image/jpeg' => 'jpg,jpeg,jpe', 'image/png' => 'png', diff --git a/lib/redmine/notifiable.rb b/lib/redmine/notifiable.rb index 71d1ba50..37d4a407 100644 --- a/lib/redmine/notifiable.rb +++ b/lib/redmine/notifiable.rb @@ -14,6 +14,7 @@ module Redmine notifications << Notifiable.new('issue_status_updated', 'issue_updated') notifications << Notifiable.new('issue_priority_updated', 'issue_updated') notifications << Notifiable.new('news_added') + notifications << Notifiable.new('news_comment_added') notifications << Notifiable.new('document_added') notifications << Notifiable.new('file_added') notifications << Notifiable.new('message_posted') diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb index 3442276b..039513aa 100644 --- a/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/lib/redmine/scm/adapters/abstract_adapter.rb @@ -19,10 +19,10 @@ require 'cgi' module Redmine module Scm - module Adapters + module Adapters class CommandFailed < StandardError #:nodoc: end - + class AbstractAdapter #:nodoc: class << self def client_command @@ -34,14 +34,14 @@ module Redmine def client_version [] end - + # Returns the version string of the scm client # Eg: '1.5.0' or 'Unknown version' if unknown def client_version_string v = client_version || 'Unknown version' v.is_a?(Array) ? v.join('.') : v.to_s end - + # Returns true if the current client version is above # or equals the given one # If option is :unknown is set to true, it will return @@ -63,17 +63,18 @@ module Redmine end end - def initialize(url, root_url=nil, login=nil, password=nil) + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) @url = url @login = login if login && !login.empty? @password = (password || "") if @login @root_url = root_url.blank? ? retrieve_root_url : root_url end - + def adapter_name 'Abstract' end - + def supports_cat? true end @@ -81,11 +82,11 @@ module Redmine def supports_annotate? respond_to?('annotate') end - + def root_url @root_url end - + def url @url end @@ -188,11 +189,11 @@ module Redmine def shellout(cmd, &block) self.class.shellout(cmd, &block) end - + def self.logger RAILS_DEFAULT_LOGGER end - + def self.shellout(cmd, &block) logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug? if Rails.env == 'development' @@ -200,7 +201,12 @@ module Redmine cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log" end begin - IO.popen(cmd, "r+") do |io| + if RUBY_VERSION < '1.9' + mode = "r+" + else + mode = "r+:ASCII-8BIT" + end + IO.popen(cmd, mode) do |io| io.close_write block.call(io) if block_given? end @@ -210,8 +216,8 @@ module Redmine logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}") raise CommandFailed.new(msg) end - end - + end + # Hides username/password in a given command def self.strip_credential(cmd) q = (Redmine::Platform.mswin? ? '"' : "'") @@ -221,8 +227,19 @@ module Redmine def strip_credential(cmd) self.class.strip_credential(cmd) end + + def scm_iconv(to, from, str) + return nil if str.nil? + return str if to == from + begin + Iconv.conv(to, from, str) + rescue Iconv::Failure => err + logger.error("failed to convert from #{from} to #{to}. #{err}") + nil + end + end end - + class Entries < Array def sort_by_name sort {|x,y| @@ -231,7 +248,7 @@ module Redmine else x.kind <=> y.kind end - } + } end def revisions @@ -307,29 +324,8 @@ module Redmine def format_identifier identifier end - - def save(repo) - Changeset.transaction do - changeset = Changeset.new( - :repository => repo, - :revision => identifier, - :scmid => scmid, - :committer => author, - :committed_on => time, - :comments => message) - - if changeset.save - paths.each do |file| - Change.create( - :changeset => changeset, - :action => file[:action], - :path => file[:path]) - end - end - end - end end - + class Annotate attr_reader :lines, :revisions diff --git a/lib/redmine/scm/adapters/bazaar_adapter.rb b/lib/redmine/scm/adapters/bazaar_adapter.rb index 1a11f639..b39150de 100644 --- a/lib/redmine/scm/adapters/bazaar_adapter.rb +++ b/lib/redmine/scm/adapters/bazaar_adapter.rb @@ -33,6 +33,28 @@ module Redmine def sq_bin @@sq_bin ||= shell_quote(BZR_BIN) end + + def client_version + @@client_version ||= (scm_command_version || []) + end + + def client_available + !client_version.empty? + end + + def scm_command_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end end # Get info about the repository diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb index c3d3bf72..9827b896 100644 --- a/lib/redmine/scm/adapters/cvs_adapter.rb +++ b/lib/redmine/scm/adapters/cvs_adapter.rb @@ -25,6 +25,9 @@ module Redmine # CVS executable name CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs" + # raised if scm command exited with error, e.g. unknown revision. + class ScmCommandAborted < CommandFailed; end + class << self def client_command @@bin ||= CVS_BIN @@ -33,6 +36,28 @@ module Redmine def sq_bin @@sq_bin ||= shell_quote(CVS_BIN) end + + def client_version + @@client_version ||= (scm_command_version || []) + end + + def client_available + client_version_above?([1, 12]) + end + + def scm_command_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}m) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s + end end # Guidelines for the input: @@ -40,7 +65,8 @@ module Redmine # root_url -> the good old, sometimes damned, CVSROOT # login -> unnecessary # password -> unnecessary too - def initialize(url, root_url=nil, login=nil, password=nil) + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) @url = url @login = login if login && !login.empty? @password = (password || "") if @login @@ -73,39 +99,54 @@ module Redmine logger.debug " entries '#{path}' with identifier '#{identifier}'" path_with_project="#{url}#{with_leading_slash(path)}" entries = Entries.new - cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rls -e" - cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier - cmd << " #{shell_quote path_with_project}" - shellout(cmd) do |io| - io.each_line(){|line| - fields=line.chop.split('/',-1) + cmd_args = %w|rls -e| + cmd_args << "-D" << time_to_cvstime_rlog(identifier) if identifier + cmd_args << path_with_project + scm_cmd(*cmd_args) do |io| + io.each_line() do |line| + fields = line.chop.split('/',-1) logger.debug(">>InspectLine #{fields.inspect}") - if fields[0]!="D" - entries << Entry.new({:name => fields[-5], + time = nil + # Thu Dec 13 16:27:22 2007 + time_l = fields[-3].split(' ') + if time_l.size == 5 && time_l[4].length == 4 + begin + time = Time.parse( + "#{time_l[1]} #{time_l[2]} #{time_l[3]} GMT #{time_l[4]}") + rescue + end + end + entries << Entry.new( + { + :name => fields[-5], #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), :path => "#{path}/#{fields[-5]}", :kind => 'file', :size => nil, - :lastrev => Revision.new({ - :revision => fields[-4], - :name => fields[-4], - :time => Time.parse(fields[-3]), - :author => '' + :lastrev => Revision.new( + { + :revision => fields[-4], + :name => fields[-4], + :time => time, + :author => '' + }) }) - }) else - entries << Entry.new({:name => fields[1], + entries << Entry.new( + { + :name => fields[1], :path => "#{path}/#{fields[1]}", :kind => 'dir', :size => nil, :lastrev => nil - }) + }) end - } + end end - return nil if $? && $?.exitstatus != 0 entries.sort_by_name + rescue ScmCommandAborted + nil end STARTLOG="----------------------------" @@ -118,10 +159,10 @@ module Redmine logger.debug " revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" path_with_project="#{url}#{with_leading_slash(path)}" - cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rlog" - cmd << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from - cmd << " #{shell_quote path_with_project}" - shellout(cmd) do |io| + cmd_args = %w|rlog| + cmd_args << "-d" << ">#{time_to_cvstime_rlog(identifier_from)}" if identifier_from + cmd_args << path_with_project + scm_cmd(*cmd_args) do |io| state="entry_start" commit_log=String.new @@ -234,6 +275,8 @@ module Redmine end end end + rescue ScmCommandAborted + Revisions.new end def diff(path, identifier_from, identifier_to=nil) @@ -315,8 +358,19 @@ module Redmine def normalize_path(path) path.sub(/^(\/)*(.*)/,'\2').sub(/(.*)(,v)+/,'\1') end + + def scm_cmd(*args, &block) + full_args = [CVS_BIN, '-d', root_url] + full_args += args + ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block) + if $? && $?.exitstatus != 0 + raise ScmCommandAborted, "cvs exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :scm_cmd end - + class CvsRevisionHelper attr_accessor :complete_rev, :revision, :base, :branchid @@ -356,7 +410,11 @@ module Redmine private def buildRevision(rev) if rev== 0 - @base + if @branchid.nil? + @base+".0" + else + @base + end elsif @branchid.nil? @base+"."+rev.to_s else diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb index 4eed61e9..92864388 100644 --- a/lib/redmine/scm/adapters/darcs_adapter.rb +++ b/lib/redmine/scm/adapters/darcs_adapter.rb @@ -21,7 +21,7 @@ require 'rexml/document' module Redmine module Scm module Adapters - class DarcsAdapter < AbstractAdapter + class DarcsAdapter < AbstractAdapter # Darcs executable name DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs" @@ -38,8 +38,15 @@ module Redmine @@client_version ||= (darcs_binary_version || []) end + def client_available + !client_version.empty? + end + def darcs_binary_version - darcsversion = darcs_binary_version_from_command_line + darcsversion = darcs_binary_version_from_command_line.dup + if darcsversion.respond_to?(:force_encoding) + darcsversion.force_encoding('ASCII-8BIT') + end if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)}) m[2].scan(%r{\d+}).collect(&:to_i) end @@ -50,7 +57,8 @@ module Redmine end end - def initialize(url, root_url=nil, login=nil, password=nil) + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) @url = url @root_url = url end @@ -73,7 +81,7 @@ module Redmine if path.blank? path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' ) end - entries = Entries.new + entries = Entries.new cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output" cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier cmd << " #{shell_quote path}" @@ -164,7 +172,7 @@ module Redmine if modified_element.elements['modified_how'].text.match(/removed/) return nil end - + Entry.new({:name => element.attributes['name'], :path => path_prefix + element.attributes['name'], :kind => element.name == 'file' ? 'file' : 'dir', @@ -173,7 +181,7 @@ module Redmine :identifier => nil, :scmid => modified_element.elements['patch'].attributes['hash'] }) - }) + }) end def get_paths_for_patch(hash) diff --git a/lib/redmine/scm/adapters/filesystem_adapter.rb b/lib/redmine/scm/adapters/filesystem_adapter.rb index b88892b5..77977d88 100644 --- a/lib/redmine/scm/adapters/filesystem_adapter.rb +++ b/lib/redmine/scm/adapters/filesystem_adapter.rb @@ -23,7 +23,7 @@ require 'find' module Redmine module Scm - module Adapters + module Adapters class FilesystemAdapter < AbstractAdapter class << self @@ -32,8 +32,10 @@ module Redmine end end - def initialize(url, root_url=nil, login=nil, password=nil) + def initialize(url, root_url=nil, login=nil, password=nil, + path_encoding=nil) @url = with_trailling_slash(url) + @path_encoding = path_encoding || 'UTF-8' end def format_path_ends(path, leading=true, trailling=true) @@ -51,47 +53,61 @@ module Redmine rescue CommandFailed return nil end - + def entries(path="", identifier=nil) entries = Entries.new - Dir.new(target(path)).each do |e| - relative_path = format_path_ends((format_path_ends(path, - false, - true) + e), - false,false) - target = target(relative_path) - entries << - Entry.new({ :name => File.basename(e), + trgt_utf8 = target(path) + trgt = scm_iconv(@path_encoding, 'UTF-8', trgt_utf8) + Dir.new(trgt).each do |e1| + e_utf8 = scm_iconv('UTF-8', @path_encoding, e1) + next if e_utf8.blank? + relative_path_utf8 = format_path_ends( + (format_path_ends(path,false,true) + e_utf8),false,false) + t1_utf8 = target(relative_path_utf8) + t1 = scm_iconv(@path_encoding, 'UTF-8', t1_utf8) + relative_path = scm_iconv(@path_encoding, 'UTF-8', relative_path_utf8) + e1 = scm_iconv(@path_encoding, 'UTF-8', e_utf8) + if File.exist?(t1) and # paranoid test + %w{file directory}.include?(File.ftype(t1)) and # avoid special types + not File.basename(e1).match(/^\.+$/) # avoid . and .. + p1 = File.readable?(t1) ? relative_path : "" + utf_8_path = scm_iconv('UTF-8', @path_encoding, p1) + entries << + Entry.new({ :name => scm_iconv('UTF-8', @path_encoding, File.basename(e1)), # below : list unreadable files, but dont link them. - :path => File.readable?(target) ? relative_path : "", - :kind => (File.directory?(target) ? 'dir' : 'file'), - :size => (File.directory?(target) ? nil : [File.size(target)].pack('l').unpack('L').first), + :path => utf_8_path, + :kind => (File.directory?(t1) ? 'dir' : 'file'), + :size => (File.directory?(t1) ? nil : [File.size(t1)].pack('l').unpack('L').first), :lastrev => - Revision.new({:time => (File.mtime(target)).localtime, - }) - }) if File.exist?(target) and # paranoid test - %w{file directory}.include?(File.ftype(target)) and # avoid special types - not File.basename(e).match(/^\.+$/) # avoid . and .. + Revision.new({:time => (File.mtime(t1)) }) + }) + end end entries.sort_by_name + rescue => err + logger.error "scm: filesystem: error: #{err.message}" + raise CommandFailed.new(err.message) end - + def cat(path, identifier=nil) - File.new(target(path), "rb").read + p = scm_iconv(@path_encoding, 'UTF-8', target(path)) + File.new(p, "rb").read + rescue => err + logger.error "scm: filesystem: error: #{err.message}" + raise CommandFailed.new(err.message) end private - + # AbstractAdapter::target is implicitly made to quote paths. # Here we do not shell-out, so we do not want quotes. def target(path=nil) - #Prevent the use of .. + # Prevent the use of .. if path and !path.match(/(^|\/)\.\.(\/|$)/) return "#{self.url}#{without_leading_slash(path)}" end return self.url end - end end end diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb index a241f839..88acc2ed 100644 --- a/lib/redmine/scm/adapters/git_adapter.rb +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -21,9 +21,15 @@ module Redmine module Scm module Adapters class GitAdapter < AbstractAdapter + + SCM_GIT_REPORT_LAST_COMMIT = true + # Git executable name GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" + # raised if scm command exited with error, e.g. unknown revision. + class ScmCommandAborted < CommandFailed; end + class << self def client_command @@bin ||= GIT_BIN @@ -33,9 +39,33 @@ module Redmine @@sq_bin ||= shell_quote(GIT_BIN) end + def client_version + @@client_version ||= (scm_command_version || []) + end + def client_available !client_version.empty? end + + def scm_command_version + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') + end + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s + end + end + + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) + super + @path_encoding = path_encoding || 'UTF-8' + @flag_report_last_commit = SCM_GIT_REPORT_LAST_COMMIT end def info @@ -49,67 +79,79 @@ module Redmine def branches return @branches if @branches @branches = [] - cmd = "#{self.class.sq_bin} --git-dir #{target('')} branch --no-color" - shellout(cmd) do |io| + cmd_args = %w|branch --no-color| + scm_cmd(*cmd_args) do |io| io.each_line do |line| @branches << line.match('\s*\*?\s*(.*)$')[1] end end @branches.sort! + rescue ScmCommandAborted + nil end def tags return @tags if @tags - cmd = "#{self.class.sq_bin} --git-dir #{target('')} tag" - shellout(cmd) do |io| + cmd_args = %w|tag| + scm_cmd(*cmd_args) do |io| @tags = io.readlines.sort!.map{|t| t.strip} end + rescue ScmCommandAborted + nil end def default_branch - branches.include?('master') ? 'master' : branches.first + bras = self.branches + return nil if bras.nil? + bras.include?('master') ? 'master' : bras.first end - + def entries(path=nil, identifier=nil) path ||= '' + p = scm_iconv(@path_encoding, 'UTF-8', path) entries = Entries.new - cmd = "#{self.class.sq_bin} --git-dir #{target('')} ls-tree -l " - cmd << shell_quote("HEAD:" + path) if identifier.nil? - cmd << shell_quote(identifier + ":" + path) if identifier - shellout(cmd) do |io| + cmd_args = %w|ls-tree -l| + cmd_args << "HEAD:#{p}" if identifier.nil? + cmd_args << "#{identifier}:#{p}" if identifier + scm_cmd(*cmd_args) do |io| io.each_line do |line| e = line.chomp.to_s if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/ type = $1 - sha = $2 + sha = $2 size = $3 name = $4 - full_path = path.empty? ? name : "#{path}/#{name}" - entries << Entry.new({:name => name, - :path => full_path, + if name.respond_to?(:force_encoding) + name.force_encoding(@path_encoding) + end + full_path = p.empty? ? name : "#{p}/#{name}" + n = scm_iconv('UTF-8', @path_encoding, name) + full_p = scm_iconv('UTF-8', @path_encoding, full_path) + entries << Entry.new({:name => n, + :path => full_p, :kind => (type == "tree") ? 'dir' : 'file', :size => (type == "tree") ? nil : size, - :lastrev => lastrev(full_path,identifier) + :lastrev => @flag_report_last_commit ? lastrev(full_path, identifier) : Revision.new }) unless entries.detect{|entry| entry.name == name} end end end - return nil if $? && $?.exitstatus != 0 entries.sort_by_name + rescue ScmCommandAborted + nil end - def lastrev(path,rev) + def lastrev(path, rev) return nil if path.nil? - cmd = "#{self.class.sq_bin} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 " - cmd << " #{shell_quote rev} " if rev - cmd << "-- #{shell_quote path} " unless path.empty? + cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| + cmd_args << rev if rev + cmd_args << "--" << path unless path.empty? lines = [] - shellout(cmd) { |io| lines = io.readlines } - return nil if $? && $?.exitstatus != 0 + scm_cmd(*cmd_args) { |io| lines = io.readlines } begin id = lines[0].split[1] author = lines[1].match('Author:\s+(.*)$')[1] - time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]).localtime + time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]) Revision.new({ :identifier => id, @@ -118,30 +160,32 @@ module Redmine :time => time, :message => nil, :paths => nil - }) + }) rescue NoMethodError => e logger.error("The revision '#{path}' has a wrong format") return nil end + rescue ScmCommandAborted + nil end def revisions(path, identifier_from, identifier_to, options={}) revisions = Revisions.new + cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller| + cmd_args << "--reverse" if options[:reverse] + cmd_args << "--all" if options[:all] + cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit] + from_to = "" + from_to << "#{identifier_from}.." if identifier_from + from_to << "#{identifier_to}" if identifier_to + cmd_args << from_to if !from_to.empty? + cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since] + cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty? - cmd = "#{self.class.sq_bin} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller " - cmd << " --reverse " if options[:reverse] - cmd << " --all " if options[:all] - cmd << " -n #{options[:limit].to_i} " if options[:limit] - cmd << "#{shell_quote(identifier_from + '..')}" if identifier_from - cmd << "#{shell_quote identifier_to}" if identifier_to - cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since] - cmd << " -- #{shell_quote path}" if path && !path.empty? - - shellout(cmd) do |io| + scm_cmd *cmd_args do |io| files=[] changeset = {} parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files - revno = 1 io.each_line do |line| if line =~ /^commit ([0-9a-f]{40})$/ @@ -164,7 +208,6 @@ module Redmine end changeset = {} files = [] - revno = revno + 1 end changeset[:commit] = $1 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ @@ -179,23 +222,25 @@ module Redmine parsing_descr = 1 changeset[:description] = "" elsif (parsing_descr == 1 || parsing_descr == 2) \ - && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/ + && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/ parsing_descr = 2 - fileaction = $1 - filepath = $2 - files << {:action => fileaction, :path => filepath} + fileaction = $1 + filepath = $2 + p = scm_iconv('UTF-8', @path_encoding, filepath) + files << {:action => fileaction, :path => p} elsif (parsing_descr == 1 || parsing_descr == 2) \ - && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/ + && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/ parsing_descr = 2 - fileaction = $1 - filepath = $3 - files << {:action => fileaction, :path => filepath} + fileaction = $1 + filepath = $3 + p = scm_iconv('UTF-8', @path_encoding, filepath) + files << {:action => fileaction, :path => p} elsif (parsing_descr == 1) && line.chomp.to_s == "" parsing_descr = 2 elsif (parsing_descr == 1) changeset[:description] << line[4..-1] end - end + end if changeset[:commit] revision = Revision.new({ @@ -214,38 +259,38 @@ module Redmine end end end - - return nil if $? && $?.exitstatus != 0 + revisions + rescue ScmCommandAborted revisions end def diff(path, identifier_from, identifier_to=nil) path ||= '' - + cmd_args = [] if identifier_to - cmd = "#{self.class.sq_bin} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}" + cmd_args << "diff" << "--no-color" << identifier_to << identifier_from else - cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}" + cmd_args << "show" << "--no-color" << identifier_from end - - cmd << " -- #{shell_quote path}" unless path.empty? + cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty? diff = [] - shellout(cmd) do |io| + scm_cmd *cmd_args do |io| io.each_line do |line| diff << line end end - return nil if $? && $?.exitstatus != 0 diff + rescue ScmCommandAborted + nil end - + def annotate(path, identifier=nil) identifier = 'HEAD' if identifier.blank? - cmd = "#{self.class.sq_bin} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}" + cmd_args = %w|blame| + cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path) blame = Annotate.new content = nil - shellout(cmd) { |io| io.binmode; content = io.read } - return nil if $? && $?.exitstatus != 0 + scm_cmd(*cmd_args) { |io| io.binmode; content = io.read } # git annotates binary files return nil if content.is_binary_data? identifier = '' @@ -257,26 +302,32 @@ module Redmine elsif line =~ /^author (.+)/ authors_by_commit[identifier] = $1.strip elsif line =~ /^\t(.*)/ - blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier])) + blame.add_line($1, Revision.new( + :identifier => identifier, + :author => authors_by_commit[identifier])) identifier = '' author = '' end end blame + rescue ScmCommandAborted + nil end - + def cat(path, identifier=nil) if identifier.nil? identifier = 'HEAD' end - cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}" + cmd_args = %w|show --no-color| + cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}" cat = nil - shellout(cmd) do |io| + scm_cmd(*cmd_args) do |io| io.binmode cat = io.read end - return nil if $? && $?.exitstatus != 0 cat + rescue ScmCommandAborted + nil end class Revision < Redmine::Scm::Adapters::Revision @@ -285,6 +336,21 @@ module Redmine identifier[0,8] end end + + def scm_cmd(*args, &block) + repo_path = root_url || url + full_args = [GIT_BIN, '--git-dir', repo_path] + if self.class.client_version_above?([1, 7, 2]) + full_args << '-c' << 'core.quotepath=false' + end + full_args += args + ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block) + if $? && $?.exitstatus != 0 + raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :scm_cmd end end end diff --git a/lib/redmine/scm/adapters/mercurial/redminehelper.py b/lib/redmine/scm/adapters/mercurial/redminehelper.py new file mode 100644 index 00000000..7b3b639f --- /dev/null +++ b/lib/redmine/scm/adapters/mercurial/redminehelper.py @@ -0,0 +1,219 @@ +# redminehelper: Redmine helper extension for Mercurial +# +# Copyright 2010 Alessio Franceschelli (alefranz.net) +# Copyright 2010-2011 Yuya Nishihara +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +"""helper commands for Redmine to reduce the number of hg calls + +To test this extension, please try:: + + $ hg --config extensions.redminehelper=redminehelper.py rhsummary + +I/O encoding: + +:file path: urlencoded, raw string +:tag name: utf-8 +:branch name: utf-8 +:node: 12-digits (short) hex string + +Output example of rhsummary:: + + + + + + + + ... + + + +Output example of rhmanifest:: + + + + + + + ... + + ... + + + +""" +import re, time, cgi, urllib +from mercurial import cmdutil, commands, node, error + +_x = cgi.escape +_u = lambda s: cgi.escape(urllib.quote(s)) + +def _tip(ui, repo): + # see mercurial/commands.py:tip + def tiprev(): + try: + return len(repo) - 1 + except TypeError: # Mercurial < 1.1 + return repo.changelog.count() - 1 + tipctx = repo.changectx(tiprev()) + ui.write('\n' + % (tipctx.rev(), _x(node.short(tipctx.node())))) + +_SPECIAL_TAGS = ('tip',) + +def _tags(ui, repo): + # see mercurial/commands.py:tags + for t, n in reversed(repo.tagslist()): + if t in _SPECIAL_TAGS: + continue + try: + r = repo.changelog.rev(n) + except error.LookupError: + continue + ui.write('\n' + % (r, _x(node.short(n)), _x(t))) + +def _branches(ui, repo): + # see mercurial/commands.py:branches + def iterbranches(): + for t, n in repo.branchtags().iteritems(): + yield t, n, repo.changelog.rev(n) + def branchheads(branch): + try: + return repo.branchheads(branch, closed=False) + except TypeError: # Mercurial < 1.2 + return repo.branchheads(branch) + for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): + if repo.lookup(r) in branchheads(t): + ui.write('\n' + % (r, _x(node.short(n)), _x(t))) + +def _manifest(ui, repo, path, rev): + ctx = repo.changectx(rev) + ui.write('\n' + % (ctx.rev(), _u(path))) + + known = set() + pathprefix = (path.rstrip('/') + '/').lstrip('/') + for f, n in sorted(ctx.manifest().iteritems(), key=lambda e: e[0]): + if not f.startswith(pathprefix): + continue + name = re.sub(r'/.*', '/', f[len(pathprefix):]) + if name in known: + continue + known.add(name) + + if name.endswith('/'): + ui.write('\n' + % _x(urllib.quote(name[:-1]))) + else: + fctx = repo.filectx(f, fileid=n) + tm, tzoffset = fctx.date() + ui.write('\n' + % (_u(name), fctx.rev(), _x(node.short(fctx.node())), + tm, fctx.size(), )) + + ui.write('\n') + +def rhannotate(ui, repo, *pats, **opts): + rev = urllib.unquote_plus(opts.pop('rev', None)) + opts['rev'] = rev + return commands.annotate(ui, repo, *map(urllib.unquote_plus, pats), **opts) + +def rhcat(ui, repo, file1, *pats, **opts): + rev = urllib.unquote_plus(opts.pop('rev', None)) + opts['rev'] = rev + return commands.cat(ui, repo, urllib.unquote_plus(file1), *map(urllib.unquote_plus, pats), **opts) + +def rhdiff(ui, repo, *pats, **opts): + """diff repository (or selected files)""" + change = opts.pop('change', None) + if change: # add -c option for Mercurial<1.1 + base = repo.changectx(change).parents()[0].rev() + opts['rev'] = [str(base), change] + opts['nodates'] = True + return commands.diff(ui, repo, *map(urllib.unquote_plus, pats), **opts) + +def rhlog(ui, repo, *pats, **opts): + rev = opts.pop('rev') + bra0 = opts.pop('branch') + from_rev = urllib.unquote_plus(opts.pop('from', None)) + to_rev = urllib.unquote_plus(opts.pop('to' , None)) + bra = urllib.unquote_plus(opts.pop('rhbranch', None)) + from_rev = from_rev.replace('"', '\\"') + to_rev = to_rev.replace('"', '\\"') + opts['rev'] = ['"%s":"%s"' % (from_rev, to_rev)] + opts['branch'] = [bra] + return commands.log(ui, repo, *map(urllib.unquote_plus, pats), **opts) + +def rhmanifest(ui, repo, path='', **opts): + """output the sub-manifest of the specified directory""" + ui.write('\n') + ui.write('\n') + ui.write('\n' % _u(repo.root)) + try: + _manifest(ui, repo, urllib.unquote_plus(path), urllib.unquote_plus(opts.get('rev'))) + finally: + ui.write('\n') + ui.write('\n') + +def rhsummary(ui, repo, **opts): + """output the summary of the repository""" + ui.write('\n') + ui.write('\n') + ui.write('\n' % _u(repo.root)) + try: + _tip(ui, repo) + _tags(ui, repo) + _branches(ui, repo) + # TODO: bookmarks in core (Mercurial>=1.8) + finally: + ui.write('\n') + ui.write('\n') + +# This extension should be compatible with Mercurial 0.9.5. +# Note that Mercurial 0.9.5 doesn't have extensions.wrapfunction(). +cmdtable = { + 'rhannotate': (rhannotate, + [('r', 'rev', '', 'revision'), + ('u', 'user', None, 'list the author (long with -v)'), + ('n', 'number', None, 'list the revision number (default)'), + ('c', 'changeset', None, 'list the changeset'), + ], + 'hg rhannotate [-r REV] [-u] [-n] [-c] FILE...'), + 'rhcat': (rhcat, + [('r', 'rev', '', 'revision')], + 'hg rhcat ([-r REV] ...) FILE...'), + 'rhdiff': (rhdiff, + [('r', 'rev', [], 'revision'), + ('c', 'change', '', 'change made by revision')], + 'hg rhdiff ([-c REV] | [-r REV] ...) [FILE]...'), + 'rhlog': (rhlog, + [ + ('r', 'rev', [], 'show the specified revision'), + ('b', 'branch', [], + 'show changesets within the given named branch', 'BRANCH'), + ('l', 'limit', '', + 'limit number of changes displayed', 'NUM'), + ('d', 'date', '', + 'show revisions matching date spec', 'DATE'), + ('u', 'user', [], + 'revisions committed by user', 'USER'), + ('', 'from', '', + '', ''), + ('', 'to', '', + '', ''), + ('', 'rhbranch', '', + '', ''), + ('', 'template', '', + 'display with template', 'TEMPLATE')], + 'hg rhlog [OPTION]... [FILE]'), + 'rhmanifest': (rhmanifest, + [('r', 'rev', '', 'show the specified revision')], + 'hg rhmanifest [-r REV] [PATH]'), + 'rhsummary': (rhsummary, [], 'hg rhsummary'), +} diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb index f8f925d4..f39b397a 100644 --- a/lib/redmine/scm/adapters/mercurial_adapter.rb +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb @@ -25,10 +25,14 @@ module Redmine # Mercurial executable name HG_BIN = Redmine::Configuration['scm_mercurial_command'] || "hg" - TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" + HELPERS_DIR = File.dirname(__FILE__) + "/mercurial" + HG_HELPER_EXT = "#{HELPERS_DIR}/redminehelper.py" TEMPLATE_NAME = "hg-template" TEMPLATE_EXTENSION = "tmpl" + # raised if hg command exited with error, e.g. unknown revision. + class HgCommandAborted < CommandFailed; end + class << self def client_command @@bin ||= HG_BIN @@ -50,7 +54,10 @@ module Redmine # The hg version is expressed either as a # release number (eg 0.9.5 or 1.0) or as a revision # id composed of 12 hexa characters. - theversion = hgversion_from_command_line + theversion = hgversion_from_command_line.dup + if theversion.respond_to?(:force_encoding) + theversion.force_encoding('ASCII-8BIT') + end if m = theversion.match(%r{\A(.*?)((\d+\.)+\d+)}) m[2].scan(%r{\d+}).collect(&:to_i) end @@ -70,153 +77,204 @@ module Redmine else ver = "0.9.5" end - "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" + "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" end end + def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil) + super + @path_encoding = path_encoding || 'UTF-8' + end + def info - cmd = "#{self.class.sq_bin} -R #{target('')} root" - root_url = nil - shellout(cmd) do |io| - root_url = io.read - end - return nil if $? && $?.exitstatus != 0 - info = Info.new({:root_url => root_url.chomp, - :lastrev => revisions(nil,nil,nil,{:limit => 1}).last - }) - info - rescue CommandFailed - return nil + tip = summary['repository']['tip'] + Info.new(:root_url => CGI.unescape(summary['repository']['root']), + :lastrev => Revision.new(:revision => tip['revision'], + :scmid => tip['node'])) end + def tags + as_ary(summary['repository']['tag']).map { |e| e['name'] } + end + + # Returns map of {'tag' => 'nodeid', ...} + def tagmap + alist = as_ary(summary['repository']['tag']).map do |e| + e.values_at('name', 'node') + end + Hash[*alist.flatten] + end + + def branches + as_ary(summary['repository']['branch']).map { |e| e['name'] } + end + + # Returns map of {'branch' => 'nodeid', ...} + def branchmap + alist = as_ary(summary['repository']['branch']).map do |e| + e.values_at('name', 'node') + end + Hash[*alist.flatten] + end + + def summary + return @summary if @summary + hg 'rhsummary' do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + @summary = ActiveSupport::XmlMini.parse(output)['rhsummary'] + rescue + end + end + end + private :summary + def entries(path=nil, identifier=nil) - path ||= '' - entries = Entries.new - cmd = "#{self.class.sq_bin} -R #{target('')} --cwd #{target('')} locate" - cmd << " -r #{hgrev(identifier)}" - cmd << " " + shell_quote("path:#{path}") unless path.empty? - shellout(cmd) do |io| - io.each_line do |line| - # HG uses antislashs as separator on Windows - line = line.gsub(/\\/, "/") - if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') - e ||= line - e = e.chomp.split(%r{[\/\\]}) - entries << Entry.new({:name => e.first, - :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), - :kind => (e.size > 1 ? 'dir' : 'file'), - :lastrev => Revision.new - }) unless e.empty? || entries.detect{|entry| entry.name == e.first} - end + p1 = scm_iconv(@path_encoding, 'UTF-8', path) + manifest = hg('rhmanifest', '-r', CGI.escape(hgrev(identifier)), + CGI.escape(without_leading_slash(p1.to_s))) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + ActiveSupport::XmlMini.parse(output)['rhmanifest']['repository']['manifest'] + rescue end end - return nil if $? && $?.exitstatus != 0 - entries.sort_by_name + path_prefix = path.blank? ? '' : with_trailling_slash(path) + + entries = Entries.new + as_ary(manifest['dir']).each do |e| + n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) + p = "#{path_prefix}#{n}" + entries << Entry.new(:name => n, :path => p, :kind => 'dir') + end + + as_ary(manifest['file']).each do |e| + n = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['name'])) + p = "#{path_prefix}#{n}" + lr = Revision.new(:revision => e['revision'], :scmid => e['node'], + :identifier => e['node'], + :time => Time.at(e['time'].to_i)) + entries << Entry.new(:name => n, :path => p, :kind => 'file', + :size => e['size'].to_i, :lastrev => lr) + end + + entries + rescue HgCommandAborted + nil # means not found end - # Fetch the revisions by using a template file that - # makes Mercurial produce a xml output. - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) - revisions = Revisions.new - cmd = "#{self.class.sq_bin} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" - if identifier_from && identifier_to - cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}" - elsif identifier_from - cmd << " -r #{hgrev(identifier_from)}:" - end - cmd << " --limit #{options[:limit].to_i}" if options[:limit] - cmd << " #{shell_quote path}" unless path.blank? - shellout(cmd) do |io| - begin - # HG doesn't close the XML Document... - doc = REXML::Document.new(io.read << "") - doc.elements.each("log/logentry") do |logentry| - paths = [] - copies = logentry.get_elements('paths/path-copied') - logentry.elements.each("paths/path") do |path| - # Detect if the added file is a copy - if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } - from_path = c.attributes['copyfrom-path'] - from_rev = logentry.attributes['revision'] - end - paths << {:action => path.attributes['action'], - :path => "/#{CGI.unescape(path.text)}", - :from_path => from_path ? "/#{CGI.unescape(from_path)}" : nil, - :from_revision => from_rev ? from_rev : nil - } - end - paths.sort! { |x,y| x[:path] <=> y[:path] } + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + revs = Revisions.new + each_revision(path, identifier_from, identifier_to, options) { |e| revs << e } + revs + end - revisions << Revision.new({:identifier => logentry.attributes['revision'], - :scmid => logentry.attributes['node'], - :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), - :time => Time.parse(logentry.elements['date'].text).localtime, - :message => logentry.elements['msg'].text, - :paths => paths - }) - end + # Iterates the revisions by using a template file that + # makes Mercurial produce a xml output. + def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) + hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] + hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" + hg_args << '--limit' << options[:limit] if options[:limit] + hg_args << hgtarget(path) unless path.blank? + log = hg(*hg_args) do |io| + output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end + begin + # Mercurial < 1.5 does not support footer template for '' + ActiveSupport::XmlMini.parse("#{output}")['log'] rescue - logger.debug($!) end end - return nil if $? && $?.exitstatus != 0 - revisions + + as_ary(log['logentry']).each do |le| + cpalist = as_ary(le['paths']['path-copied']).map do |e| + [e['__content__'], e['copyfrom-path']].map do |s| + scm_iconv('UTF-8', @path_encoding, CGI.unescape(s)) + end + end + cpmap = Hash[*cpalist.flatten] + + paths = as_ary(le['paths']['path']).map do |e| + p = scm_iconv('UTF-8', @path_encoding, CGI.unescape(e['__content__']) ) + {:action => e['action'], :path => with_leading_slash(p), + :from_path => (cpmap.member?(p) ? with_leading_slash(cpmap[p]) : nil), + :from_revision => (cpmap.member?(p) ? le['revision'] : nil)} + end.sort { |a, b| a[:path] <=> b[:path] } + + yield Revision.new(:revision => le['revision'], + :scmid => le['node'], + :author => (le['author']['__content__'] rescue ''), + :time => Time.parse(le['date']['__content__']), + :message => le['msg']['__content__'], + :paths => paths) + end + self + end + + # Returns list of nodes in the specified branch + def nodes_in_branch(branch, options={}) + hg_args = ['rhlog', '--template', '{node|short}\n', '--rhbranch', CGI.escape(branch)] + hg_args << '--from' << CGI.escape(branch) + hg_args << '--to' << '0' + hg_args << '--limit' << options[:limit] if options[:limit] + hg(*hg_args) { |io| io.readlines.map { |e| e.chomp } } end def diff(path, identifier_from, identifier_to=nil) - path ||= '' - diff_args = '' - diff = [] + hg_args = %w|rhdiff| if identifier_to - diff_args = "-r #{hgrev(identifier_to)} -r #{hgrev(identifier_from)}" + hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from) else - if self.class.client_version_above?([1, 2]) - diff_args = "-c #{hgrev(identifier_from)}" - else - return [] - end + hg_args << '-c' << hgrev(identifier_from) end - cmd = "#{self.class.sq_bin} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}" - cmd << " -I #{target(path)}" unless path.empty? - shellout(cmd) do |io| + unless path.blank? + p = scm_iconv(@path_encoding, 'UTF-8', path) + hg_args << CGI.escape(hgtarget(p)) + end + diff = [] + hg *hg_args do |io| io.each_line do |line| diff << line end end - return nil if $? && $?.exitstatus != 0 diff + rescue HgCommandAborted + nil # means not found end def cat(path, identifier=nil) - cmd = "#{self.class.sq_bin} -R #{target('')} cat" - cmd << " -r #{hgrev(identifier)}" - cmd << " #{target(path)}" - cat = nil - shellout(cmd) do |io| + p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) + hg 'rhcat', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| io.binmode - cat = io.read + io.read end - return nil if $? && $?.exitstatus != 0 - cat + rescue HgCommandAborted + nil # means not found end def annotate(path, identifier=nil) - path ||= '' - cmd = "#{self.class.sq_bin} -R #{target('')}" - cmd << " annotate -ncu" - cmd << " -r #{hgrev(identifier)}" - cmd << " #{target(path)}" + p = CGI.escape(scm_iconv(@path_encoding, 'UTF-8', path)) blame = Annotate.new - shellout(cmd) do |io| + hg 'rhannotate', '-ncu', '-r', CGI.escape(hgrev(identifier)), hgtarget(p) do |io| io.each_line do |line| + line.force_encoding('ASCII-8BIT') if line.respond_to?(:force_encoding) next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):\s(.*)$} r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3, :identifier => $3) blame.add_line($4.rstrip, r) end end - return nil if $? && $?.exitstatus != 0 blame + rescue HgCommandAborted + nil # means not found or cannot be annotated end class Revision < Redmine::Scm::Adapters::Revision @@ -226,11 +284,40 @@ module Redmine end end + # Runs 'hg' command with the given args + def hg(*args, &block) + repo_path = root_url || url + full_args = [HG_BIN, '-R', repo_path, '--encoding', 'utf-8'] + full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" + full_args << '--config' << 'diff.git=false' + full_args += args + ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block) + if $? && $?.exitstatus != 0 + raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}" + end + ret + end + private :hg + # Returns correct revision identifier - def hgrev(identifier) - shell_quote(identifier.blank? ? 'tip' : identifier.to_s) + def hgrev(identifier, sq=false) + rev = identifier.blank? ? 'tip' : identifier.to_s + rev = shell_quote(rev) if sq + rev end private :hgrev + + def hgtarget(path) + path ||= '' + root_url + '/' + without_leading_slash(path) + end + private :hgtarget + + def as_ary(o) + return [] unless o + o.is_a?(Array) ? o : Array[o] + end + private :as_ary end end end diff --git a/lib/redmine/scm/adapters/subversion_adapter.rb b/lib/redmine/scm/adapters/subversion_adapter.rb index c17d7a5e..59f577e6 100644 --- a/lib/redmine/scm/adapters/subversion_adapter.rb +++ b/lib/redmine/scm/adapters/subversion_adapter.rb @@ -39,17 +39,22 @@ module Redmine @@client_version ||= (svn_binary_version || []) end + def client_available + !client_version.empty? + end + def svn_binary_version - cmd = "#{sq_bin} --version" - version = nil - shellout(cmd) do |io| - # Read svn version in first returned line - if m = io.read.to_s.match(%r{\A(.*?)((\d+\.)+\d+)}) - version = m[2].scan(%r{\d+}).collect(&:to_i) - end + scm_version = scm_version_from_command_line.dup + if scm_version.respond_to?(:force_encoding) + scm_version.force_encoding('ASCII-8BIT') end - return nil if $? && $?.exitstatus != 0 - version + if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)}) + m[2].scan(%r{\d+}).collect(&:to_i) + end + end + + def scm_version_from_command_line + shellout("#{sq_bin} --version") { |io| io.read }.to_s end end @@ -60,6 +65,9 @@ module Redmine info = nil shellout(cmd) do |io| output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end begin doc = ActiveSupport::XmlMini.parse(output) #root_url = doc.elements["info/entry/repository/root"].text @@ -89,6 +97,9 @@ module Redmine cmd << credentials_string shellout(cmd) do |io| output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end begin doc = ActiveSupport::XmlMini.parse(output) each_xml_element(doc['lists']['list'], 'entry') do |entry| @@ -129,6 +140,9 @@ module Redmine properties = {} shellout(cmd) do |io| output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end begin doc = ActiveSupport::XmlMini.parse(output) each_xml_element(doc['properties']['target'], 'property') do |property| @@ -153,6 +167,9 @@ module Redmine cmd << ' ' + target(path) shellout(cmd) do |io| output = io.read + if output.respond_to?(:force_encoding) + output.force_encoding('UTF-8') + end begin doc = ActiveSupport::XmlMini.parse(output) each_xml_element(doc['log'], 'logentry') do |logentry| @@ -183,6 +200,7 @@ module Redmine def diff(path, identifier_from, identifier_to=nil, type="inline") path ||= '' identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' + identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) cmd = "#{self.class.sq_bin} diff -r " diff --git a/lib/redmine/unified_diff.rb b/lib/redmine/unified_diff.rb index 09fbfcf1..f77721d6 100644 --- a/lib/redmine/unified_diff.rb +++ b/lib/redmine/unified_diff.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -17,18 +17,28 @@ module Redmine # Class used to parse unified diffs - class UnifiedDiff < Array + class UnifiedDiff < Array + attr_reader :diff_type + def initialize(diff, options={}) options.assert_valid_keys(:type, :max_lines) diff = diff.split("\n") if diff.is_a?(String) - diff_type = options[:type] || 'inline' - + @diff_type = options[:type] || 'inline' lines = 0 @truncated = false - diff_table = DiffTable.new(diff_type) + diff_table = DiffTable.new(@diff_type) diff.each do |line| + line_encoding = nil + if line.respond_to?(:force_encoding) + line_encoding = line.encoding + # TODO: UTF-16 and Japanese CP932 which is imcompatible with ASCII + # In Japan, diffrence between file path encoding + # and file contents encoding is popular. + line.force_encoding('ASCII-8BIT') + end unless diff_table.add_line line - self << diff_table if diff_table.length > 1 + line.force_encoding(line_encoding) if line_encoding + self << diff_table if diff_table.length > 0 diff_table = DiffTable.new(diff_type) end lines += 1 @@ -40,22 +50,20 @@ module Redmine self << diff_table unless diff_table.empty? self end - + def truncated?; @truncated; end end # Class that represents a file diff - class DiffTable < Hash - attr_reader :file_name, :line_num_l, :line_num_r + class DiffTable < Array + attr_reader :file_name # Initialize with a Diff file and the type of Diff View # The type view must be inline or sbs (side_by_side) def initialize(type="inline") @parsing = false - @nb_line = 1 - @start = false - @before = 'same' - @second = true + @added = 0 + @removed = 0 @type = type end @@ -78,11 +86,21 @@ module Redmine @line_num_l = $2.to_i @line_num_r = $5.to_i else - @nb_line += 1 if parse_line(line, @type) + parse_line(line, @type) end end return true end + + def each_line + prev_line_left, prev_line_right = nil, nil + each do |line| + spacing = prev_line_left && prev_line_right && (line.nb_line_left != prev_line_left+1) && (line.nb_line_right != prev_line_right+1) + yield spacing, line + prev_line_left = line.nb_line_left.to_i if line.nb_line_left.to_i > 0 + prev_line_right = line.nb_line_right.to_i if line.nb_line_right.to_i > 0 + end + end def inspect puts '### DIFF TABLE ###' @@ -92,74 +110,91 @@ module Redmine end end - private - # Test if is a Side By Side type - def sbs?(type, func) - if @start and type == "sbs" - if @before == func and @second - tmp_nb_line = @nb_line - self[tmp_nb_line] = Diff.new - else - @second = false - tmp_nb_line = @start - @start += 1 - @nb_line -= 1 - end - else - tmp_nb_line = @nb_line - @start = @nb_line - self[tmp_nb_line] = Diff.new - @second = true - end - unless self[tmp_nb_line] - @nb_line += 1 - self[tmp_nb_line] = Diff.new - else - self[tmp_nb_line] - end - end + private # Escape the HTML for the diff def escapeHTML(line) CGI.escapeHTML(line) end + + def diff_for_added_line + if @type == 'sbs' && @removed > 0 && @added < @removed + self[-(@removed - @added)] + else + diff = Diff.new + self << diff + diff + end + end def parse_line(line, type="inline") if line[0, 1] == "+" - diff = sbs? type, 'add' - @before = 'add' + diff = diff_for_added_line diff.line_right = escapeHTML line[1..-1] diff.nb_line_right = @line_num_r diff.type_diff_right = 'diff_in' @line_num_r += 1 + @added += 1 true elsif line[0, 1] == "-" - diff = sbs? type, 'remove' - @before = 'remove' + diff = Diff.new diff.line_left = escapeHTML line[1..-1] diff.nb_line_left = @line_num_l diff.type_diff_left = 'diff_out' + self << diff @line_num_l += 1 + @removed += 1 true - elsif line[0, 1] =~ /\s/ - @before = 'same' - @start = false - diff = Diff.new - diff.line_right = escapeHTML line[1..-1] - diff.nb_line_right = @line_num_r - diff.line_left = escapeHTML line[1..-1] - diff.nb_line_left = @line_num_l - self[@nb_line] = diff - @line_num_l += 1 - @line_num_r += 1 - true - elsif line[0, 1] = "\\" + else + write_offsets + if line[0, 1] =~ /\s/ + diff = Diff.new + diff.line_right = escapeHTML line[1..-1] + diff.nb_line_right = @line_num_r + diff.line_left = escapeHTML line[1..-1] + diff.nb_line_left = @line_num_l + self << diff + @line_num_l += 1 + @line_num_r += 1 + true + elsif line[0, 1] = "\\" true else false end end end + + def write_offsets + if @added > 0 && @added == @removed + @added.times do |i| + line = self[-(1 + i)] + removed = (@type == 'sbs') ? line : self[-(1 + @added + i)] + offsets = offsets(removed.line_left, line.line_right) + removed.offsets = line.offsets = offsets + end + end + @added = 0 + @removed = 0 + end + + def offsets(line_left, line_right) + if line_left.present? && line_right.present? && line_left != line_right + max = [line_left.size, line_right.size].min + starting = 0 + while starting < max && line_left[starting] == line_right[starting] + starting += 1 + end + ending = -1 + while ending >= -(max - starting) && line_left[ending] == line_right[ending] + ending -= 1 + end + unless starting == 0 && ending == -1 + [starting, ending] + end + end + end + end # A line of diff class Diff @@ -169,6 +204,7 @@ module Redmine attr_accessor :line_right attr_accessor :type_diff_right attr_accessor :type_diff_left + attr_accessor :offsets def initialize() self.nb_line_left = '' @@ -178,6 +214,38 @@ module Redmine self.type_diff_right = '' self.type_diff_left = '' end + + def type_diff + type_diff_right == 'diff_in' ? type_diff_right : type_diff_left + end + + def line + type_diff_right == 'diff_in' ? line_right : line_left + end + + def html_line_left + if offsets + line_left.dup.insert(offsets.first, '').insert(offsets.last, '') + else + line_left + end + end + + def html_line_right + if offsets + line_right.dup.insert(offsets.first, '').insert(offsets.last, '') + else + line_right + end + end + + def html_line + if offsets + line.dup.insert(offsets.first, '').insert(offsets.last, '') + else + line + end + end def inspect puts '### Start Line Diff ###' diff --git a/lib/redmine/wiki_formatting/textile/formatter.rb b/lib/redmine/wiki_formatting/textile/formatter.rb index 88d52a6c..a32e648e 100644 --- a/lib/redmine/wiki_formatting/textile/formatter.rb +++ b/lib/redmine/wiki_formatting/textile/formatter.rb @@ -43,7 +43,7 @@ module Redmine # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet. # http://code.whytheluckystiff.net/redcloth/changeset/128 def hard_break( text ) - text.gsub!( /(.)\n(?!\n|\Z|>| *([#*=]+(\s|$)|[{|]))/, "\\1
" ) if hard_breaks + text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1
" ) if hard_breaks end # Patch to add code highlighting support to RedCloth diff --git a/lib/redmine/wiki_formatting/textile/helper.rb b/lib/redmine/wiki_formatting/textile/helper.rb index 71996138..f5302ddd 100644 --- a/lib/redmine/wiki_formatting/textile/helper.rb +++ b/lib/redmine/wiki_formatting/textile/helper.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -20,18 +20,15 @@ module Redmine module Textile module Helper def wikitoolbar_for(field_id) + heads_for_wiki_formatter # Is there a simple way to link to a public resource? url = "#{Redmine::Utils.relative_url_root}/help/wiki_syntax.html" - - help_link = l(:setting_text_formatting) + ': ' + - link_to(l(:label_help), url, - :onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;", - :tabindex => -1) + + help_link = link_to(l(:setting_text_formatting), url, + :onclick => "window.open(\"#{ url }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;", + :tabindex => -1) - javascript_include_tag('jstoolbar/jstoolbar') + - javascript_include_tag('jstoolbar/textile') + - javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language.to_s.downcase}") + - javascript_tag("var wikiToolbar = new jsToolBar($('#{field_id}')); wikiToolbar.setHelpLink('#{help_link}'); wikiToolbar.draw();") + javascript_tag("var wikiToolbar = new jsToolBar($('#{field_id}')); wikiToolbar.setHelpLink('#{escape_javascript help_link}'); wikiToolbar.draw();") end def initial_page_content(page) @@ -39,7 +36,15 @@ module Redmine end def heads_for_wiki_formatter - stylesheet_link_tag 'jstoolbar' + unless @heads_for_wiki_formatter_included + content_for :header_tags do + javascript_include_tag('jstoolbar/jstoolbar') + + javascript_include_tag('jstoolbar/textile') + + javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language.to_s.downcase}") + + stylesheet_link_tag('jstoolbar') + end + @heads_for_wiki_formatter_included = true + end end end end diff --git a/lib/tasks/ciphering.rake b/lib/tasks/ciphering.rake new file mode 100644 index 00000000..fec6f468 --- /dev/null +++ b/lib/tasks/ciphering.rake @@ -0,0 +1,35 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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. + + +namespace :db do + desc 'Encrypts SCM and LDAP passwords in the database.' + task :encrypt => :environment do + unless (Repository.encrypt_all(:password) && + AuthSource.encrypt_all(:account_password)) + raise "Some objects could not be saved after encryption, update was rollback'ed." + end + end + + desc 'Decrypts SCM and LDAP passwords in the database.' + task :decrypt => :environment do + unless (Repository.decrypt_all(:password) && + AuthSource.decrypt_all(:account_password)) + raise "Some objects could not be saved after decryption, update was rollback'ed." + end + end +end diff --git a/lib/tasks/locales.rake b/lib/tasks/locales.rake index 51253847..7b8efd47 100644 --- a/lib/tasks/locales.rake +++ b/lib/tasks/locales.rake @@ -1,3 +1,10 @@ +desc 'Updates and checks locales against en.yml' +task :locales do + %w(locales:update locales:check_interpolation).collect do |task| + Rake::Task[task].invoke + end +end + namespace :locales do desc 'Updates language files based on en.yml content (only works for new top level keys).' task :update do @@ -28,6 +35,26 @@ namespace :locales do lang.close end end + + desc 'Checks interpolation arguments in locals against en.yml' + task :check_interpolation do + dir = ENV['DIR'] || './config/locales' + en_strings = YAML.load(File.read(File.join(dir,'en.yml')))['en'] + files = Dir.glob(File.join(dir,'*.{yaml,yml}')) + files.each do |file| + file_strings = YAML.load(File.read(file)) + file_strings = file_strings[file_strings.keys.first] + + file_strings.each do |key, string| + next unless string.is_a?(String) + string.scan /%\{\w+\}/ do |match| + unless en_strings[key].nil? || en_strings[key].include?(match) + puts "#{file}: #{key} uses #{match} not found in en.yml" + end + end + end + end + end desc <<-END_DESC Removes a translation string from all locale file (only works for top-level childless non-multiline keys, probably doesn\'t work on windows). diff --git a/public/images/zoom_in.png b/public/images/zoom_in.png index a3d38574..af4fe074 100644 Binary files a/public/images/zoom_in.png and b/public/images/zoom_in.png differ diff --git a/public/images/zoom_in_g.png b/public/images/zoom_in_g.png deleted file mode 100644 index 260f6dbc..00000000 Binary files a/public/images/zoom_in_g.png and /dev/null differ diff --git a/public/images/zoom_out.png b/public/images/zoom_out.png index 819d32b6..81f28199 100644 Binary files a/public/images/zoom_out.png and b/public/images/zoom_out.png differ diff --git a/public/images/zoom_out_g.png b/public/images/zoom_out_g.png deleted file mode 100644 index 57805935..00000000 Binary files a/public/images/zoom_out_g.png and /dev/null differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 26671685..11940f3c 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -40,12 +40,49 @@ function toggleRowGroup(el) { } } +function collapseAllRowGroups(el) { + var tbody = Element.up(el, 'tbody'); + tbody.childElements('tr').each(function(tr) { + if (tr.hasClassName('group')) { + tr.removeClassName('open'); + } else { + tr.hide(); + } + }) +} + +function expandAllRowGroups(el) { + var tbody = Element.up(el, 'tbody'); + tbody.childElements('tr').each(function(tr) { + if (tr.hasClassName('group')) { + tr.addClassName('open'); + } else { + tr.show(); + } + }) +} + +function toggleAllRowGroups(el) { + var tr = Element.up(el, 'tr'); + if (tr.hasClassName('open')) { + collapseAllRowGroups(el); + } else { + expandAllRowGroups(el); + } +} + function toggleFieldset(el) { var fieldset = Element.up(el, 'fieldset'); fieldset.toggleClassName('collapsed'); Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2}); } +function hideFieldset(el) { + var fieldset = Element.up(el, 'fieldset'); + fieldset.toggleClassName('collapsed'); + fieldset.down('div').hide(); +} + var fileFieldCount = 1; function addFileField() { @@ -249,10 +286,71 @@ function observeProjectModules() { Event.observe('project_enabled_module_names_issue_tracking', 'change', f); } +/* + * Class used to warn user when leaving a page with unsaved textarea + * Author: mathias.fischer@berlinonline.de +*/ -/* shows and hides ajax indicator */ +var WarnLeavingUnsaved = Class.create({ + observedForms: false, + observedElements: false, + changedForms: false, + message: null, + + initialize: function(message){ + this.observedForms = $$('form'); + this.observedElements = $$('textarea'); + this.message = message; + + this.observedElements.each(this.observeChange.bind(this)); + this.observedForms.each(this.submitAction.bind(this)); + + window.onbeforeunload = this.unload.bind(this); + }, + + unload: function(){ + if(this.changedForms) + return this.message; + }, + + setChanged: function(){ + this.changedForms = true; + }, + + setUnchanged: function(){ + this.changedForms = false; + }, + + observeChange: function(element){ + element.observe('change',this.setChanged.bindAsEventListener(this)); + }, + + submitAction: function(element){ + element.observe('submit',this.setUnchanged.bindAsEventListener(this)); + } +}); + +/* + * 1 - registers a callback which copies the csrf token into the + * X-CSRF-Token header with each ajax request. Necessary to + * work with rails applications which have fixed + * CVE-2011-0447 + * 2 - shows and hides ajax indicator + */ Ajax.Responders.register({ - onCreate: function(){ + onCreate: function(request){ + var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; + + if (csrf_meta_tag) { + var header = 'X-CSRF-Token', + token = csrf_meta_tag.readAttribute('content'); + + if (!request.options.requestHeaders) { + request.options.requestHeaders = {}; + } + request.options.requestHeaders[header] = token; + } + if ($('ajax-indicator') && Ajax.activeRequestCount > 0) { Element.show('ajax-indicator'); } diff --git a/public/javascripts/calendar/lang/calendar-fa.js b/public/javascripts/calendar/lang/calendar-fa.js new file mode 100644 index 00000000..01ebbb2a --- /dev/null +++ b/public/javascripts/calendar/lang/calendar-fa.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar FA language +// Author: Behrang Noroozinia, behrangn at g mail +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("یک‌شنبه", + "دوشنبه", + "سه‌شنبه", + "چهارشنبه", + "پنج‌شنبه", + "آدینه", + "شنبه", + "یک‌شنبه"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("یک", + "دو", + "سه", + "چهار", + "پنج", + "آدینه", + "شنبه", + "یک"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// full month names +Calendar._MN = new Array +("ژانویه", + "فوریه", + "مارس", + "آوریل", + "مه", + "ژوئن", + "ژوئیه", + "اوت", + "سپتامبر", + "اکتبر", + "نوامبر", + "دسامبر"); + +// short month names +Calendar._SMN = new Array +("ژان", + "فور", + "مار", + "آور", + "مه", + "ژوئن", + "ژوئیه", + "اوت", + "سپت", + "اکت", + "نوا", + "دسا"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "درباره گاهشمار"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "سال پیشین (برای فهرست نگه دارید)"; +Calendar._TT["PREV_MONTH"] = "ماه پیشین ( برای فهرست نگه دارید)"; +Calendar._TT["GO_TODAY"] = "برو به امروز"; +Calendar._TT["NEXT_MONTH"] = "ماه پسین (برای فهرست نگه دارید)"; +Calendar._TT["NEXT_YEAR"] = "سال پسین (برای فهرست نگه دارید)"; +Calendar._TT["SEL_DATE"] = "گزینش"; +Calendar._TT["DRAG_TO_MOVE"] = "برای جابجایی بکشید"; +Calendar._TT["PART_TODAY"] = " (امروز)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "آغاز هفته از %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "4,5"; + +Calendar._TT["CLOSE"] = "بسته"; +Calendar._TT["TODAY"] = "امروز"; +Calendar._TT["TIME_PART"] = "زدن (با Shift) یا کشیدن برای ویرایش"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "هفته"; +Calendar._TT["TIME"] = "زمان:"; diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index 2e582d96..e81eb0ae 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -11,7 +11,7 @@ ContextMenu.prototype = { if (!observingContextMenuClick) { Event.observe(document, 'click', this.Click.bindAsEventListener(this)); - Event.observe(document, (window.opera ? 'click' : 'contextmenu'), this.RightClick.bindAsEventListener(this)); + Event.observe(document, 'contextmenu', this.RightClick.bindAsEventListener(this)); observingContextMenuClick = true; } @@ -23,8 +23,6 @@ ContextMenu.prototype = { this.hideMenu(); // do not show the context menu on links if (Event.element(e).tagName == 'A') { return; } - // right-click simulated by Alt+Click with Opera - if (window.opera && !e.altKey) { return; } var tr = Event.findElement(e, 'tr'); if (tr == document || tr == undefined || !tr.hasClassName('hascontextmenu')) { return; } Event.stop(e); @@ -39,7 +37,6 @@ ContextMenu.prototype = { Click: function(e) { this.hideMenu(); if (Event.element(e).tagName == 'A') { return; } - if (window.opera && e.altKey) { return; } if (!Event.isRightClick(e) || (navigator.appVersion.match(/\bMSIE\b/))) { var tr = Event.findElement(e, 'tr'); if (tr!=null && tr!=document && tr.hasClassName('hascontextmenu')) { diff --git a/public/javascripts/jstoolbar/lang/jstoolbar-fa.js b/public/javascripts/jstoolbar/lang/jstoolbar-fa.js new file mode 100644 index 00000000..61236bf6 --- /dev/null +++ b/public/javascripts/jstoolbar/lang/jstoolbar-fa.js @@ -0,0 +1,16 @@ +jsToolBar.strings = {}; +jsToolBar.strings['Strong'] = 'پررنگ'; +jsToolBar.strings['Italic'] = 'کج'; +jsToolBar.strings['Underline'] = 'زیرخط'; +jsToolBar.strings['Deleted'] = 'برداشته شده'; +jsToolBar.strings['Code'] = 'کد درون خطی'; +jsToolBar.strings['Heading 1'] = 'سربرگ ۱'; +jsToolBar.strings['Heading 2'] = 'سربرگ ۲'; +jsToolBar.strings['Heading 3'] = 'سربرگ ۳'; +jsToolBar.strings['Unordered list'] = 'فهرست بدون شماره'; +jsToolBar.strings['Ordered list'] = 'فهرست با شماره'; +jsToolBar.strings['Quote'] = 'تو بردن'; +jsToolBar.strings['Unquote'] = 'بیرون آوردن'; +jsToolBar.strings['Preformatted text'] = 'نوشته قالب بندی شده'; +jsToolBar.strings['Wiki link'] = 'پیوند به برگ ویکی'; +jsToolBar.strings['Image'] = 'عکس'; diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 7e5847b1..0c79cd64 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -107,7 +107,8 @@ table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; } table.list td { vertical-align: top; } table.list td.id { width: 2%; text-align: center;} -table.list td.checkbox { width: 15px; padding: 0px;} +table.list td.checkbox { width: 15px; padding: 2px 0 0 0; } +table.list td.checkbox input {padding:0px;} table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; } table.list td.buttons a { padding-right: 0.6em; } table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; } @@ -171,7 +172,7 @@ tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repea tr.version.closed, tr.version.closed a { color: #999; } tr.version td.name { padding-left: 20px; } tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; } -tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; } +tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; } tr.user td { width:13%; } tr.user td.email { width:18%; } @@ -194,6 +195,9 @@ table.plugins span.url { display: block; font-size: 0.9em; } table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; } table.list tbody tr.group span.count { color: #aaa; font-size: 80%; } +tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;} +tr.group:hover a.toggle-all { display:inline;} +a.toggle-all:hover {text-decoration:none;} table.list tbody tr:hover { background-color:#ffffdd; } table.list tbody tr.group:hover { background-color:inherit; } @@ -370,6 +374,8 @@ ul.properties li span {font-style:italic;} #workflow_copy_form select { width: 200px; } +textarea#custom_field_possible_values {width: 99%} + .pagination {font-size: 90%} p.pagination {margin-top:8px;} @@ -424,7 +430,7 @@ input#time_entry_comments { width: 90%;} .tabular.settings textarea { width: 99%; } fieldset.settings label { display: block; } -.parent { padding-left: 20px; } +fieldset#notified_events .parent { padding-left: 20px; } .required {color: #bb0000;} .summary {font-style: italic;} @@ -673,7 +679,16 @@ div.autocomplete ul li span.informal { /***** Diff *****/ .diff_out { background: #fcc; } +.diff_out span { background: #faa; } .diff_in { background: #cfc; } +.diff_in span { background: #afa; } + +.text-diff { +padding: 1em; +background-color:#f6f6f6; +color:#505050; +border: 1px solid #e4e4e4; +} /***** Wiki *****/ div.wiki table { @@ -785,6 +800,7 @@ background-image:url('../images/close_hl.png'); } .gantt_subjects { font-size: 0.8em; } +.gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; } .task { position: absolute; @@ -793,7 +809,7 @@ background-image:url('../images/close_hl.png'); color:#888; padding:0; margin:0; - line-height:0.8em; + line-height:16px; white-space:nowrap; } @@ -950,4 +966,7 @@ h2 img { vertical-align:middle; } #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;} #wiki_add_attachment { display:none; } .hide-when-print { display: none; } + .autoscroll {overflow-x: visible;} + table.list {margin-top:0.5em;} + table.list th, table.list td {border: 1px solid #aaa;} } diff --git a/public/stylesheets/context_menu.css b/public/stylesheets/context_menu.css index f1d69d2f..f86e12e0 100644 --- a/public/stylesheets/context_menu.css +++ b/public/stylesheets/context_menu.css @@ -21,6 +21,7 @@ position:relative; padding:1px; z-index:39; + border:1px solid white; } #context-menu li.folder ul { position:absolute; left:168px; /* IE6 */ top:-2px; max-height:300px; overflow:hidden; overflow-y: auto; } #context-menu li.folder>ul { left:148px; } @@ -30,7 +31,6 @@ #context-menu.reverse-x li.folder>ul { right:148px; } #context-menu a { - border:1px solid white; text-decoration:none !important; background-repeat: no-repeat; background-position: 1px 50%; @@ -40,8 +40,8 @@ #context-menu li>a { width:auto; } /* others */ #context-menu a.disabled, #context-menu a.disabled:hover {color: #ccc;} #context-menu li a.submenu { background:url("../images/bullet_arrow_right.png") right no-repeat; } -#context-menu a:hover { border-color:gray; background-color:#eee; color:#2A5685; } -#context-menu li.folder a:hover { background-color:#eee; } +#context-menu li:hover { border:1px solid gray; background-color:#eee; } +#context-menu a:hover {color:#2A5685;} #context-menu li.folder:hover { z-index:40; } #context-menu ul ul, #context-menu li:hover ul ul { display:none; } #context-menu li:hover ul, #context-menu li:hover li:hover ul { display:block; } diff --git a/public/stylesheets/jstoolbar.css b/public/stylesheets/jstoolbar.css index 9514e3eb..91eae7b5 100644 --- a/public/stylesheets/jstoolbar.css +++ b/public/stylesheets/jstoolbar.css @@ -44,7 +44,8 @@ margin-right: 4px; } -.jstElements .help { float: right; margin-right: 1em; padding-top: 8px; font-size: 0.9em; } +.jstElements .help { float: right; margin-right: 0.5em; padding-top: 8px; font-size: 0.9em; } +.jstElements .help a {padding: 2px 0 2px 20px; background: url(../images/help.png) no-repeat 0 50%;} /* Buttons -------------------------------------------------------- */ diff --git a/public/stylesheets/rtl.css b/public/stylesheets/rtl.css index 5dfe320b..ea6574bc 100644 --- a/public/stylesheets/rtl.css +++ b/public/stylesheets/rtl.css @@ -56,6 +56,7 @@ table.list td.buttons a { padding-right: 20px; } .changeset-changes { direction: ltr; padding-left: 2em } .changesets { direction: ltr; } div#issue-changesets { float: left; margin-right: 1em; margin-left: 0 } +div#issue-changesets div.wiki { direction: ltr; padding-left: 2em } #activity dt, .journal { clear: right; } .journal-link { float: left; } div.wiki pre { direction: ltr; } diff --git a/test/fixtures/diffs/partials.diff b/test/fixtures/diffs/partials.diff new file mode 100644 index 00000000..f745776c --- /dev/null +++ b/test/fixtures/diffs/partials.diff @@ -0,0 +1,46 @@ +--- partials.txt Wed Jan 19 12:06:17 2011 ++++ partials.1.txt Wed Jan 19 12:06:10 2011 +@@ -1,31 +1,31 @@ +-Lorem ipsum dolor sit amet, consectetur adipiscing elit ++Lorem ipsum dolor sit amet, consectetur adipiscing xx + Praesent et sagittis dui. Vivamus ac diam diam +-Ut sed auctor justo ++xxx auctor justo + Suspendisse venenatis sollicitudin magna quis suscipit +-Sed blandit gravida odio ac ultrices ++Sed blandit gxxxxa odio ac ultrices + Morbi rhoncus est ut est aliquam tempus +-Morbi id nisi vel felis tincidunt tempus ++Morbi id nisi vel felis xx tempus + Mauris auctor sagittis ante eu luctus +-Fusce commodo felis sed ligula congue molestie ++Fusce commodo felis sed ligula congue + Lorem ipsum dolor sit amet, consectetur adipiscing elit +-Praesent et sagittis dui. Vivamus ac diam diam ++et sagittis dui. Vivamus ac diam diam + Ut sed auctor justo + Suspendisse venenatis sollicitudin magna quis suscipit + Sed blandit gravida odio ac ultrices + +-Lorem ipsum dolor sit amet, consectetur adipiscing elit +-Praesent et sagittis dui. Vivamus ac diam diam ++Lorem ipsum dolor sit amet, xxxx adipiscing elit + Ut sed auctor justo + Suspendisse venenatis sollicitudin magna quis suscipit + Sed blandit gravida odio ac ultrices +-Morbi rhoncus est ut est aliquam tempus ++Morbi rhoncus est ut est xxxx tempus ++New line + Morbi id nisi vel felis tincidunt tempus + Mauris auctor sagittis ante eu luctus + Fusce commodo felis sed ligula congue molestie + +-Lorem ipsum dolor sit amet, consectetur adipiscing elit +-Praesent et sagittis dui. Vivamus ac diam diam +-Ut sed auctor justo ++Lorem ipsum dolor sit amet, xxxxtetur adipiscing elit ++Praesent et xxxxx. Vivamus ac diam diam ++Ut sed auctor + Suspendisse venenatis sollicitudin magna quis suscipit + Sed blandit gravida odio ac ultrices + Morbi rhoncus est ut est aliquam tempus diff --git a/test/fixtures/issue_statuses.yml b/test/fixtures/issue_statuses.yml index 098ac961..ff40b1c5 100644 --- a/test/fixtures/issue_statuses.yml +++ b/test/fixtures/issue_statuses.yml @@ -1,31 +1,37 @@ --- -issue_statuses_006: - name: Rejected - is_default: false - is_closed: true - id: 6 issue_statuses_001: + id: 1 name: New is_default: true is_closed: false - id: 1 + position: 1 issue_statuses_002: + id: 2 name: Assigned is_default: false is_closed: false - id: 2 + position: 2 issue_statuses_003: + id: 3 name: Resolved is_default: false is_closed: false - id: 3 + position: 3 issue_statuses_004: name: Feedback + id: 4 is_default: false is_closed: false - id: 4 + position: 4 issue_statuses_005: + id: 5 name: Closed is_default: false is_closed: true - id: 5 + position: 5 +issue_statuses_006: + id: 6 + name: Rejected + is_default: false + is_closed: true + position: 6 diff --git a/test/fixtures/journal_details.yml b/test/fixtures/journal_details.yml index 046202dd..8077d7cd 100644 --- a/test/fixtures/journal_details.yml +++ b/test/fixtures/journal_details.yml @@ -20,3 +20,17 @@ journal_details_003: value: "6" prop_key: fixed_version_id journal_id: 4 +journal_details_004: + old_value: "This word was removed and an other was" + property: attr + id: 4 + value: "This word was and an other was added" + prop_key: description + journal_id: 3 +journal_details_005: + old_value: Old value + property: cf + id: 5 + value: New value + prop_key: 2 + journal_id: 3 diff --git a/test/fixtures/repositories/filesystem_repository.tar.gz b/test/fixtures/repositories/filesystem_repository.tar.gz index 7e8a4ac5..075d8c72 100644 Binary files a/test/fixtures/repositories/filesystem_repository.tar.gz and b/test/fixtures/repositories/filesystem_repository.tar.gz differ diff --git a/test/fixtures/repositories/git_repository.tar.gz b/test/fixtures/repositories/git_repository.tar.gz index 58b4df5f..c0a1577f 100644 Binary files a/test/fixtures/repositories/git_repository.tar.gz and b/test/fixtures/repositories/git_repository.tar.gz differ diff --git a/test/fixtures/repositories/mercurial_repository.hg b/test/fixtures/repositories/mercurial_repository.hg index c06844d2..54a8c2ef 100644 Binary files a/test/fixtures/repositories/mercurial_repository.hg and b/test/fixtures/repositories/mercurial_repository.hg differ diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index f26c09c0..1e612fc9 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -4,7 +4,9 @@ users_004: status: 1 last_login_on: language: en - hashed_password: 4e4aeb7baaf0706bd670263fef42dad15763b608 + # password = foo + salt: 3126f764c3c5ac61cbfc103f25f934cf + hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b updated_on: 2006-07-19 19:34:07 +02:00 admin: false mail: rhill@somenet.foo @@ -20,7 +22,9 @@ users_001: status: 1 last_login_on: 2006-07-19 22:57:52 +02:00 language: en - hashed_password: d033e22ae348aeb5660fc2140aec35850c4da997 + # password = admin + salt: 82090c953c4a0000a7db253b0691a6b4 + hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150 updated_on: 2006-07-19 22:57:52 +02:00 admin: true mail: admin@somenet.foo @@ -36,7 +40,9 @@ users_002: status: 1 last_login_on: 2006-07-19 22:42:15 +02:00 language: en - hashed_password: a9a653d4151fa2c081ba1ffc2c2726f3b80b7d7d + # password = jsmith + salt: 67eb4732624d5a7753dcea7ce0bb7d7d + hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc updated_on: 2006-07-19 22:42:15 +02:00 admin: false mail: jsmith@somenet.foo @@ -52,7 +58,9 @@ users_003: status: 1 last_login_on: language: en - hashed_password: 7feb7657aa7a7bf5aef3414a5084875f27192415 + # password = foo + salt: 7599f9963ec07b5a3b55b354407120c0 + hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed updated_on: 2006-07-19 19:33:19 +02:00 admin: false mail: dlopper@somenet.foo @@ -70,7 +78,7 @@ users_005: status: 3 last_login_on: language: en - hashed_password: 7feb7657aa7a7bf5aef3414a5084875f27192415 + hashed_password: 1 updated_on: 2006-07-19 19:33:19 +02:00 admin: false mail: dlopper2@somenet.foo diff --git a/test/functional/auth_sources_controller_test.rb b/test/functional/auth_sources_controller_test.rb index eadbaa73..5f65f419 100644 --- a/test/functional/auth_sources_controller_test.rb +++ b/test/functional/auth_sources_controller_test.rb @@ -66,18 +66,30 @@ class AuthSourcesControllerTest < ActionController::TestCase end context "post :destroy" do + setup do + @auth_source = AuthSource.generate!(:name => 'TestEdit') + end + context "without users" do setup do - @auth_source = AuthSource.generate!(:name => 'TestEdit') post :destroy, :id => @auth_source.id end should_respond_with :redirect should_redirect_to("index") {{:action => 'index'}} should_set_the_flash_to /deletion/i - end - should "be tested with users" + context "with users" do + setup do + User.generate!(:auth_source => @auth_source) + post :destroy, :id => @auth_source.id + end + + should_respond_with :redirect + should "not destroy the AuthSource" do + assert AuthSource.find(@auth_source.id) + end + end end end diff --git a/test/functional/custom_fields_controller_test.rb b/test/functional/custom_fields_controller_test.rb index d1e251b5..5e665ec1 100644 --- a/test/functional/custom_fields_controller_test.rb +++ b/test/functional/custom_fields_controller_test.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -31,6 +31,31 @@ class CustomFieldsControllerTest < ActionController::TestCase @request.session[:user_id] = 1 end + def test_get_new_issue_custom_field + get :new, :type => 'IssueCustomField' + assert_response :success + assert_template 'new' + assert_tag :select, + :attributes => {:name => 'custom_field[field_format]'}, + :child => { + :tag => 'option', + :attributes => {:value => 'user'}, + :content => 'User' + } + assert_tag :select, + :attributes => {:name => 'custom_field[field_format]'}, + :child => { + :tag => 'option', + :attributes => {:value => 'version'}, + :content => 'Version' + } + end + + def test_get_new_with_invalid_custom_field_class_should_redirect_to_list + get :new, :type => 'UnknownCustomField' + assert_redirected_to '/custom_fields' + end + def test_post_new_list_custom_field assert_difference 'CustomField.count' do post :new, :type => "IssueCustomField", @@ -53,9 +78,4 @@ class CustomFieldsControllerTest < ActionController::TestCase assert_equal ["0.1", "0.2"], field.possible_values assert_equal 1, field.trackers.size end - - def test_invalid_custom_field_class_should_redirect_to_list - get :new, :type => 'UnknownCustomField' - assert_redirected_to '/custom_fields' - end end diff --git a/test/functional/groups_controller_test.rb b/test/functional/groups_controller_test.rb index a1a164b0..f45dfc45 100644 --- a/test/functional/groups_controller_test.rb +++ b/test/functional/groups_controller_test.rb @@ -104,4 +104,13 @@ class GroupsControllerTest < ActionController::TestCase post :destroy_membership, :id => 10, :membership_id => 6 end end + + def test_autocomplete_for_user + get :autocomplete_for_user, :id => 10, :q => 'mis' + assert_response :success + users = assigns(:users) + assert_not_nil users + assert users.any? + assert !users.include?(Group.find(10).users.first) + end end diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index b21b28e6..d013b59e 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -139,9 +139,9 @@ class IssuesControllerTest < ActionController::TestCase def test_index_with_project_and_filter get :index, :project_id => 1, :set_filter => 1, - :fields => ['tracker_id'], - :operators => {'tracker_id' => '='}, - :values => {'tracker_id' => ['1']} + :f => ['tracker_id'], + :op => {'tracker_id' => '='}, + :v => {'tracker_id' => ['1']} assert_response :success assert_template 'index.rhtml' assert_not_nil assigns(:issues) @@ -248,7 +248,7 @@ class IssuesControllerTest < ActionController::TestCase def test_index_with_columns columns = ['tracker', 'subject', 'assigned_to'] - get :index, :set_filter => 1, :query => { 'column_names' => columns} + get :index, :set_filter => 1, :c => columns assert_response :success # query should use specified columns @@ -1002,7 +1002,7 @@ class IssuesControllerTest < ActionController::TestCase assert_equal 1, ActionMailer::Base.deliveries.size end - def test_put_update_with_invalid_spent_time + def test_put_update_with_invalid_spent_time_hours_only @request.session[:user_id] = 2 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time' @@ -1015,11 +1015,30 @@ class IssuesControllerTest < ActionController::TestCase assert_response :success assert_template 'edit' - assert_tag :textarea, :attributes => { :name => 'notes' }, - :content => notes + assert_error_tag :descendant => {:content => /Activity can't be blank/} + assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" } end + def test_put_update_with_invalid_spent_time_comments_only + @request.session[:user_id] = 2 + notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time' + + assert_no_difference('Journal.count') do + put :update, + :id => 1, + :notes => notes, + :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""} + end + assert_response :success + assert_template 'edit' + + assert_error_tag :descendant => {:content => /Activity can't be blank/} + assert_error_tag :descendant => {:content => /Hours can't be blank/} + assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes + assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" } + end + def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject issue = Issue.find(2) @request.session[:user_id] = 2 @@ -1072,6 +1091,8 @@ class IssuesControllerTest < ActionController::TestCase assert_response :success assert_template 'bulk_edit' + assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'} + # Project specific custom field, date type field = CustomField.find(9) assert !field.is_for_all? @@ -1089,6 +1110,9 @@ class IssuesControllerTest < ActionController::TestCase assert_response :success assert_template 'bulk_edit' + # Can not set issues from different projects as children of an issue + assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'} + # Project specific custom field, date type field = CustomField.find(9) assert !field.is_for_all? @@ -1178,6 +1202,19 @@ class IssuesControllerTest < ActionController::TestCase issue = Issue.find(1) assert issue.closed? end + + def test_bulk_update_parent_id + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1, 3], + :notes => 'Bulk editing parent', + :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'} + + assert_response 302 + parent = Issue.find(2) + assert_equal parent.id, Issue.find(1).parent_id + assert_equal parent.id, Issue.find(3).parent_id + assert_equal [1, 3], parent.children.collect(&:id).sort + end def test_bulk_update_custom_field @request.session[:user_id] = 2 @@ -1286,6 +1323,18 @@ class IssuesControllerTest < ActionController::TestCase assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6)) end + def test_destroy_parent_and_child_issues + parent = Issue.generate!(:project_id => 1, :tracker_id => 1) + child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id) + assert child.is_descendant_of?(parent.reload) + + @request.session[:user_id] = 2 + assert_difference 'Issue.count', -2 do + post :destroy, :ids => [parent.id, child.id], :todo => 'destroy' + end + assert_response 302 + end + def test_default_search_scope get :index assert_tag :div, :attributes => {:id => 'quick-search'}, diff --git a/test/functional/journals_controller_test.rb b/test/functional/journals_controller_test.rb index ff123f91..fa29744e 100644 --- a/test/functional/journals_controller_test.rb +++ b/test/functional/journals_controller_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -22,7 +22,8 @@ require 'journals_controller' class JournalsController; def rescue_action(e) raise e end; end class JournalsControllerTest < ActionController::TestCase - fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules + fixtures :projects, :users, :members, :member_roles, :roles, :issues, :journals, :journal_details, :enabled_modules, + :trackers, :issue_statuses, :enumerations, :custom_fields, :custom_values, :custom_fields_projects def setup @controller = JournalsController.new @@ -38,6 +39,19 @@ class JournalsControllerTest < ActionController::TestCase assert_equal 'application/atom+xml', @response.content_type end + def test_diff + get :diff, :id => 3, :detail_id => 4 + assert_response :success + assert_template 'diff' + + assert_tag 'span', + :attributes => {:class => 'diff_out'}, + :content => /removed/ + assert_tag 'span', + :attributes => {:class => 'diff_in'}, + :content => /added/ + end + def test_reply_to_issue @request.session[:user_id] = 2 get :new, :id => 6 diff --git a/test/functional/previews_controller_test.rb b/test/functional/previews_controller_test.rb index 45f51f61..c5261d54 100644 --- a/test/functional/previews_controller_test.rb +++ b/test/functional/previews_controller_test.rb @@ -1,3 +1,20 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) class PreviewsControllerTest < ActionController::TestCase @@ -18,6 +35,15 @@ class PreviewsControllerTest < ActionController::TestCase assert_template 'preview' assert_not_nil assigns(:notes) end + + def test_preview_journal_notes_for_update + @request.session[:user_id] = 2 + post :issue, :project_id => '1', :id => 1, :notes => 'Foo' + assert_response :success + assert_template 'preview' + assert_not_nil assigns(:notes) + assert_tag :p, :content => 'Foo' + end def test_news get :news, :project_id => 1, diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index db3f3ab9..8f1f642f 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -288,6 +288,22 @@ class ProjectsControllerTest < ActionController::TestCase end end + def test_create_should_preserve_modules_on_validation_failure + with_settings :default_projects_modules => ['issue_tracking', 'repository'] do + @request.session[:user_id] = 1 + assert_no_difference 'Project.count' do + post :create, :project => { + :name => "blog", + :identifier => "", + :enabled_module_names => %w(issue_tracking news) + } + end + assert_response :success + project = assigns(:project) + assert_equal %w(issue_tracking news), project.enabled_module_names.sort + end + end + def test_create_should_not_accept_get @request.session[:user_id] = 1 get :create diff --git a/test/functional/queries_controller_test.rb b/test/functional/queries_controller_test.rb index 7c5d7ca5..ea3a7ada 100644 --- a/test/functional/queries_controller_test.rb +++ b/test/functional/queries_controller_test.rb @@ -64,9 +64,9 @@ class QueriesControllerTest < ActionController::TestCase :project_id => 'ecookbook', :confirm => '1', :default_columns => '1', - :fields => ["status_id", "assigned_to_id"], - :operators => {"assigned_to_id" => "=", "status_id" => "o"}, - :values => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, + :f => ["status_id", "assigned_to_id"], + :op => {"assigned_to_id" => "=", "status_id" => "o"}, + :v => { "assigned_to_id" => ["1"], "status_id" => ["1"]}, :query => {"name" => "test_new_project_public_query", "is_public" => "1"} q = Query.find_by_name('test_new_project_public_query') @@ -101,7 +101,8 @@ class QueriesControllerTest < ActionController::TestCase :fields => ["status_id", "assigned_to_id"], :operators => {"assigned_to_id" => "=", "status_id" => "o"}, :values => { "assigned_to_id" => ["me"], "status_id" => ["1"]}, - :query => {"name" => "test_new_global_private_query", "is_public" => "1", "column_names" => ["", "tracker", "subject", "priority", "category"]} + :query => {"name" => "test_new_global_private_query", "is_public" => "1"}, + :c => ["", "tracker", "subject", "priority", "category"] q = Query.find_by_name('test_new_global_private_query') assert_redirected_to :controller => 'issues', :action => 'index', :project_id => nil, :query_id => q diff --git a/test/functional/repositories_bazaar_controller_test.rb b/test/functional/repositories_bazaar_controller_test.rb index 5f7de1de..d014c753 100644 --- a/test/functional/repositories_bazaar_controller_test.rb +++ b/test/functional/repositories_bazaar_controller_test.rb @@ -32,9 +32,13 @@ class RepositoriesBazaarControllerTest < ActionController::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new User.current = nil - Repository::Bazaar.create(:project => Project.find(3), :url => REPOSITORY_PATH) + @project = Project.find(3) + @repository = Repository::Bazaar.create( + :project => @project, :url => REPOSITORY_PATH, + :log_encoding => 'UTF-8') + assert @repository end - + if File.directory?(REPOSITORY_PATH) def test_show get :show, :id => 3 @@ -43,7 +47,7 @@ class RepositoriesBazaarControllerTest < ActionController::TestCase assert_not_nil assigns(:entries) assert_not_nil assigns(:changesets) end - + def test_browse_root get :show, :id => 3 assert_response :success @@ -53,7 +57,7 @@ class RepositoriesBazaarControllerTest < ActionController::TestCase assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'} assert assigns(:entries).detect {|e| e.name == 'doc-mkdir.txt' && e.kind == 'file'} end - + def test_browse_directory get :show, :id => 3, :path => ['directory'] assert_response :success @@ -112,12 +116,12 @@ class RepositoriesBazaarControllerTest < ActionController::TestCase get :diff, :id => 3, :rev => 3 assert_response :success assert_template 'diff' - # Line 22 removed + # Line 11 removed assert_tag :tag => 'th', - :content => /2/, + :content => /11/, :sibling => { :tag => 'td', - :attributes => { :class => /diff_in/ }, - :content => /Main purpose/ } + :attributes => { :class => /diff_out/ }, + :content => /Display more information/ } end def test_annotate diff --git a/test/functional/repositories_controller_test.rb b/test/functional/repositories_controller_test.rb index 629be69e..984da66f 100644 --- a/test/functional/repositories_controller_test.rb +++ b/test/functional/repositories_controller_test.rb @@ -22,15 +22,17 @@ require 'repositories_controller' class RepositoriesController; def rescue_action(e) raise e end; end class RepositoriesControllerTest < ActionController::TestCase - fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers - + fixtures :projects, :users, :roles, :members, :member_roles, + :repositories, :issues, :issue_statuses, :changesets, :changes, + :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers + def setup @controller = RepositoriesController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new User.current = nil end - + def test_revisions get :revisions, :id => 1 assert_response :success @@ -44,7 +46,7 @@ class RepositoriesControllerTest < ActionController::TestCase assert_not_nil assigns(:changeset) assert_equal "1", assigns(:changeset).revision end - + def test_revision_with_before_nil_and_afer_normal get :revision, {:id => 1, :rev => 1} assert_response :success @@ -56,24 +58,30 @@ class RepositoriesControllerTest < ActionController::TestCase :child => { :tag => "a", :attributes => { :href => '/projects/ecookbook/repository/revisions/2'} } end - + def test_graph_commits_per_month get :graph, :id => 1, :graph => 'commits_per_month' assert_response :success assert_equal 'image/svg+xml', @response.content_type end - + def test_graph_commits_per_author get :graph, :id => 1, :graph => 'commits_per_author' assert_response :success assert_equal 'image/svg+xml', @response.content_type end - + def test_committers @request.session[:user_id] = 2 # add a commit with an unknown user - Changeset.create!(:repository => Project.find(1).repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.') - + Changeset.create!( + :repository => Project.find(1).repository, + :committer => 'foo', + :committed_on => Time.now, + :revision => 100, + :comments => 'Committed by foo.' + ) + get :committers, :id => 1 assert_response :success assert_template 'committers' @@ -94,8 +102,13 @@ class RepositoriesControllerTest < ActionController::TestCase def test_map_committers @request.session[:user_id] = 2 # add a commit with an unknown user - c = Changeset.create!(:repository => Project.find(1).repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.') - + c = Changeset.create!( + :repository => Project.find(1).repository, + :committer => 'foo', + :committed_on => Time.now, + :revision => 100, + :comments => 'Committed by foo.' + ) assert_no_difference "Changeset.count(:conditions => 'user_id = 3')" do post :committers, :id => 1, :committers => { '0' => ['foo', '2'], '1' => ['dlopper', '3']} assert_redirected_to '/projects/ecookbook/repository/committers' diff --git a/test/functional/repositories_cvs_controller_test.rb b/test/functional/repositories_cvs_controller_test.rb index 5e2a1dc9..8da64eda 100644 --- a/test/functional/repositories_cvs_controller_test.rb +++ b/test/functional/repositories_cvs_controller_test.rb @@ -39,23 +39,14 @@ class RepositoriesCvsControllerTest < ActionController::TestCase User.current = nil @project = Project.find(PRJ_ID) - @repository = Repository::Cvs.create(:project => Project.find(PRJ_ID), - :root_url => REPOSITORY_PATH, - :url => MODULE_NAME) + @repository = Repository::Cvs.create(:project => Project.find(PRJ_ID), + :root_url => REPOSITORY_PATH, + :url => MODULE_NAME, + :log_encoding => 'UTF-8') assert @repository end - + if File.directory?(REPOSITORY_PATH) - def test_show - @repository.fetch_changesets - @repository.reload - get :show, :id => PRJ_ID - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_not_nil assigns(:changesets) - end - def test_browse_root @repository.fetch_changesets @repository.reload @@ -64,14 +55,17 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_template 'show' assert_not_nil assigns(:entries) assert_equal 3, assigns(:entries).size - + entry = assigns(:entries).detect {|e| e.name == 'images'} assert_equal 'dir', entry.kind entry = assigns(:entries).detect {|e| e.name == 'README'} assert_equal 'file', entry.kind + + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end - + def test_browse_directory @repository.fetch_changesets @repository.reload @@ -85,7 +79,7 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path end - + def test_browse_at_given_revision @repository.fetch_changesets @repository.reload @@ -95,7 +89,7 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_not_nil assigns(:entries) assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) end - + def test_entry @repository.fetch_changesets @repository.reload @@ -105,7 +99,7 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_no_tag :tag => 'td', :attributes => { :class => /line-code/}, :content => /before_filter/ end - + def test_entry_at_given_revision # changesets must be loaded @repository.fetch_changesets @@ -117,7 +111,7 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_tag :tag => 'td', :attributes => { :class => /line-code/}, :content => /before_filter/ end - + def test_entry_not_found @repository.fetch_changesets @repository.reload @@ -125,7 +119,7 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, :content => /The entry or revision was not found in the repository/ end - + def test_entry_download @repository.fetch_changesets @repository.reload @@ -142,7 +136,7 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_not_nil assigns(:entry) assert_equal 'sources', assigns(:entry).name end - + def test_diff @repository.fetch_changesets @repository.reload @@ -150,9 +144,27 @@ class RepositoriesCvsControllerTest < ActionController::TestCase assert_response :success assert_template 'diff' assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' }, - :content => /watched.remove_watcher/ + :content => /before_filter :require_login/ assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' }, - :content => /watched.remove_all_watcher/ + :content => /with one change/ + end + + def test_diff_new_files + @repository.fetch_changesets + @repository.reload + get :diff, :id => PRJ_ID, :rev => 1, :type => 'inline' + assert_response :success + assert_template 'diff' + assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_in' }, + :content => /watched.remove_watcher/ + assert_tag :tag => 'th', :attributes => { :class => 'filename' }, + :content => /test\/README/ + assert_tag :tag => 'th', :attributes => { :class => 'filename' }, + :content => /test\/images\/delete.png / + assert_tag :tag => 'th', :attributes => { :class => 'filename' }, + :content => /test\/images\/edit.png/ + assert_tag :tag => 'th', :attributes => { :class => 'filename' }, + :content => /test\/sources\/watchers_controller.rb/ end def test_annotate diff --git a/test/functional/repositories_darcs_controller_test.rb b/test/functional/repositories_darcs_controller_test.rb index 0a5d57a8..12a5053e 100644 --- a/test/functional/repositories_darcs_controller_test.rb +++ b/test/functional/repositories_darcs_controller_test.rb @@ -26,26 +26,25 @@ class RepositoriesDarcsControllerTest < ActionController::TestCase # No '..' in the repository path REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository' + PRJ_ID = 3 def setup @controller = RepositoriesController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new User.current = nil - Repository::Darcs.create(:project => Project.find(3), :url => REPOSITORY_PATH) + @project = Project.find(PRJ_ID) + @repository = Repository::Darcs.create( + :project => @project, :url => REPOSITORY_PATH, + :log_encoding => 'UTF-8') + assert @repository end - + if File.directory?(REPOSITORY_PATH) - def test_show - get :show, :id => 3 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_not_nil assigns(:changesets) - end - def test_browse_root - get :show, :id => 3 + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -54,9 +53,11 @@ class RepositoriesDarcsControllerTest < ActionController::TestCase assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} end - + def test_browse_directory - get :show, :id => 3, :path => ['images'] + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID, :path => ['images'] assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -66,27 +67,31 @@ class RepositoriesDarcsControllerTest < ActionController::TestCase assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path end - + def test_browse_at_given_revision - Project.find(3).repository.fetch_changesets - get :show, :id => 3, :path => ['images'], :rev => 1 + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID, :path => ['images'], :rev => 1 assert_response :success assert_template 'show' assert_not_nil assigns(:entries) assert_equal ['delete.png'], assigns(:entries).collect(&:name) end - + def test_changes - get :changes, :id => 3, :path => ['images', 'edit.png'] + @repository.fetch_changesets + @repository.reload + get :changes, :id => PRJ_ID, :path => ['images', 'edit.png'] assert_response :success assert_template 'changes' assert_tag :tag => 'h2', :content => 'edit.png' end - + def test_diff - Project.find(3).repository.fetch_changesets + @repository.fetch_changesets + @repository.reload # Full diff of changeset 5 - get :diff, :id => 3, :rev => 5 + get :diff, :id => PRJ_ID, :rev => 5 assert_response :success assert_template 'diff' # Line 22 removed diff --git a/test/functional/repositories_filesystem_controller_test.rb b/test/functional/repositories_filesystem_controller_test.rb new file mode 100644 index 00000000..5df88656 --- /dev/null +++ b/test/functional/repositories_filesystem_controller_test.rb @@ -0,0 +1,108 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) +require 'repositories_controller' + +# Re-raise errors caught by the controller. +class RepositoriesController; def rescue_action(e) raise e end; end + +class RepositoriesFilesystemControllerTest < ActionController::TestCase + fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules + + # No '..' in the repository path + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/filesystem_repository' + PRJ_ID = 3 + + def setup + @controller = RepositoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + Setting.enabled_scm << 'Filesystem' unless Setting.enabled_scm.include?('Filesystem') + @repository = Repository::Filesystem.create( + :project => Project.find(PRJ_ID), + :url => REPOSITORY_PATH, + :path_encoding => '' + ) + assert @repository + end + + if File.directory?(REPOSITORY_PATH) + def test_browse_root + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert assigns(:entries).size > 0 + assert_not_nil assigns(:changesets) + assert assigns(:changesets).size == 0 + end + + def test_show_no_extension + get :entry, :id => PRJ_ID, :path => ['test'] + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /TEST CAT/ } + end + + def test_entry_download_no_extension + get :entry, :id => PRJ_ID, :path => ['test'], :format => 'raw' + assert_response :success + assert_equal 'application/octet-stream', @response.content_type + end + + def test_show_non_ascii_contents + with_settings :repositories_encodings => 'UTF-8,EUC-JP' do + get :entry, :id => PRJ_ID, :path => ['japanese', 'euc-jp.txt'] + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '2', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /japanese/ } + end + end + + def test_show_utf16 + with_settings :repositories_encodings => 'UTF-16' do + get :entry, :id => PRJ_ID, :path => ['japanese', 'utf-16.txt'] + assert_response :success + assert_tag :tag => 'th', + :content => '2', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', :content => /japanese/ } + end + end + + def test_show_text_file_should_send_if_too_big + with_settings :file_max_size_displayed => 1 do + get :entry, :id => PRJ_ID, :path => ['japanese', 'big-file.txt'] + assert_response :success + assert_equal 'text/plain', @response.content_type + end + end + else + puts "Filesystem test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff --git a/test/functional/repositories_git_controller_test.rb b/test/functional/repositories_git_controller_test.rb index 86d6035f..469d9b66 100644 --- a/test/functional/repositories_git_controller_test.rb +++ b/test/functional/repositories_git_controller_test.rb @@ -33,20 +33,18 @@ class RepositoriesGitControllerTest < ActionController::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new User.current = nil - @repository = Repository::Git.create(:project => Project.find(3), :url => REPOSITORY_PATH) + @repository = Repository::Git.create( + :project => Project.find(3), + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) assert @repository end if File.directory?(REPOSITORY_PATH) - def test_show - get :show, :id => 3 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_not_nil assigns(:changesets) - end - def test_browse_root + @repository.fetch_changesets + @repository.reload get :show, :id => 3 assert_response :success assert_template 'show' @@ -61,9 +59,13 @@ class RepositoriesGitControllerTest < ActionController::TestCase assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'} assert assigns(:entries).detect {|e| e.name == 'filemane with spaces.txt' && e.kind == 'file'} assert assigns(:entries).detect {|e| e.name == ' filename with a leading space.txt ' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end def test_browse_branch + @repository.fetch_changesets + @repository.reload get :show, :id => 3, :rev => 'test_branch' assert_response :success assert_template 'show' @@ -73,9 +75,30 @@ class RepositoriesGitControllerTest < ActionController::TestCase assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 + end + + def test_browse_tag + @repository.fetch_changesets + @repository.reload + [ + "tag00.lightweight", + "tag01.annotated", + ].each do |t1| + get :show, :id => 3, :rev => t1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assigns(:entries).size > 0 + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 + end end def test_browse_directory + @repository.fetch_changesets + @repository.reload get :show, :id => 3, :path => ['images'] assert_response :success assert_template 'show' @@ -85,14 +108,20 @@ class RepositoriesGitControllerTest < ActionController::TestCase assert_not_nil entry assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end - + def test_browse_at_given_revision + @repository.fetch_changesets + @repository.reload get :show, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518' assert_response :success assert_template 'show' assert_not_nil assigns(:entries) assert_equal ['delete.png'], assigns(:entries).collect(&:name) + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end def test_changes @@ -101,7 +130,7 @@ class RepositoriesGitControllerTest < ActionController::TestCase assert_template 'changes' assert_tag :tag => 'h2', :content => 'edit.png' end - + def test_entry_show get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'] assert_response :success @@ -112,14 +141,14 @@ class RepositoriesGitControllerTest < ActionController::TestCase :attributes => { :class => /line-num/ }, :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } end - + def test_entry_download get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw' assert_response :success # File content assert @response.body.include?('WITHOUT ANY WARRANTY') end - + def test_directory_entry get :entry, :id => 3, :path => ['sources'] assert_response :success @@ -183,7 +212,7 @@ class RepositoriesGitControllerTest < ActionController::TestCase get :annotate, :id => 3, :path => ['images', 'edit.png'] assert_response 500 assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, - :content => /can not be annotated/ + :content => /cannot be annotated/ end def test_revision diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb index ae7f4226..0c638df1 100644 --- a/test/functional/repositories_mercurial_controller_test.rb +++ b/test/functional/repositories_mercurial_controller_test.rb @@ -26,27 +26,45 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase # No '..' in the repository path REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository' + CHAR_1_HEX = "\xc3\x9c" + PRJ_ID = 3 + + ruby19_non_utf8_pass = (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8') def setup @controller = RepositoriesController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new User.current = nil - @repository = Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH) + @repository = Repository::Mercurial.create( + :project => Project.find(PRJ_ID), + :url => REPOSITORY_PATH, + :path_encoding => 'ISO-8859-1' + ) assert @repository + @diff_c_support = true + @char_1 = CHAR_1_HEX.dup + @tag_char_1 = "tag-#{CHAR_1_HEX}-00" + @branch_char_0 = "branch-#{CHAR_1_HEX}-00" + @branch_char_1 = "branch-#{CHAR_1_HEX}-01" + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @tag_char_1.force_encoding('UTF-8') + @branch_char_0.force_encoding('UTF-8') + @branch_char_1.force_encoding('UTF-8') + end end - if File.directory?(REPOSITORY_PATH) - def test_show - get :show, :id => 3 - assert_response :success - assert_template 'show' - assert_not_nil assigns(:entries) - assert_not_nil assigns(:changesets) - end - + if ruby19_non_utf8_pass + puts "TODO: Mercurial functional test fails in Ruby 1.9 " + + "and Encoding.default_external is not UTF-8. " + + "Current value is '#{Encoding.default_external.to_s}'" + def test_fake; assert true end + elsif File.directory?(REPOSITORY_PATH) def test_show_root - get :show, :id => 3 + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -54,10 +72,14 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end def test_show_directory - get :show, :id => 3, :path => ['images'] + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID, :path => ['images'] assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -66,43 +88,108 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase assert_not_nil entry assert_equal 'file', entry.kind assert_equal 'images/edit.png', entry.path + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end def test_show_at_given_revision + @repository.fetch_changesets + @repository.reload [0, '0', '0885933ad4f6'].each do |r1| - get :show, :id => 3, :path => ['images'], :rev => r1 + get :show, :id => PRJ_ID, :path => ['images'], :rev => r1 assert_response :success assert_template 'show' assert_not_nil assigns(:entries) assert_equal ['delete.png'], assigns(:entries).collect(&:name) + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end end def test_show_directory_sql_escape_percent + @repository.fetch_changesets + @repository.reload [13, '13', '3a330eb32958'].each do |r1| - get :show, :id => 3, :path => ['sql_escape', 'percent%dir'], :rev => r1 + get :show, :id => PRJ_ID, :path => ['sql_escape', 'percent%dir'], :rev => r1 assert_response :success assert_template 'show' assert_not_nil assigns(:entries) assert_equal ['percent%file1.txt', 'percentfile1.txt'], assigns(:entries).collect(&:name) changesets = assigns(:changesets) + assert_not_nil changesets + assigns(:changesets).size > 0 + assert_equal %w(13 11 10 9), changesets.collect(&:revision) + end + end - ## This is not yet implemented. - # assert_not_nil changesets - # assert_equal %w(13 11 10 9), changesets.collect(&:revision) + def test_show_directory_latin_1 + @repository.fetch_changesets + @repository.reload + [21, '21', 'adf805632193'].each do |r1| + get :show, :id => PRJ_ID, :path => ['latin-1-dir'], :rev => r1 + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:entries) + assert_equal ["make-latin-1-file.rb", + "test-#{@char_1}-1.txt", + "test-#{@char_1}-2.txt", + "test-#{@char_1}.txt"], assigns(:entries).collect(&:name) + changesets = assigns(:changesets) + assert_not_nil changesets + assert_equal %w(21 20 19 18 17), changesets.collect(&:revision) + end + end + + def test_show_branch + @repository.fetch_changesets + @repository.reload + [ + 'default', + @branch_char_1, + 'branch (1)[2]&,%.-3_4', + @branch_char_0, + 'test_branch.latin-1', + 'test-branch-00', + ].each do |bra| + get :show, :id => PRJ_ID, :rev => bra + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert assigns(:entries).size > 0 + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 + end + end + + def test_show_tag + @repository.fetch_changesets + @repository.reload + [ + @tag_char_1, + 'tag_test.00', + 'tag-init-revision' + ].each do |tag| + get :show, :id => PRJ_ID, :rev => tag + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert assigns(:entries).size > 0 + assert_not_nil assigns(:changesets) + assigns(:changesets).size > 0 end end def test_changes - get :changes, :id => 3, :path => ['images', 'edit.png'] + get :changes, :id => PRJ_ID, :path => ['images', 'edit.png'] assert_response :success assert_template 'changes' assert_tag :tag => 'h2', :content => 'edit.png' end def test_entry_show - get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'] + get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'] assert_response :success assert_template 'entry' # Line 10 @@ -111,33 +198,50 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase :attributes => { :class => 'line-num' }, :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } end + + def test_entry_show_latin_1 + [21, '21', 'adf805632193'].each do |r1| + get :entry, :id => PRJ_ID, :path => ['latin-1-dir', "test-#{@char_1}-2.txt"], :rev => r1 + assert_response :success + assert_template 'entry' + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', + :content => /Mercurial is a distributed version control system/ } + end + end def test_entry_download - get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw' + get :entry, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'], :format => 'raw' assert_response :success # File content assert @response.body.include?('WITHOUT ANY WARRANTY') end + def test_entry_binary_force_download + get :entry, :id => PRJ_ID, :rev => 1, :path => ['images', 'edit.png'] + assert_response :success + assert_equal 'image/png', @response.content_type + end + def test_directory_entry - get :entry, :id => 3, :path => ['sources'] + get :entry, :id => PRJ_ID, :path => ['sources'] assert_response :success assert_template 'show' assert_not_nil assigns(:entry) assert_equal 'sources', assigns(:entry).name end - + def test_diff @repository.fetch_changesets @repository.reload - [4, '4', 'def6d2f1254a'].each do |r1| # Full diff of changeset 4 - get :diff, :id => 3, :rev => r1 + get :diff, :id => PRJ_ID, :rev => r1 assert_response :success assert_template 'diff' - - if @repository.scm.class.client_version_above?([1, 2]) + if @diff_c_support # Line 22 removed assert_tag :tag => 'th', :content => '22', @@ -152,11 +256,10 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase def test_diff_two_revs @repository.fetch_changesets @repository.reload - [2, '400bb8672109', '400', 400].each do |r1| [4, 'def6d2f1254a'].each do |r2| - get :diff, :id => 3, :rev => r1, - :rev_to => r2 + get :diff, :id => PRJ_ID, :rev => r1, + :rev_to => r2 assert_response :success assert_template 'diff' @@ -167,8 +270,21 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase end end + def test_diff_latin_1 + [21, 'adf805632193'].each do |r1| + get :diff, :id => PRJ_ID, :rev => r1 + assert_response :success + assert_template 'diff' + assert_tag :tag => 'th', + :content => '2', + :sibling => { :tag => 'td', + :attributes => { :class => /diff_in/ }, + :content => /It is written in Python/ } + end + end + def test_annotate - get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb'] + get :annotate, :id => PRJ_ID, :path => ['sources', 'watchers_controller.rb'] assert_response :success assert_template 'annotate' # Line 23, revision 4:def6d2f1254a @@ -200,18 +316,51 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase @repository.fetch_changesets @repository.reload [2, '400bb8672109', '400', 400].each do |r1| - get :annotate, :id => 3, :rev => r1, :path => ['sources', 'watchers_controller.rb'] + get :annotate, :id => PRJ_ID, :rev => r1, :path => ['sources', 'watchers_controller.rb'] assert_response :success assert_template 'annotate' assert_tag :tag => 'h2', :content => /@ 2:400bb8672109/ end end + def test_annotate_latin_1 + [21, '21', 'adf805632193'].each do |r1| + get :annotate, :id => PRJ_ID, :path => ['latin-1-dir', "test-#{@char_1}-2.txt"], :rev => r1 + assert_response :success + assert_template 'annotate' + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => + { + :tag => 'td', + :attributes => { :class => 'revision' }, + :child => { :tag => 'a', :content => '20:709858aafd1b' } + } + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => + { + :tag => 'td' , + :content => 'jsmith' , + :attributes => { :class => 'author' }, + + } + assert_tag :tag => 'th', + :content => '1', + :attributes => { :class => 'line-num' }, + :sibling => { :tag => 'td', + :content => /Mercurial is a distributed version control system/ } + + end + end + def test_empty_revision @repository.fetch_changesets @repository.reload ['', ' ', nil].each do |r| - get :revision, :id => 3, :rev => r + get :revision, :id => PRJ_ID, :rev => r assert_response 404 assert_error_tag :content => /was not found/ end diff --git a/test/functional/repositories_subversion_controller_test.rb b/test/functional/repositories_subversion_controller_test.rb index be2b6087..26d15cd5 100644 --- a/test/functional/repositories_subversion_controller_test.rb +++ b/test/functional/repositories_subversion_controller_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -26,17 +26,26 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers + PRJ_ID = 3 + def setup @controller = RepositoriesController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new Setting.default_language = 'en' User.current = nil + + @project = Project.find(PRJ_ID) + @repository = Repository::Subversion.create(:project => @project, + :url => self.class.subversion_repository_url) + assert @repository end if repository_configured?('subversion') def test_show - get :show, :id => 1 + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -44,7 +53,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_browse_root - get :show, :id => 1 + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -53,7 +64,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_browse_directory - get :show, :id => 1, :path => ['subversion_test'] + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID, :path => ['subversion_test'] assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -65,7 +78,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_browse_at_given_revision - get :show, :id => 1, :path => ['subversion_test'], :rev => 4 + @repository.fetch_changesets + @repository.reload + get :show, :id => PRJ_ID, :path => ['subversion_test'], :rev => 4 assert_response :success assert_template 'show' assert_not_nil assigns(:entries) @@ -73,7 +88,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_file_changes - get :changes, :id => 1, :path => ['subversion_test', 'folder', 'helloworld.rb' ] + @repository.fetch_changesets + @repository.reload + get :changes, :id => PRJ_ID, :path => ['subversion_test', 'folder', 'helloworld.rb' ] assert_response :success assert_template 'changes' @@ -93,7 +110,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_directory_changes - get :changes, :id => 1, :path => ['subversion_test', 'folder' ] + @repository.fetch_changesets + @repository.reload + get :changes, :id => PRJ_ID, :path => ['subversion_test', 'folder' ] assert_response :success assert_template 'changes' @@ -103,15 +122,19 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_entry - get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'] + @repository.fetch_changesets + @repository.reload + get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c'] assert_response :success assert_template 'entry' end def test_entry_should_send_if_too_big + @repository.fetch_changesets + @repository.reload # no files in the test repo is larger than 1KB... with_settings :file_max_size_displayed => 0 do - get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'] + get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c'] assert_response :success assert_template '' assert_equal 'attachment; filename="helloworld.c"', @response.headers['Content-Disposition'] @@ -119,7 +142,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_entry_at_given_revision - get :entry, :id => 1, :path => ['subversion_test', 'helloworld.rb'], :rev => 2 + @repository.fetch_changesets + @repository.reload + get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.rb'], :rev => 2 assert_response :success assert_template 'entry' # this line was removed in r3 and file was moved in r6 @@ -128,27 +153,36 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_entry_not_found - get :entry, :id => 1, :path => ['subversion_test', 'zzz.c'] + @repository.fetch_changesets + @repository.reload + get :entry, :id => PRJ_ID, :path => ['subversion_test', 'zzz.c'] assert_tag :tag => 'p', :attributes => { :id => /errorExplanation/ }, :content => /The entry or revision was not found in the repository/ end def test_entry_download - get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'], :format => 'raw' + @repository.fetch_changesets + @repository.reload + get :entry, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c'], :format => 'raw' assert_response :success assert_template '' assert_equal 'attachment; filename="helloworld.c"', @response.headers['Content-Disposition'] end def test_directory_entry - get :entry, :id => 1, :path => ['subversion_test', 'folder'] + @repository.fetch_changesets + @repository.reload + get :entry, :id => PRJ_ID, :path => ['subversion_test', 'folder'] assert_response :success assert_template 'show' assert_not_nil assigns(:entry) assert_equal 'folder', assigns(:entry).name end + # TODO: this test needs fixtures. def test_revision + @repository.fetch_changesets + @repository.reload get :revision, :id => 1, :rev => 2 assert_response :success assert_template 'revision' @@ -167,19 +201,30 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_invalid_revision - get :revision, :id => 1, :rev => 'something_weird' + @repository.fetch_changesets + @repository.reload + get :revision, :id => PRJ_ID, :rev => 'something_weird' + assert_response 404 + assert_error_tag :content => /was not found/ + end + + def test_invalid_revision_diff + get :diff, :id => PRJ_ID, :rev => '1', :rev_to => 'something_weird' assert_response 404 assert_error_tag :content => /was not found/ end def test_empty_revision + @repository.fetch_changesets + @repository.reload ['', ' ', nil].each do |r| - get :revision, :id => 1, :rev => r + get :revision, :id => PRJ_ID, :rev => r assert_response 404 assert_error_tag :content => /was not found/ end end + # TODO: this test needs fixtures. def test_revision_with_repository_pointing_to_a_subdirectory r = Project.find(1).repository # Changes repository url to a subdirectory @@ -203,7 +248,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_revision_diff - get :diff, :id => 1, :rev => 3 + @repository.fetch_changesets + @repository.reload + get :diff, :id => PRJ_ID, :rev => 3 assert_response :success assert_template 'diff' @@ -211,7 +258,9 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_directory_diff - get :diff, :id => 1, :rev => 6, :rev_to => 2, :path => ['subversion_test', 'folder'] + @repository.fetch_changesets + @repository.reload + get :diff, :id => PRJ_ID, :rev => 6, :rev_to => 2, :path => ['subversion_test', 'folder'] assert_response :success assert_template 'diff' @@ -224,13 +273,17 @@ class RepositoriesSubversionControllerTest < ActionController::TestCase end def test_annotate - get :annotate, :id => 1, :path => ['subversion_test', 'helloworld.c'] + @repository.fetch_changesets + @repository.reload + get :annotate, :id => PRJ_ID, :path => ['subversion_test', 'helloworld.c'] assert_response :success assert_template 'annotate' end def test_annotate_at_given_revision - get :annotate, :id => 1, :rev => 8, :path => ['subversion_test', 'helloworld.c'] + @repository.fetch_changesets + @repository.reload + get :annotate, :id => PRJ_ID, :rev => 8, :path => ['subversion_test', 'helloworld.c'] assert_response :success assert_template 'annotate' assert_tag :tag => 'h2', :content => /@ 8/ diff --git a/test/functional/roles_controller_test.rb b/test/functional/roles_controller_test.rb index 810cafda..e132f30d 100644 --- a/test/functional/roles_controller_test.rb +++ b/test/functional/roles_controller_test.rb @@ -114,7 +114,7 @@ class RolesControllerTest < ActionController::TestCase def test_destroy_role_in_use post :destroy, :id => 1 assert_redirected_to '/roles' - assert flash[:error] == 'This role is in use and can not be deleted.' + assert flash[:error] == 'This role is in use and cannot be deleted.' assert_not_nil Role.find_by_id(1) end diff --git a/test/functional/sys_controller_test.rb b/test/functional/sys_controller_test.rb index 6a3da29a..5eda732c 100644 --- a/test/functional/sys_controller_test.rb +++ b/test/functional/sys_controller_test.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -17,12 +17,13 @@ require File.expand_path('../../test_helper', __FILE__) require 'sys_controller' +require 'mocha' # Re-raise errors caught by the controller. class SysController; def rescue_action(e) raise e end; end class SysControllerTest < ActionController::TestCase - fixtures :projects, :repositories + fixtures :projects, :repositories, :enabled_modules def setup @controller = SysController.new @@ -55,11 +56,13 @@ class SysControllerTest < ActionController::TestCase end def test_fetch_changesets + Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true) get :fetch_changesets assert_response :success end def test_fetch_changesets_one_project + Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true) get :fetch_changesets, :id => 'ecookbook' assert_response :success end diff --git a/test/functional/time_entry_reports_controller_test.rb b/test/functional/time_entry_reports_controller_test.rb index 70a3d1c7..b53eebd7 100644 --- a/test/functional/time_entry_reports_controller_test.rb +++ b/test/functional/time_entry_reports_controller_test.rb @@ -4,16 +4,20 @@ require File.expand_path('../../test_helper', __FILE__) class TimeEntryReportsControllerTest < ActionController::TestCase fixtures :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :time_entries, :users, :trackers, :enumerations, :issue_statuses, :custom_fields, :custom_values - def test_report_no_criteria - get :report, :project_id => 1 + def test_report_at_project_level + get :report, :project_id => 'ecookbook' assert_response :success assert_template 'report' + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries/report", :id => 'query_form'} end def test_report_all_projects get :report assert_response :success assert_template 'report' + assert_tag :form, + :attributes => {:action => "/time_entries/report", :id => 'query_form'} end def test_report_all_projects_denied @@ -80,6 +84,8 @@ class TimeEntryReportsControllerTest < ActionController::TestCase assert_template 'report' assert_not_nil assigns(:total_hours) assert_equal "154.25", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/issues/1/time_entries/report", :id => 'query_form'} end def test_report_custom_field_criteria diff --git a/test/functional/timelog_controller_test.rb b/test/functional/timelog_controller_test.rb index 634363dd..669014d8 100644 --- a/test/functional/timelog_controller_test.rb +++ b/test/functional/timelog_controller_test.rb @@ -163,10 +163,12 @@ class TimelogControllerTest < ActionController::TestCase assert_template 'index' assert_not_nil assigns(:total_hours) assert_equal "162.90", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/time_entries", :id => 'query_form'} end def test_index_at_project_level - get :index, :project_id => 1 + get :index, :project_id => 'ecookbook' assert_response :success assert_template 'index' assert_not_nil assigns(:entries) @@ -178,10 +180,12 @@ class TimelogControllerTest < ActionController::TestCase # display all time by default assert_equal '2007-03-12'.to_date, assigns(:from) assert_equal '2007-04-22'.to_date, assigns(:to) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_at_project_level_with_date_range - get :index, :project_id => 1, :from => '2007-03-20', :to => '2007-04-30' + get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30' assert_response :success assert_template 'index' assert_not_nil assigns(:entries) @@ -190,24 +194,30 @@ class TimelogControllerTest < ActionController::TestCase assert_equal "12.90", "%.2f" % assigns(:total_hours) assert_equal '2007-03-20'.to_date, assigns(:from) assert_equal '2007-04-30'.to_date, assigns(:to) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_at_project_level_with_period - get :index, :project_id => 1, :period => '7_days' + get :index, :project_id => 'ecookbook', :period => '7_days' assert_response :success assert_template 'index' assert_not_nil assigns(:entries) assert_not_nil assigns(:total_hours) assert_equal Date.today - 7, assigns(:from) assert_equal Date.today, assigns(:to) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_one_day - get :index, :project_id => 1, :from => "2007-03-23", :to => "2007-03-23" + get :index, :project_id => 'ecookbook', :from => "2007-03-23", :to => "2007-03-23" assert_response :success assert_template 'index' assert_not_nil assigns(:total_hours) assert_equal "4.25", "%.2f" % assigns(:total_hours) + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/time_entries", :id => 'query_form'} end def test_index_at_issue_level @@ -221,6 +231,10 @@ class TimelogControllerTest < ActionController::TestCase # display all time based on what's been logged assert_equal '2007-03-12'.to_date, assigns(:from) assert_equal '2007-04-22'.to_date, assigns(:to) + # TODO: remove /projects/:project_id/issues/:issue_id/time_entries routes + # to use /issues/:issue_id/time_entries + assert_tag :form, + :attributes => {:action => "/projects/ecookbook/issues/1/time_entries", :id => 'query_form'} end def test_index_atom_feed diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index 8aa311eb..107ce106 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -24,7 +24,7 @@ class UsersController; def rescue_action(e) raise e end; end class UsersControllerTest < ActionController::TestCase include Redmine::I18n - fixtures :users, :projects, :members, :member_roles, :roles, :auth_sources, :custom_fields, :custom_values + fixtures :users, :projects, :members, :member_roles, :roles, :auth_sources, :custom_fields, :custom_values, :groups_users def setup @controller = UsersController.new @@ -59,6 +59,15 @@ class UsersControllerTest < ActionController::TestCase assert_equal 'John', users.first.firstname end + def test_index_with_group_filter + get :index, :group_id => '10' + assert_response :success + assert_template 'index' + users = assigns(:users) + assert users.any? + assert_equal([], (users - Group.find(10).users)) + end + def test_show @request.session[:user_id] = nil get :show, :id => 2 diff --git a/test/functional/versions_controller_test.rb b/test/functional/versions_controller_test.rb index 96ab4944..de0e3062 100644 --- a/test/functional/versions_controller_test.rb +++ b/test/functional/versions_controller_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -22,7 +22,7 @@ require 'versions_controller' class VersionsController; def rescue_action(e) raise e end; end class VersionsControllerTest < ActionController::TestCase - fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules + fixtures :projects, :versions, :issues, :users, :roles, :members, :member_roles, :enabled_modules, :issue_statuses def setup @controller = VersionsController.new @@ -145,4 +145,10 @@ class VersionsControllerTest < ActionController::TestCase assert_response :success assert_template '_issue_counts' end + + def test_issue_status_by_status + xhr :get, :status_by, :id => 2, :status_by => 'status' + assert_response :success + assert_template '_issue_counts' + end end diff --git a/test/functional/welcome_controller_test.rb b/test/functional/welcome_controller_test.rb index 7a53c8b6..86542829 100644 --- a/test/functional/welcome_controller_test.rb +++ b/test/functional/welcome_controller_test.rb @@ -67,4 +67,28 @@ class WelcomeControllerTest < ActionController::TestCase assert_equal 'text/plain', @response.content_type assert @response.body.match(%r{^Disallow: /projects/ecookbook/issues\r?$}) end + + def test_warn_on_leaving_unsaved_turn_on + user = User.find(2) + user.pref.warn_on_leaving_unsaved = '1' + user.pref.save! + @request.session[:user_id] = 2 + + get :index + assert_tag 'script', + :attributes => {:type => "text/javascript"}, + :content => %r{new WarnLeavingUnsaved} + end + + def test_warn_on_leaving_unsaved_turn_off + user = User.find(2) + user.pref.warn_on_leaving_unsaved = '0' + user.pref.save! + @request.session[:user_id] = 2 + + get :index + assert_no_tag 'script', + :attributes => {:type => "text/javascript"}, + :content => %r{new WarnLeavingUnsaved} + end end diff --git a/test/functional/wiki_controller_test.rb b/test/functional/wiki_controller_test.rb index c40b2f39..397018f5 100644 --- a/test/functional/wiki_controller_test.rb +++ b/test/functional/wiki_controller_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -155,6 +155,42 @@ class WikiControllerTest < ActionController::TestCase assert_tag :tag => 'input', :attributes => {:id => 'content_version', :value => '1'} end + def test_update_stale_page_should_not_raise_an_error + @request.session[:user_id] = 2 + c = Wiki.find(1).find_page('Another_page').content + c.text = 'Previous text' + c.save! + assert_equal 2, c.version + + assert_no_difference 'WikiPage.count' do + assert_no_difference 'WikiContent.count' do + assert_no_difference 'WikiContent::Version.count' do + put :update, :project_id => 1, + :id => 'Another_page', + :content => { + :comments => 'My comments', + :text => 'Text should not be lost', + :version => 1 + } + end + end + end + assert_response :success + assert_template 'edit' + assert_tag :div, + :attributes => { :class => /error/ }, + :content => /Data has been updated by another user/ + assert_tag 'textarea', + :attributes => { :name => 'content[text]' }, + :content => /Text should not be lost/ + assert_tag 'input', + :attributes => { :name => 'content[comments]', :value => 'My comments' } + + c.reload + assert_equal 'Previous text', c.text + assert_equal 2, c.version + end + def test_preview @request.session[:user_id] = 2 xhr :post, :preview, :project_id => 1, :id => 'CookBook_documentation', @@ -339,6 +375,7 @@ class WikiControllerTest < ActionController::TestCase pages = assigns(:pages) assert_not_nil pages assert_equal Project.find(1).wiki.pages.size, pages.size + assert_equal pages.first.content.updated_on, pages.first.updated_on assert_tag :ul, :attributes => { :class => 'pages-hierarchy' }, :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/CookBook_documentation' }, @@ -350,6 +387,11 @@ class WikiControllerTest < ActionController::TestCase :child => { :tag => 'li', :child => { :tag => 'a', :attributes => { :href => '/projects/ecookbook/wiki/Another_page' }, :content => 'Another page' } } end + + def test_index_should_include_atom_link + get :index, :project_id => 'ecookbook' + assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'} + end context "GET :export" do context "with an authorized user to export the wiki" do @@ -389,6 +431,9 @@ class WikiControllerTest < ActionController::TestCase should_assign_to :pages_by_date should_render_template 'wiki/date_index' + should "include atom link" do + assert_tag 'a', :attributes => { :href => '/projects/ecookbook/activity.atom?show_wiki_edits=1'} + end end def test_not_found diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index 64d606a8..c868e019 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -65,17 +65,17 @@ class WorkflowsControllerTest < ActionController::TestCase # allowed transitions assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[3][]', - :value => '5', + :name => 'issue_status[3][5][]', + :value => 'always', :checked => 'checked' } # not allowed assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[3][]', - :value => '2', + :name => 'issue_status[3][2][]', + :value => 'always', :checked => nil } # unused assert_no_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[4][]' } + :name => 'issue_status[1][1][]' } end def test_get_edit_with_role_and_tracker_and_all_statuses @@ -89,13 +89,17 @@ class WorkflowsControllerTest < ActionController::TestCase assert_equal IssueStatus.count, assigns(:statuses).size assert_tag :tag => 'input', :attributes => { :type => 'checkbox', - :name => 'issue_status[1][]', - :value => '1', + :name => 'issue_status[1][1][]', + :value => 'always', :checked => nil } end def test_post_edit - post :edit, :role_id => 2, :tracker_id => 1, :issue_status => {'4' => ['5'], '3' => ['1', '2']} + post :edit, :role_id => 2, :tracker_id => 1, + :issue_status => { + '4' => {'5' => ['always']}, + '3' => {'1' => ['always'], '2' => ['always']} + } assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' assert_equal 3, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) @@ -103,6 +107,30 @@ class WorkflowsControllerTest < ActionController::TestCase assert_nil Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4}) end + def test_post_edit_with_additional_transitions + post :edit, :role_id => 2, :tracker_id => 1, + :issue_status => { + '4' => {'5' => ['always']}, + '3' => {'1' => ['author'], '2' => ['assignee'], '4' => ['author', 'assignee']} + } + assert_redirected_to '/workflows/edit?role_id=2&tracker_id=1' + + assert_equal 4, Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) + + w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 4, :new_status_id => 5}) + assert ! w.author + assert ! w.assignee + w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 1}) + assert w.author + assert ! w.assignee + w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 2}) + assert ! w.author + assert w.assignee + w = Workflow.find(:first, :conditions => {:role_id => 2, :tracker_id => 1, :old_status_id => 3, :new_status_id => 4}) + assert w.author + assert w.assignee + end + def test_clear_workflow assert Workflow.count(:conditions => {:tracker_id => 1, :role_id => 2}) > 0 diff --git a/test/integration/api_test/users_test.rb b/test/integration/api_test/users_test.rb index e1eb7a23..c460b260 100644 --- a/test/integration/api_test/users_test.rb +++ b/test/integration/api_test/users_test.rb @@ -141,7 +141,7 @@ class ApiTest::UsersTest < ActionController::IntegrationTest assert_response :unprocessable_entity assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Firstname can't be blank"} + assert_tag 'errors', :child => {:tag => 'error', :content => "First name can't be blank"} end end @@ -226,7 +226,7 @@ class ApiTest::UsersTest < ActionController::IntegrationTest assert_response :unprocessable_entity assert_equal 'application/xml', @response.content_type - assert_tag 'errors', :child => {:tag => 'error', :content => "Firstname can't be blank"} + assert_tag 'errors', :child => {:tag => 'error', :content => "First name can't be blank"} end end diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index 80824f6d..56f84716 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -126,4 +126,75 @@ class IssuesTest < ActionController::IntegrationTest :attributes => { :href => '/projects/ecookbook/issues?page=2' } end + + def test_issue_with_user_custom_field + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all) + Role.anonymous.add_permission! :add_issues, :edit_issues + users = Project.find(1).users + tester = users.first + + # Issue form + get '/projects/ecookbook/issues/new' + assert_response :success + assert_tag :select, + :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, + :children => {:count => (users.size + 1)}, # +1 for blank value + :child => { + :tag => 'option', + :attributes => {:value => tester.id.to_s}, + :content => tester.name + } + + # Create issue + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', + :issue => { + :tracker_id => '1', + :priority_id => '4', + :subject => 'Issue with user custom field', + :custom_field_values => {@field.id.to_s => users.first.id.to_s} + } + end + issue = Issue.first(:order => 'id DESC') + assert_response 302 + + # Issue view + follow_redirect! + assert_tag :th, + :content => /Tester/, + :sibling => { + :tag => 'td', + :content => tester.name + } + assert_tag :select, + :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, + :children => {:count => (users.size + 1)}, # +1 for blank value + :child => { + :tag => 'option', + :attributes => {:value => tester.id.to_s, :selected => 'selected'}, + :content => tester.name + } + + # Update issue + new_tester = users[1] + assert_difference 'Journal.count' do + put "/issues/#{issue.id}", + :notes => 'Updating custom field', + :issue => { + :custom_field_values => {@field.id.to_s => new_tester.id.to_s} + } + end + assert_response 302 + + # Issue view + follow_redirect! + assert_tag :content => 'Tester', + :ancestor => {:tag => 'ul', :attributes => {:class => /details/}}, + :sibling => { + :content => tester.name, + :sibling => { + :content => new_tester.name + } + } + end end diff --git a/test/integration/layout_test.rb b/test/integration/layout_test.rb index cb637160..dc406871 100644 --- a/test/integration/layout_test.rb +++ b/test/integration/layout_test.rb @@ -21,4 +21,29 @@ class LayoutTest < ActionController::IntegrationTest assert_response :forbidden assert_select "#admin-menu", :count => 0 end + + def test_top_menu_and_search_not_visible_when_login_required + with_settings :login_required => '1' do + get '/' + assert_select "#top-menu > ul", 0 + assert_select "#quick-search", 0 + end + end + + def test_top_menu_and_search_visible_when_login_not_required + with_settings :login_required => '0' do + get '/' + assert_select "#top-menu > ul" + assert_select "#quick-search" + end + end + + def test_wiki_formatter_header_tags + Role.anonymous.add_permission! :add_issues + + get '/projects/ecookbook/issues/new' + assert_tag :script, + :attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}}, + :parent => {:tag => 'head'} + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 1ff99cac..c5891bcf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -87,6 +87,7 @@ class ActiveSupport::TestCase saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].dup; h} options.each {|k, v| Setting[k] = v} yield + ensure saved_settings.each {|k, v| Setting[k] = v} end @@ -109,6 +110,13 @@ class ActiveSupport::TestCase File.join(RAILS_ROOT.gsub(%r{config\/\.\.}, ''), "/tmp/test/#{vendor.downcase}_repository") end + # Returns the url of the subversion test repository + def self.subversion_repository_url + path = repository_path('subversion') + path = '/' + path unless path.starts_with?('/') + "file://#{path}" + end + # Returns true if the +vendor+ test repository is configured def self.repository_configured?(vendor) File.directory?(repository_path(vendor)) @@ -416,7 +424,7 @@ class ActiveSupport::TestCase # 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)) + assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) end end diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb index 2f0415d3..80fb429a 100644 --- a/test/unit/changeset_test.rb +++ b/test/unit/changeset_test.rb @@ -72,24 +72,40 @@ class ChangesetTest < ActiveSupport::TestCase Setting.commit_ref_keywords = '*' Setting.commit_logtime_enabled = '1' - c = Changeset.new(:repository => Project.find(1).repository, - :committed_on => 24.hours.ago, - :comments => 'Worked on this issue #1 @2h', - :revision => '520', - :user => User.find(2)) - assert_difference 'TimeEntry.count' do - c.scan_comment_for_issue_ids + { + '2' => 2.0, + '2h' => 2.0, + '2hours' => 2.0, + '15m' => 0.25, + '15min' => 0.25, + '3h15' => 3.25, + '3h15m' => 3.25, + '3h15min' => 3.25, + '3:15' => 3.25, + '3.25' => 3.25, + '3.25h' => 3.25, + '3,25' => 3.25, + '3,25h' => 3.25, + }.each do |syntax, expected_hours| + c = Changeset.new(:repository => Project.find(1).repository, + :committed_on => 24.hours.ago, + :comments => "Worked on this issue #1 @#{syntax}", + :revision => '520', + :user => User.find(2)) + assert_difference 'TimeEntry.count' do + c.scan_comment_for_issue_ids + end + assert_equal [1], c.issue_ids.sort + + time = TimeEntry.first(:order => 'id desc') + assert_equal 1, time.issue_id + assert_equal 1, time.project_id + assert_equal 2, time.user_id + assert_equal expected_hours, time.hours, "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}" + assert_equal Date.yesterday, time.spent_on + assert time.activity.is_default? + assert time.comments.include?('r520'), "r520 was expected in time_entry comments: #{time.comments}" end - assert_equal [1], c.issue_ids.sort - - time = TimeEntry.first(:order => 'id desc') - assert_equal 1, time.issue_id - assert_equal 1, time.project_id - assert_equal 2, time.user_id - assert_equal 2.0, time.hours - assert_equal Date.yesterday, time.spent_on - assert time.activity.is_default? - assert time.comments.include?('r520'), "r520 was expected in time_entry comments: #{time.comments}" end def test_ref_keywords_closing_with_timelog @@ -100,7 +116,7 @@ class ChangesetTest < ActiveSupport::TestCase c = Changeset.new(:repository => Project.find(1).repository, :committed_on => Time.now, - :comments => 'This is a comment. Fixes #1 @2.5, #2 @1', + :comments => 'This is a comment. Fixes #1 @4.5, #2 @1', :user => User.find(2)) assert_difference 'TimeEntry.count', 2 do c.scan_comment_for_issue_ids @@ -159,24 +175,28 @@ class ChangesetTest < ActiveSupport::TestCase def test_commit_referencing_a_parent_project_issue # repository of child project - r = Repository::Subversion.create!(:project => Project.find(3), :url => 'svn://localhost/test') - + r = Repository::Subversion.create!( + :project => Project.find(3), + :url => 'svn://localhost/test') + c = Changeset.new(:repository => r, :committed_on => Time.now, :comments => 'refs #2, an issue of a parent project') c.scan_comment_for_issue_ids - + assert_equal [2], c.issue_ids.sort assert c.issues.first.project != c.project end - + def test_text_tag_revision c = Changeset.new(:revision => '520') assert_equal 'r520', c.text_tag end - + def test_text_tag_hash - c = Changeset.new(:scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518', :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518') + c = Changeset.new( + :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518', + :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518') assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag end @@ -204,19 +224,67 @@ class ChangesetTest < ActiveSupport::TestCase changeset = Changeset.find_by_revision('10') assert_nil changeset.next end - + def test_comments_should_be_converted_to_utf8 - with_settings :commit_logs_encoding => 'ISO-8859-1' do - c = Changeset.new - c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + proj = Project.find(3) + str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + r = Repository::Bazaar.create!( + :project => proj, :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-8859-1' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => str) + assert( c.save ) assert_equal "Texte encodé en ISO-8859-1.", c.comments - end end - + def test_invalid_utf8_sequences_in_comments_should_be_stripped - c = Changeset.new - c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") - assert_equal "Texte encod en ISO-8859-1.", c.comments + proj = Project.find(3) + str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + r = Repository::Bazaar.create!( + :project => proj, :url => '/tmp/test/bazaar', + :log_encoding => 'UTF-8' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => str) + assert( c.save ) + if str.respond_to?(:force_encoding) + assert_equal "Texte encod? en ISO-8859-1.", c.comments + else + assert_equal "Texte encod en ISO-8859-1.", c.comments + end + end + + def test_comments_should_be_converted_all_latin1_to_utf8 + s1 = "\xC2\x80" + s2 = "\xc3\x82\xc2\x80" + if s1.respond_to?(:force_encoding) + s3 = s1.dup + s4 = s2.dup + s1.force_encoding('ASCII-8BIT') + s2.force_encoding('ASCII-8BIT') + s3.force_encoding('ISO-8859-1') + s4.force_encoding('UTF-8') + assert_equal s3.encode('UTF-8'), s4 + end + proj = Project.find(3) + r = Repository::Bazaar.create!( + :project => proj, :url => '/tmp/test/bazaar', + :log_encoding => 'ISO-8859-1' ) + assert r + c = Changeset.new(:repository => r, + :committed_on => Time.now, + :revision => '123', + :scmid => '12345', + :comments => s1) + assert( c.save ) + assert_equal s2, c.comments end def test_identifier diff --git a/test/unit/comment_test.rb b/test/unit/comment_test.rb index 43265c47..02be8fc5 100644 --- a/test/unit/comment_test.rb +++ b/test/unit/comment_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -31,6 +31,15 @@ class CommentTest < ActiveSupport::TestCase @news.reload assert_equal 2, @news.comments_count end + + def test_create_should_send_notification + Setting.notified_events << 'news_comment_added' + Watcher.create!(:watchable => @news, :user => @jsmith) + + assert_difference 'ActionMailer::Base.deliveries.size' do + Comment.create!(:commented => @news, :author => @jsmith, :comments => "my comment") + end + end def test_validate comment = Comment.new(:commented => @news) diff --git a/test/unit/custom_field_user_format_test.rb b/test/unit/custom_field_user_format_test.rb new file mode 100644 index 00000000..5cc4ea5c --- /dev/null +++ b/test/unit/custom_field_user_format_test.rb @@ -0,0 +1,70 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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.expand_path('../../test_helper', __FILE__) + +class CustomFieldUserFormatTest < ActiveSupport::TestCase + fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues + + def setup + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') + end + + def test_possible_values_with_no_arguments + assert_equal [], @field.possible_values + assert_equal [], @field.possible_values(nil) + end + + def test_possible_values_with_project_resource + project = Project.find(1) + possible_values = @field.possible_values(project.issues.first) + assert possible_values.any? + assert_equal project.users.sort.collect(&:id).map(&:to_s), possible_values + end + + def test_possible_values_with_nil_project_resource + project = Project.find(1) + assert_equal [], @field.possible_values(Issue.new) + end + + def test_possible_values_options_with_no_arguments + assert_equal [], @field.possible_values_options + assert_equal [], @field.possible_values_options(nil) + end + + def test_possible_values_options_with_project_resource + project = Project.find(1) + possible_values_options = @field.possible_values_options(project.issues.first) + assert possible_values_options.any? + assert_equal project.users.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_cast_blank_value + assert_equal nil, @field.cast_value(nil) + assert_equal nil, @field.cast_value("") + end + + def test_cast_valid_value + user = @field.cast_value("2") + assert_kind_of User, user + assert_equal User.find(2), user + end + + def test_cast_invalid_value + assert_equal nil, @field.cast_value("187") + end +end diff --git a/test/unit/helpers/application_helper_test.rb b/test/unit/helpers/application_helper_test.rb index 69eaf745..5cb2c4cc 100644 --- a/test/unit/helpers/application_helper_test.rb +++ b/test/unit/helpers/application_helper_test.rb @@ -292,7 +292,9 @@ RAW 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link, } @project = Project.find(3) - r = Repository::Darcs.create!(:project => @project, :url => '/tmp/test/darcs') + r = Repository::Darcs.create!( + :project => @project, :url => '/tmp/test/darcs', + :log_encoding => 'UTF-8') assert r c = Changeset.new(:repository => r, :committed_on => Time.now, @@ -524,6 +526,13 @@ EXPECTED assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '') end + def test_headings + raw = 'h1. Some heading' + expected = %|\n

Some heading

| + + assert_equal expected, textilizable(raw) + end + def test_table_of_content raw = <<-RAW {{toc}} @@ -600,111 +609,6 @@ RAW assert textilizable(raw).gsub("\n", "").include?(expected) end - def test_blockquote - # orig raw text - raw = <<-RAW -John said: -> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero. -> Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor. -> * Donec odio lorem, -> * sagittis ac, -> * malesuada in, -> * adipiscing eu, dolor. -> -> >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus. -> Proin a tellus. Nam vel neque. - -He's right. -RAW - - # expected html - expected = <<-EXPECTED -

John said:

-
-Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero. -Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor. -
    -
  • Donec odio lorem,
  • -
  • sagittis ac,
  • -
  • malesuada in,
  • -
  • adipiscing eu, dolor.
  • -
-
-

Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.

-
-

Proin a tellus. Nam vel neque.

-
-

He's right.

-EXPECTED - - assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '') - end - - def test_table - raw = <<-RAW -This is a table with empty cells: - -|cell11|cell12|| -|cell21||cell23| -|cell31|cell32|cell33| -RAW - - expected = <<-EXPECTED -

This is a table with empty cells:

- - - - - -
cell11cell12
cell21cell23
cell31cell32cell33
-EXPECTED - - assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '') - end - - def test_table_with_line_breaks - raw = <<-RAW -This is a table with line breaks: - -|cell11 -continued|cell12|| -|-cell21-||cell23 -cell23 line2 -cell23 *line3*| -|cell31|cell32 -cell32 line2|cell33| - -RAW - - expected = <<-EXPECTED -

This is a table with line breaks:

- - - - - - - - - - - - - - - - - -
cell11
continued
cell12
cell21cell23
cell23 line2
cell23 line3
cell31cell32
cell32 line2
cell33
-EXPECTED - - assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '') - end - - def test_textile_should_not_mangle_brackets - assert_equal '

[msg1][msg2]

', textilizable('[msg1][msg2]') - end - def test_default_formatter Setting.text_formatting = 'unknown' text = 'a *link*: http://www.example.net/' diff --git a/test/unit/helpers/custom_fields_helper_test.rb b/test/unit/helpers/custom_fields_helper_test.rb index 6baff742..58700296 100644 --- a/test/unit/helpers/custom_fields_helper_test.rb +++ b/test/unit/helpers/custom_fields_helper_test.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -26,4 +26,21 @@ class CustomFieldsHelperTest < HelperTestCase assert_equal 'Yes', format_value('1', 'bool') assert_equal 'No', format_value('0', 'bool') end + + def test_unknow_field_format_should_be_edited_as_string + field = CustomField.new(:field_format => 'foo') + value = CustomValue.new(:value => 'bar', :custom_field => field) + field.id = 52 + + assert_equal '', + custom_field_tag('object', value) + end + + def test_unknow_field_format_should_be_bulk_edited_as_string + field = CustomField.new(:field_format => 'foo') + field.id = 52 + + assert_equal '', + custom_field_tag_for_bulk_edit('object', field) + end end diff --git a/test/unit/helpers/repository_helper_test.rb b/test/unit/helpers/repository_helper_test.rb new file mode 100644 index 00000000..23067ea5 --- /dev/null +++ b/test/unit/helpers/repository_helper_test.rb @@ -0,0 +1,103 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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.expand_path('../../../test_helper', __FILE__) + +class RepositoryHelperTest < HelperTestCase + include RepositoriesHelper + + def test_from_latin1_to_utf8 + with_settings :repositories_encodings => 'UTF-8,ISO-8859-1' do + s1 = "Texte encod\xc3\xa9" + s2 = "Texte encod\xe9" + s3 = s2.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ASCII-8BIT") + s3.force_encoding("UTF-8") + end + assert_equal s1, to_utf8(s2) + assert_equal s1, to_utf8(s3) + end + end + + def test_from_euc_jp_to_utf8 + with_settings :repositories_encodings => 'UTF-8,EUC-JP' do + s1 = "\xe3\x83\xac\xe3\x83\x83\xe3\x83\x89\xe3\x83\x9e\xe3\x82\xa4\xe3\x83\xb3" + s2 = "\xa5\xec\xa5\xc3\xa5\xc9\xa5\xde\xa5\xa4\xa5\xf3" + s3 = s2.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ASCII-8BIT") + s3.force_encoding("UTF-8") + end + assert_equal s1, to_utf8(s2) + assert_equal s1, to_utf8(s3) + end + end + + def test_to_utf8_should_be_converted_all_latin1_to_utf8 + with_settings :repositories_encodings => 'ISO-8859-1' do + s1 = "\xc3\x82\xc2\x80" + s2 = "\xC2\x80" + s3 = s2.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ASCII-8BIT") + s3.force_encoding("UTF-8") + end + assert_equal s1, to_utf8(s2) + assert_equal s1, to_utf8(s3) + end + end + + def test_to_utf8_blank_string + assert_equal "", to_utf8("") + assert_equal nil, to_utf8(nil) + end + + def test_to_utf8_returns_ascii_as_utf8 + s1 = "ASCII" + s2 = s1.dup + if s1.respond_to?(:force_encoding) + s1.force_encoding("UTF-8") + s2.force_encoding("ISO-8859-1") + end + str1 = to_utf8(s1) + str2 = to_utf8(s2) + assert_equal s1, str1 + assert_equal s1, str2 + if s1.respond_to?(:force_encoding) + assert_equal "UTF-8", str1.encoding.to_s + assert_equal "UTF-8", str2.encoding.to_s + end + end + + def test_to_utf8_invalid_utf8_sequences_should_be_stripped + with_settings :repositories_encodings => '' do + s1 = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt") + str = to_utf8(s1) + if str.respond_to?(:force_encoding) + assert_equal "Texte encod? en ISO-8859-1.", str + assert str.valid_encoding? + assert_equal "UTF-8", str.encoding.to_s + else + assert_equal "Texte encod en ISO-8859-1.", str + end + end + end +end diff --git a/test/unit/issue_nested_set_test.rb b/test/unit/issue_nested_set_test.rb index cf61cfde..46f4dc4d 100644 --- a/test/unit/issue_nested_set_test.rb +++ b/test/unit/issue_nested_set_test.rb @@ -223,6 +223,16 @@ class IssueNestedSetTest < ActiveSupport::TestCase assert_equal [issue1.id, 2, 3], [issue4.root_id, issue4.lft, issue4.rgt] end + def test_destroy_parent_issue_updated_during_children_destroy + parent = create_issue! + create_issue!(:start_date => Date.today, :parent_issue_id => parent.id) + create_issue!(:start_date => 2.days.from_now, :parent_issue_id => parent.id) + + assert_difference 'Issue.count', -3 do + Issue.find(parent.id).destroy + end + end + def test_destroy_child_issue_with_children root = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'root') child = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => root.id) diff --git a/test/unit/issue_status_test.rb b/test/unit/issue_status_test.rb index 4fc6de1e..bc6535ed 100644 --- a/test/unit/issue_status_test.rb +++ b/test/unit/issue_status_test.rb @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class IssueStatusTest < ActiveSupport::TestCase - fixtures :issue_statuses, :issues + fixtures :issue_statuses, :issues, :roles, :trackers def test_create status = IssueStatus.new :name => "Assigned" @@ -68,6 +68,30 @@ class IssueStatusTest < ActiveSupport::TestCase status.reload assert status.is_default? end + + def test_new_statuses_allowed_to + Workflow.delete_all + + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false) + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false) + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true) + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true) + status = IssueStatus.find(1) + role = Role.find(1) + tracker = Tracker.find(1) + + assert_equal [2], status.new_statuses_allowed_to([role], tracker, false, false).map(&:id) + assert_equal [2], status.find_new_statuses_allowed_to([role], tracker, false, false).map(&:id) + + assert_equal [2, 3], status.new_statuses_allowed_to([role], tracker, true, false).map(&:id) + assert_equal [2, 3], status.find_new_statuses_allowed_to([role], tracker, true, false).map(&:id) + + assert_equal [2, 4], status.new_statuses_allowed_to([role], tracker, false, true).map(&:id) + assert_equal [2, 4], status.find_new_statuses_allowed_to([role], tracker, false, true).map(&:id) + + assert_equal [2, 3, 4, 5], status.new_statuses_allowed_to([role], tracker, true, true).map(&:id) + assert_equal [2, 3, 4, 5], status.find_new_statuses_allowed_to([role], tracker, true, true).map(&:id) + end context "#update_done_ratios" do setup do diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index d284c746..f4dcf662 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -210,6 +210,33 @@ class IssueTest < ActiveSupport::TestCase assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to end + + + def test_new_statuses_allowed_to + Workflow.delete_all + + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false) + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false) + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true) + Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true) + status = IssueStatus.find(1) + role = Role.find(1) + tracker = Tracker.find(1) + user = User.find(2) + + issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1) + assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id) + + issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user) + assert_equal [1, 2, 3], issue.new_statuses_allowed_to(user).map(&:id) + + issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user) + assert_equal [1, 2, 4], issue.new_statuses_allowed_to(user).map(&:id) + + issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user) + assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id) + end + def test_copy issue = Issue.new.copy_from(1) assert issue.save @@ -594,6 +621,29 @@ class IssueTest < ActiveSupport::TestCase assert ActionMailer::Base.deliveries.empty? end + def test_journalized_description + IssueCustomField.delete_all + + i = Issue.first + old_description = i.description + new_description = "This is the new description" + + i.init_journal(User.find(2)) + i.description = new_description + assert_difference 'Journal.count', 1 do + assert_difference 'JournalDetail.count', 1 do + i.save! + end + end + + detail = JournalDetail.first(:order => 'id DESC') + assert_equal i, detail.journal.journalized + assert_equal 'attr', detail.property + assert_equal 'description', detail.prop_key + assert_equal old_description, detail.old_value + assert_equal new_description, detail.value + end + def test_saving_twice_should_not_duplicate_journal_details i = Issue.find(:first) i.init_journal(User.find(2), 'Some notes') @@ -636,6 +686,18 @@ class IssueTest < ActiveSupport::TestCase assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort end + + def test_all_dependent_issues_with_persistent_multiple_circular_dependencies + IssueRelation.delete_all + assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES) + assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_RELATES) + assert IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_RELATES) + # Validation skipping + assert IssueRelation.new(:issue_from => Issue.find(8), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES).save(false) + assert IssueRelation.new(:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_RELATES).save(false) + + assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort + end context "#done_ratio" do setup do diff --git a/test/unit/journal_test.rb b/test/unit/journal_test.rb index 67e719df..2a1c2842 100644 --- a/test/unit/journal_test.rb +++ b/test/unit/journal_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -18,7 +18,7 @@ require File.expand_path('../../test_helper', __FILE__) class JournalTest < ActiveSupport::TestCase - fixtures :issues, :issue_statuses, :journals, :journal_details + fixtures :projects, :issues, :issue_statuses, :journals, :journal_details, :users, :members, :member_roles def setup @journal = Journal.find 1 @@ -46,5 +46,45 @@ class JournalTest < ActiveSupport::TestCase assert journal.save assert_equal 1, ActionMailer::Base.deliveries.size end - + + def test_visible_scope_for_anonymous + # Anonymous user should see issues of public projects only + journals = Journal.visible(User.anonymous).all + assert journals.any? + assert_nil journals.detect {|journal| !journal.issue.project.is_public?} + # Anonymous user should not see issues without permission + Role.anonymous.remove_permission!(:view_issues) + journals = Journal.visible(User.anonymous).all + assert journals.empty? + end + + def test_visible_scope_for_user + user = User.find(9) + assert user.projects.empty? + # Non member user should see issues of public projects only + journals = Journal.visible(user).all + assert journals.any? + assert_nil journals.detect {|journal| !journal.issue.project.is_public?} + # Non member user should not see issues without permission + Role.non_member.remove_permission!(:view_issues) + user.reload + journals = Journal.visible(user).all + assert journals.empty? + # User should see issues of projects for which he has view_issues permissions only + Member.create!(:principal => user, :project_id => 1, :role_ids => [1]) + user.reload + journals = Journal.visible(user).all + assert journals.any? + assert_nil journals.detect {|journal| journal.issue.project_id != 1} + end + + def test_visible_scope_for_admin + user = User.find(1) + user.members.each(&:destroy) + assert user.projects.empty? + journals = Journal.visible(user).all + assert journals.any? + # Admin should see issues on private projects that he does not belong to + assert journals.detect {|journal| !journal.issue.project.is_public?} + end end diff --git a/test/unit/lib/redmine/ciphering_test.rb b/test/unit/lib/redmine/ciphering_test.rb new file mode 100644 index 00000000..5af5f711 --- /dev/null +++ b/test/unit/lib/redmine/ciphering_test.rb @@ -0,0 +1,84 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 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.expand_path('../../../../test_helper', __FILE__) + +class Redmine::CipheringTest < ActiveSupport::TestCase + + def test_password_should_be_encrypted + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository::Subversion.generate!(:password => 'foo') + assert_equal 'foo', r.password + assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) + end + end + + def test_password_should_be_clear_with_blank_key + Redmine::Configuration.with 'database_cipher_key' => '' do + r = Repository::Subversion.generate!(:password => 'foo') + assert_equal 'foo', r.password + assert_equal 'foo', r.read_attribute(:password) + end + end + + def test_password_should_be_clear_with_nil_key + Redmine::Configuration.with 'database_cipher_key' => nil do + r = Repository::Subversion.generate!(:password => 'foo') + assert_equal 'foo', r.password + assert_equal 'foo', r.read_attribute(:password) + end + end + + def test_unciphered_password_should_be_readable + Redmine::Configuration.with 'database_cipher_key' => nil do + r = Repository::Subversion.generate!(:password => 'clear') + end + + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + r = Repository.first(:order => 'id DESC') + assert_equal 'clear', r.password + end + end + + def test_encrypt_all + Repository.delete_all + Redmine::Configuration.with 'database_cipher_key' => nil do + Repository::Subversion.generate!(:password => 'foo') + Repository::Subversion.generate!(:password => 'bar') + end + + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + assert Repository.encrypt_all(:password) + r = Repository.first(:order => 'id DESC') + assert_equal 'bar', r.password + assert r.read_attribute(:password).match(/\Aaes-256-cbc:.+\Z/) + end + end + + def test_decrypt_all + Repository.delete_all + Redmine::Configuration.with 'database_cipher_key' => 'secret' do + Repository::Subversion.generate!(:password => 'foo') + Repository::Subversion.generate!(:password => 'bar') + + assert Repository.decrypt_all(:password) + r = Repository.first(:order => 'id DESC') + assert_equal 'bar', r.password + assert_equal 'bar', r.read_attribute(:password) + end + end +end diff --git a/test/unit/lib/redmine/configuration_test.rb b/test/unit/lib/redmine/configuration_test.rb index 239f2d3d..d172c95f 100644 --- a/test/unit/lib/redmine/configuration_test.rb +++ b/test/unit/lib/redmine/configuration_test.rb @@ -41,6 +41,15 @@ class Redmine::ConfigurationTest < ActiveSupport::TestCase assert_equal 'bar', @conf['somesetting'] end + def test_with + load_conf('default.yml', 'test') + assert_equal 'foo', @conf['somesetting'] + @conf.with 'somesetting' => 'bar' do + assert_equal 'bar', @conf['somesetting'] + end + assert_equal 'foo', @conf['somesetting'] + end + private def load_conf(file, env) diff --git a/test/unit/calendar_test.rb b/test/unit/lib/redmine/helpers/calendar_test.rb similarity index 68% rename from test/unit/calendar_test.rb rename to test/unit/lib/redmine/helpers/calendar_test.rb index ac6a651e..54de986a 100644 --- a/test/unit/calendar_test.rb +++ b/test/unit/lib/redmine/helpers/calendar_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2007 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -15,7 +15,7 @@ # 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.expand_path('../../test_helper', __FILE__) +require File.expand_path('../../../../../test_helper', __FILE__) class CalendarTest < ActiveSupport::TestCase @@ -40,4 +40,24 @@ class CalendarTest < ActiveSupport::TestCase c = Redmine::Helpers::Calendar.new(Date.today, :en, :week) assert_equal [7, 6], [c.startdt.cwday, c.enddt.cwday] end + + def test_monthly_start_day + [1, 6, 7].each do |day| + with_settings :start_of_week => day do + c = Redmine::Helpers::Calendar.new(Date.today, :en, :month) + assert_equal day , c.startdt.cwday + assert_equal (day + 5) % 7 + 1, c.enddt.cwday + end + end + end + + def test_weekly_start_day + [1, 6, 7].each do |day| + with_settings :start_of_week => day do + c = Redmine::Helpers::Calendar.new(Date.today, :en, :week) + assert_equal day, c.startdt.cwday + assert_equal (day + 5) % 7 + 1, c.enddt.cwday + end + end + end end diff --git a/test/unit/lib/redmine/helpers/gantt_test.rb b/test/unit/lib/redmine/helpers/gantt_test.rb index ec9b0651..a32b41d2 100644 --- a/test/unit/lib/redmine/helpers/gantt_test.rb +++ b/test/unit/lib/redmine/helpers/gantt_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -95,15 +95,9 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase setup do create_gantt end - - should "clear the @query.project so cross-project issues and versions can be counted" do - assert @gantt.query.project - @gantt.number_of_rows_on_project(@project) - assert_nil @gantt.query.project - end - should "count 1 for the project itself" do - assert_equal 1, @gantt.number_of_rows_on_project(@project) + should "count 0 for an empty the project" do + assert_equal 0, @gantt.number_of_rows_on_project(@project) end should "count the number of issues without a version" do @@ -111,12 +105,6 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_equal 2, @gantt.number_of_rows_on_project(@project) end - should "count the number of versions" do - @project.versions << Version.generate! - @project.versions << Version.generate! - assert_equal 3, @gantt.number_of_rows_on_project(@project) - end - should "count the number of issues on versions, including cross-project" do version = Version.generate! @project.versions << version @@ -124,21 +112,6 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_equal 3, @gantt.number_of_rows_on_project(@project) end - - should "recursive and count the number of rows on each subproject" do - @project.versions << Version.generate! # +1 - - @subproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1 - @subproject.set_parent!(@project) - @subproject.issues << Issue.generate_for_project!(@subproject) # +1 - @subproject.issues << Issue.generate_for_project!(@subproject) # +1 - - @subsubproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1 - @subsubproject.set_parent!(@subproject) - @subsubproject.issues << Issue.generate_for_project!(@subsubproject) # +1 - - assert_equal 7, @gantt.number_of_rows_on_project(@project) # +1 for self - end end # TODO: more of an integration test @@ -183,6 +156,18 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase @response.body = @gantt.subjects assert_select "div.version-name[style*=left:24px]" end + + context "without assigned issues" do + setup do + @version = Version.generate!(:effective_date => 2.week.from_now.to_date, :sharing => 'none', :name => 'empty_version') + @project.versions << @version + end + + should "not be rendered" do + @response.body = @gantt.subjects + assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0 + end + end end context "issue" do @@ -196,6 +181,31 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_select "div.issue-subject[style*=left:44px]" end + context "assigned to a shared version of another project" do + setup do + p = Project.generate! + p.trackers << @tracker + p.enabled_module_names = [:issue_tracking] + @shared_version = Version.generate!(:sharing => 'system') + p.versions << @shared_version + # Reassign the issue to a shared version of another project + + @issue = Issue.generate!(:fixed_version => @shared_version, + :subject => "gantt#assigned_to_shared_version", + :tracker => @tracker, + :project => @project, + :done_ratio => 30, + :start_date => Date.yesterday, + :due_date => 1.week.from_now.to_date) + @project.issues << @issue + end + + should "be rendered" do + @response.body = @gantt.subjects + assert_select "div.issue-subject", /#{@issue.subject}/ + end + end + context "with subtasks" do setup do attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version} @@ -537,9 +547,9 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_select "div.version.task_done[style*=left:28px]", true, @response.body end - should "Be the total done width of the version" do + should "be the total done width of the version" do @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.task_done[style*=width:18px]", true, @response.body + assert_select "div.version.task_done[style*=width:16px]", true, @response.body end end @@ -697,9 +707,10 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_select "div.task_done[style*=left:28px]", true, @response.body end - should "Be the total done width of the issue" do + should "be the total done width of the issue" do @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) - assert_select "div.task_done[style*=width:18px]", true, @response.body + # 15 days * 4 px * 30% - 2 px for borders = 16 px + assert_select "div.task_done[style*=width:16px]", true, @response.body end should "not be the total done width if the chart starts after issue start date" do @@ -707,7 +718,24 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) assert_select "div.task_done[style*=left:0px]", true, @response.body - assert_select "div.task_done[style*=width:10px]", true, @response.body + assert_select "div.task_done[style*=width:8px]", true, @response.body + end + + context "for completed issue" do + setup do + @issue.done_ratio = 100 + end + + should "be the total width of the issue" do + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_done[style*=width:58px]", true, @response.body + end + + should "be the total width of the issue with due_date=start_date" do + @issue.due_date = @issue.start_date + @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4}) + assert_select "div.task_done[style*=width:2px]", true, @response.body + end end end diff --git a/test/unit/lib/redmine/i18n_test.rb b/test/unit/lib/redmine/i18n_test.rb index 5bb69c9a..7ef5feb3 100644 --- a/test/unit/lib/redmine/i18n_test.rb +++ b/test/unit/lib/redmine/i18n_test.rb @@ -58,13 +58,36 @@ class Redmine::I18nTest < ActiveSupport::TestCase end end + def test_time_format + set_language_if_valid 'en' + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '%H:%M' do + with_settings :date_format => '' do + assert_equal '02/20/2011 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 15:45', format_time(now) + assert_equal '15:45', format_time(now, false) + end + end + end + def test_time_format_default set_language_if_valid 'en' - now = Time.now - Setting.date_format = '' - Setting.time_format = '' - assert_equal I18n.l(now), format_time(now) - assert_equal I18n.l(now, :format => :time), format_time(now, false) + now = Time.parse('2011-02-20 15:45:22') + with_settings :time_format => '' do + with_settings :date_format => '' do + assert_equal '02/20/2011 03:45 pm', format_time(now) + assert_equal '03:45 pm', format_time(now, false) + end + + with_settings :date_format => '%Y-%m-%d' do + assert_equal '2011-02-20 03:45 pm', format_time(now) + assert_equal '03:45 pm', format_time(now, false) + end + end end def test_time_format diff --git a/test/unit/lib/redmine/notifiable_test.rb b/test/unit/lib/redmine/notifiable_test.rb index fe02fbfc..7ca44920 100644 --- a/test/unit/lib/redmine/notifiable_test.rb +++ b/test/unit/lib/redmine/notifiable_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -22,9 +22,9 @@ class Redmine::NotifiableTest < ActiveSupport::TestCase end def test_all - assert_equal 11, Redmine::Notifiable.all.length + assert_equal 12, Redmine::Notifiable.all.length - %w(issue_added issue_updated issue_note_added issue_status_updated issue_priority_updated news_added document_added file_added message_posted wiki_content_added wiki_content_updated).each do |notifiable| + %w(issue_added issue_updated issue_note_added issue_status_updated issue_priority_updated news_added news_comment_added document_added file_added message_posted wiki_content_added wiki_content_updated).each do |notifiable| assert Redmine::Notifiable.all.collect(&:name).include?(notifiable), "missing #{notifiable}" end end diff --git a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb index 82372bbb..7945e9bc 100644 --- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb @@ -5,12 +5,41 @@ begin class BazaarAdapterTest < ActiveSupport::TestCase REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/bazaar_repository' + REPOSITORY_PATH.gsub!(/\/+/, '/') if File.directory?(REPOSITORY_PATH) def setup - @adapter = Redmine::Scm::Adapters::BazaarAdapter.new(MODULE_NAME, REPOSITORY_PATH) + @adapter = Redmine::Scm::Adapters::BazaarAdapter.new(REPOSITORY_PATH) end + def test_scm_version + to_test = { "Bazaar (bzr) 2.1.2\n" => [2,1,2], + "2.1.1\n1.7\n1.8" => [2,1,1], + "2.0.1\r\n1.8.1\r\n1.9.1" => [2,0,1]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + def test_cat + cat = @adapter.cat('directory/document.txt') + assert cat =~ /Write the contents of a file as of a given revision to standard output/ + end + + def test_annotate + annotate = @adapter.annotate('doc-mkdir.txt') + assert_equal 17, annotate.lines.size + assert_equal '1', annotate.revisions[0].identifier + assert_equal 'jsmith@', annotate.revisions[0].author + assert_equal 'mkdir', annotate.lines[0] + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end else puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!" def test_fake; assert true end diff --git a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb index b6b4b6ee..197d7df1 100644 --- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb @@ -13,6 +13,15 @@ begin @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) end + def test_scm_version + to_test = { "\nConcurrent Versions System (CVS) 1.12.13 (client/server)\n" => [1,12,13], + "\r\n1.12.12\r\n1.12.11" => [1,12,12], + "1.12.11\r\n1.12.10\r\n" => [1,12,11]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + def test_revisions_all cnt = 0 @adapter.revisions('', nil, nil, :with_paths => true) do |revision| @@ -29,6 +38,21 @@ begin end assert_equal 2, cnt end + + def test_entries_rev3 + rev3_committed_on = Time.gm(2007, 12, 13, 16, 27, 22) + entries = @adapter.entries('sources', rev3_committed_on) + assert_equal 2, entries.size + assert_equal entries[0].name, "watchers_controller.rb" + assert_equal entries[0].lastrev.time, Time.gm(2007, 12, 13, 16, 27, 22) + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end else puts "Cvs test repository NOT FOUND. Skipping unit tests !!!" def test_fake; assert true end diff --git a/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb index fc37254e..74502cf5 100644 --- a/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/filesystem_adapter_test.rb @@ -1,42 +1,38 @@ require File.expand_path('../../../../../../test_helper', __FILE__) - class FilesystemAdapterTest < ActiveSupport::TestCase - + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/filesystem_repository' - + if File.directory?(REPOSITORY_PATH) def setup @adapter = Redmine::Scm::Adapters::FilesystemAdapter.new(REPOSITORY_PATH) end - + def test_entries - assert_equal 2, @adapter.entries.size - assert_equal ["dir", "test"], @adapter.entries.collect(&:name) - assert_equal ["dir", "test"], @adapter.entries(nil).collect(&:name) - assert_equal ["dir", "test"], @adapter.entries("/").collect(&:name) + assert_equal 3, @adapter.entries.size + assert_equal ["dir", "japanese", "test"], @adapter.entries.collect(&:name) + assert_equal ["dir", "japanese", "test"], @adapter.entries(nil).collect(&:name) + assert_equal ["dir", "japanese", "test"], @adapter.entries("/").collect(&:name) ["dir", "/dir", "/dir/", "dir/"].each do |path| assert_equal ["subdir", "dirfile"], @adapter.entries(path).collect(&:name) end # If y try to use "..", the path is ignored ["/../","dir/../", "..", "../", "/..", "dir/.."].each do |path| - assert_equal ["dir", "test"], @adapter.entries(path).collect(&:name), ".. must be ignored in path argument" + assert_equal ["dir", "japanese", "test"], @adapter.entries(path).collect(&:name), + ".. must be ignored in path argument" end end - + def test_cat assert_equal "TEST CAT\n", @adapter.cat("test") assert_equal "TEST CAT\n", @adapter.cat("/test") # Revision number is ignored assert_equal "TEST CAT\n", @adapter.cat("/test", 1) end - else puts "Filesystem test repository NOT FOUND. Skipping unit tests !!! See doc/RUNNING_TESTS." def test_fake; assert true end end - end - - diff --git a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb index 049296d9..a1e7f680 100644 --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb @@ -10,23 +10,78 @@ begin class GitAdapterTest < ActiveSupport::TestCase REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository' + FELIX_UTF8 = "Felix Schäfer" + FELIX_HEX = "Felix Sch\xC3\xA4fer" + CHAR_1_HEX = "\xc3\x9c" + + ## Ruby uses ANSI api to fork a process on Windows. + ## Japanese Shift_JIS and Traditional Chinese Big5 have 0x5c(backslash) problem + ## and these are incompatible with ASCII. + # WINDOWS_PASS = Redmine::Platform.mswin? + WINDOWS_PASS = false + if File.directory?(REPOSITORY_PATH) def setup - @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH) + @adapter = Redmine::Scm::Adapters::GitAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + 'ISO-8859-1' + ) + assert @adapter + @char_1 = CHAR_1_HEX.dup + if @char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + end + end + + def test_scm_version + to_test = { "git version 1.7.3.4\n" => [1,7,3,4], + "1.6.1\n1.7\n1.8" => [1,6,1], + "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end end def test_branches - assert_equal @adapter.branches, ['master', 'test_branch'] + assert_equal [ + 'latin-1-path-encoding', + 'master', + 'test-latin-1', + 'test_branch', + ], @adapter.branches + end + + def test_tags + assert_equal [ + "tag00.lightweight", + "tag01.annotated", + ], @adapter.tags end def test_getting_all_revisions - assert_equal 15, @adapter.revisions('',nil,nil,:all => true).length + assert_equal 21, @adapter.revisions('',nil,nil,:all => true).length end def test_getting_certain_revisions assert_equal 1, @adapter.revisions('','899a15d^','899a15d').length end + def test_revisions_reverse + revs1 = @adapter.revisions('',nil,nil,{:all => true, :reverse => true }) + assert_equal 21, revs1.length + assert_equal '7234cb2750b63f47bff735edc50a1c0a433c2518', revs1[0].identifier + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs1[20].identifier + + since2 = Time.gm(2010, 9, 30, 0, 0, 0) + revs2 = @adapter.revisions('',nil,nil,{:all => true, :since => since2, :reverse => true }) + assert_equal 6, revs2.length + assert_equal '67e7792ce20ccae2e4bb73eed09bb397819c8834', revs2[0].identifier + assert_equal '1ca7f5ed374f3cb31a93ae5215c2e25cc6ec5127', revs2[5].identifier + end + def test_getting_revisions_with_spaces_in_filename assert_equal 1, @adapter.revisions("filemane with spaces.txt", nil, nil, :all => true).length @@ -48,7 +103,8 @@ begin annotate = @adapter.annotate('sources/watchers_controller.rb') assert_kind_of Redmine::Scm::Adapters::Annotate, annotate assert_equal 41, annotate.lines.size - assert_equal "# This program is free software; you can redistribute it and/or", annotate.lines[4].strip + assert_equal "# This program is free software; you can redistribute it and/or", + annotate.lines[4].strip assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", annotate.revisions[4].identifier assert_equal "jsmith", annotate.revisions[4].author @@ -72,12 +128,101 @@ begin def test_last_rev_with_spaces_in_filename last_rev = @adapter.lastrev("filemane with spaces.txt", "ed5bb786bbda2dee66a2d50faf51429dbc043a7b") + str_felix_utf8 = FELIX_UTF8.dup + str_felix_hex = FELIX_HEX.dup + last_rev_author = last_rev.author + if last_rev_author.respond_to?(:force_encoding) + last_rev_author.force_encoding('UTF-8') + end assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier - assert_equal "Felix Schäfer ", + assert_equal "#{str_felix_utf8} ", + last_rev.author + assert_equal "#{str_felix_hex} ", last_rev.author assert_equal "2010-09-18 19:59:46".to_time, last_rev.time end + + def test_latin_1_path + if WINDOWS_PASS + # + else + p2 = "latin-1-dir/test-#{@char_1}-2.txt" + ['4fc55c43bf3d3dc2efb66145365ddc17639ce81e', '4fc55c43bf3'].each do |r1| + assert @adapter.diff(p2, r1) + assert @adapter.cat(p2, r1) + assert_equal 1, @adapter.annotate(p2, r1).lines.length + ['64f1f3e89ad1cb57976ff0ad99a107012ba3481d', '64f1f3e89ad1cb5797'].each do |r2| + assert @adapter.diff(p2, r1, r2) + end + end + end + end + + def test_entries_tag + entries1 = @adapter.entries(nil, 'tag01.annotated') + assert entries1 + assert_equal 3, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 27, readme.size + assert_equal '899a15dba03a3b350b89c3f537e4bbe02a03cdc9', readme.lastrev.identifier + assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time + end + + def test_entries_branch + entries1 = @adapter.entries(nil, 'test_branch') + assert entries1 + assert_equal 4, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 159, readme.size + assert_equal '713f4944648826f558cf548222f813dabe7cbb04', readme.lastrev.identifier + assert_equal Time.gm(2009, 6, 19, 4, 37, 23), readme.lastrev.time + end + + def test_entries_latin_1_files + entries1 = @adapter.entries('latin-1-dir', '64f1f3e8') + assert entries1 + assert_equal 3, entries1.size + f1 = entries1[1] + assert_equal "test-#{@char_1}-2.txt", f1.name + assert_equal "latin-1-dir/test-#{@char_1}-2.txt", f1.path + assert_equal 'file', f1.kind + end + + def test_entries_latin_1_dir + if WINDOWS_PASS + # + else + entries1 = @adapter.entries("latin-1-dir/test-#{@char_1}-subdir", + '1ca7f5ed') + assert entries1 + assert_equal 3, entries1.size + f1 = entries1[1] + assert_equal "test-#{@char_1}-2.txt", f1.name + assert_equal "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-2.txt", f1.path + assert_equal 'file', f1.kind + end + end + + private + + def test_scm_version_for(scm_command_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_command_version) + assert_equal version, @adapter.class.scm_command_version + end + else puts "Git test repository NOT FOUND. Skipping unit tests !!!" def test_fake; assert true end diff --git a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb index bc82c562..2d6812ad 100644 --- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb @@ -4,15 +4,34 @@ begin class MercurialAdapterTest < ActiveSupport::TestCase - TEMPLATES_DIR = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATES_DIR + HELPERS_DIR = Redmine::Scm::Adapters::MercurialAdapter::HELPERS_DIR TEMPLATE_NAME = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_NAME TEMPLATE_EXTENSION = Redmine::Scm::Adapters::MercurialAdapter::TEMPLATE_EXTENSION REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository' + CHAR_1_HEX = "\xc3\x9c" + if File.directory?(REPOSITORY_PATH) def setup - @adapter = Redmine::Scm::Adapters::MercurialAdapter.new(REPOSITORY_PATH) + @adapter = Redmine::Scm::Adapters::MercurialAdapter.new( + REPOSITORY_PATH, + nil, + nil, + nil, + 'ISO-8859-1') + @diff_c_support = true + + @char_1 = CHAR_1_HEX.dup + @tag_char_1 = "tag-#{CHAR_1_HEX}-00" + @branch_char_0 = "branch-#{CHAR_1_HEX}-00" + @branch_char_1 = "branch-#{CHAR_1_HEX}-01" + if @tag_char_1.respond_to?(:force_encoding) + @char_1.force_encoding('UTF-8') + @tag_char_1.force_encoding('UTF-8') + @branch_char_0.force_encoding('UTF-8') + @branch_char_1.force_encoding('UTF-8') + end end def test_hgversion @@ -42,6 +61,31 @@ begin end end + def test_info + [REPOSITORY_PATH, REPOSITORY_PATH + "/", + REPOSITORY_PATH + "//"].each do |repo| + adp = Redmine::Scm::Adapters::MercurialAdapter.new(repo) + repo_path = adp.info.root_url.gsub(/\\/, "/") + assert_equal REPOSITORY_PATH, repo_path + assert_equal '28', adp.info.lastrev.revision + assert_equal '3ae45e2d177d',adp.info.lastrev.scmid + end + end + + def test_revisions + revisions = @adapter.revisions(nil, 2, 4) + assert_equal 3, revisions.size + assert_equal '2', revisions[0].revision + assert_equal '400bb8672109', revisions[0].scmid + assert_equal '4', revisions[2].revision + assert_equal 'def6d2f1254a', revisions[2].scmid + + revisions = @adapter.revisions(nil, 2, 4, {:limit => 2}) + assert_equal 2, revisions.size + assert_equal '2', revisions[0].revision + assert_equal '400bb8672109', revisions[0].scmid + end + def test_diff if @adapter.class.client_version_above?([1, 2]) assert_nil @adapter.diff(nil, '100000') @@ -49,7 +93,7 @@ begin assert_nil @adapter.diff(nil, '100000', '200000') [2, '400bb8672109', '400', 400].each do |r1| diff1 = @adapter.diff(nil, r1) - if @adapter.class.client_version_above?([1, 2]) + if @diff_c_support assert_equal 28, diff1.size buf = diff1[24].gsub(/\r\n|\r|\n/, "") assert_equal "+ return true unless klass.respond_to?('watched_by')", buf @@ -70,8 +114,8 @@ begin end def test_diff_made_by_revision - if @adapter.class.client_version_above?([1, 2]) - [16, '16', '4cddb4e45f52'].each do |r1| + if @diff_c_support + [24, '24', '4cddb4e45f52'].each do |r1| diff1 = @adapter.diff(nil, r1) assert_equal 5, diff1.size buf = diff1[4].gsub(/\r\n|\r|\n/, "") @@ -104,9 +148,12 @@ begin end end - # TODO filesize etc. def test_entries assert_nil @adapter.entries(nil, '100000') + + assert_equal 1, @adapter.entries("sources", 3).size + assert_equal 1, @adapter.entries("sources", 'b3a615152df8').size + [2, '400bb8672109', '400', 400].each do |r| entries1 = @adapter.entries(nil, r) assert entries1 @@ -114,9 +161,15 @@ begin assert_equal 'sources', entries1[1].name assert_equal 'sources', entries1[1].path assert_equal 'dir', entries1[1].kind - assert_equal 'README', entries1[2].name - assert_equal 'README', entries1[2].path - assert_equal 'file', entries1[2].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 27, readme.size + assert_equal '1', readme.lastrev.revision + assert_equal '9d5b5b004199', readme.lastrev.identifier + # 2007-12-14 10:24:01 +0100 + assert_equal Time.gm(2007, 12, 14, 9, 24, 1), readme.lastrev.time entries2 = @adapter.entries('sources', r) assert entries2 @@ -130,6 +183,48 @@ begin end end + def test_entries_tag + entries1 = @adapter.entries(nil, 'tag_test.00') + assert entries1 + assert_equal 3, entries1.size + assert_equal 'sources', entries1[1].name + assert_equal 'sources', entries1[1].path + assert_equal 'dir', entries1[1].kind + readme = entries1[2] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 21, readme.size + assert_equal '0', readme.lastrev.revision + assert_equal '0885933ad4f6', readme.lastrev.identifier + # 2007-12-14 10:22:52 +0100 + assert_equal Time.gm(2007, 12, 14, 9, 22, 52), readme.lastrev.time + end + + def test_entries_branch + entries1 = @adapter.entries(nil, 'test-branch-00') + assert entries1 + assert_equal 5, entries1.size + assert_equal 'sql_escape', entries1[2].name + assert_equal 'sql_escape', entries1[2].path + assert_equal 'dir', entries1[2].kind + readme = entries1[4] + assert_equal 'README', readme.name + assert_equal 'README', readme.path + assert_equal 'file', readme.kind + assert_equal 365, readme.size + assert_equal '8', readme.lastrev.revision + assert_equal 'c51f5bb613cd', readme.lastrev.identifier + # 2001-02-01 00:00:00 -0900 + assert_equal Time.gm(2001, 2, 1, 9, 0, 0), readme.lastrev.time + end + + def test_locate_on_outdated_repository + assert_equal 1, @adapter.entries("images", 0).size + assert_equal 2, @adapter.entries("images").size + assert_equal 2, @adapter.entries("images", 2).size + end + def test_access_by_nodeid path = 'sources/welcome_controller.rb' assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400bb8672109') @@ -141,6 +236,96 @@ begin assert_equal @adapter.cat(path, 2), @adapter.cat(path, '400') end + def test_tags + assert_equal [@tag_char_1, 'tag_test.00', 'tag-init-revision'], @adapter.tags + end + + def test_tagmap + tm = { + @tag_char_1 => 'adf805632193', + 'tag_test.00' => '6987191f453a', + 'tag-init-revision' => '0885933ad4f6', + } + assert_equal tm, @adapter.tagmap + end + + def test_branches + assert_equal [ + 'default', + @branch_char_1, + 'branch (1)[2]&,%.-3_4', + @branch_char_0, + 'test_branch.latin-1', + 'test-branch-00', + ], @adapter.branches + end + + def test_branchmap + bm = { + 'default' => '3ae45e2d177d', + 'test_branch.latin-1' => 'c2ffe7da686a', + 'branch (1)[2]&,%.-3_4' => 'afc61e85bde7', + 'test-branch-00' => '3a330eb32958', + @branch_char_0 => 'c8d3e4887474', + @branch_char_1 => '7bbf4c738e71', + } + assert_equal bm, @adapter.branchmap + end + + def test_path_space + p = 'README (1)[2]&,%.-3_4' + [15, '933ca60293d7'].each do |r1| + assert @adapter.diff(p, r1) + assert @adapter.cat(p, r1) + assert_equal 1, @adapter.annotate(p, r1).lines.length + [25, 'afc61e85bde7'].each do |r2| + assert @adapter.diff(p, r1, r2) + end + end + end + + def test_tag_non_ascii + p = "latin-1-dir/test-#{@char_1}-1.txt" + assert @adapter.cat(p, @tag_char_1) + assert_equal 1, @adapter.annotate(p, @tag_char_1).lines.length + end + + def test_branch_non_ascii + p = "latin-1-dir/test-#{@char_1}-subdir/test-#{@char_1}-1.txt" + assert @adapter.cat(p, @branch_char_1) + assert_equal 1, @adapter.annotate(p, @branch_char_1).lines.length + end + + def test_nodes_in_branch + [ + 'default', + @branch_char_1, + 'branch (1)[2]&,%.-3_4', + @branch_char_0, + 'test_branch.latin-1', + 'test-branch-00', + ].each do |bra| + nib0 = @adapter.nodes_in_branch(bra) + assert nib0 + nib1 = @adapter.nodes_in_branch(bra, :limit => 1) + assert_equal 1, nib1.size + case bra + when 'branch (1)[2]&,%.-3_4' + assert_equal 3, nib0.size + assert_equal nib0[0], 'afc61e85bde7' + nib2 = @adapter.nodes_in_branch(bra, :limit => 2) + assert_equal 2, nib2.size + assert_equal nib2[1], '933ca60293d7' + when @branch_char_1 + assert_equal 2, nib0.size + assert_equal nib0[1], '08ff3227303e' + nib2 = @adapter.nodes_in_branch(bra, :limit => 1) + assert_equal 1, nib2.size + assert_equal nib2[0], '7bbf4c738e71' + end + end + end + private def test_hgversion_for(hgversion, version) @@ -149,7 +334,7 @@ begin end def test_template_path_for(version, template) - assert_equal "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}", + assert_equal "#{HELPERS_DIR}/#{TEMPLATE_NAME}-#{template}.#{TEMPLATE_EXTENSION}", @adapter.class.template_path_for(version) assert File.exist?(@adapter.class.template_path_for(version)) end diff --git a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb index 966a8292..bc769562 100644 --- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 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 @@ -24,12 +24,31 @@ begin if repository_configured?('subversion') def setup + @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) end def test_client_version v = Redmine::Scm::Adapters::SubversionAdapter.client_version assert v.is_a?(Array) end + + def test_scm_version + to_test = { "svn, version 1.6.13 (r1002816)\n" => [1,6,13], + "svn, versione 1.6.13 (r1002816)\n" => [1,6,13], + "1.6.1\n1.7\n1.8" => [1,6,1], + "1.6.2\r\n1.8.1\r\n1.9.1" => [1,6,2]} + to_test.each do |s, v| + test_scm_version_for(s, v) + end + end + + private + + def test_scm_version_for(scm_version, version) + @adapter.class.expects(:scm_version_from_command_line).returns(scm_version) + assert_equal version, @adapter.class.svn_binary_version + end + else puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" def test_fake; assert true end diff --git a/test/unit/lib/redmine/unified_diff_test.rb b/test/unit/lib/redmine/unified_diff_test.rb index 7bac78d8..13653e3a 100644 --- a/test/unit/lib/redmine/unified_diff_test.rb +++ b/test/unit/lib/redmine/unified_diff_test.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Copyright (C) 2006-2011 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 @@ -34,6 +34,63 @@ class Redmine::UnifiedDiffTest < ActiveSupport::TestCase assert_equal 2, diff.size end + def test_inline_partials + diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff')) + assert_equal 1, diff.size + diff = diff.first + assert_equal 43, diff.size + + assert_equal [51, -1], diff[0].offsets + assert_equal [51, -1], diff[1].offsets + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[1].html_line + + assert_nil diff[2].offsets + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[2].html_line + + assert_equal [0, -14], diff[3].offsets + assert_equal [0, -14], diff[4].offsets + assert_equal 'Ut sed auctor justo', diff[3].html_line + assert_equal 'xxx auctor justo', diff[4].html_line + + assert_equal [13, -19], diff[6].offsets + assert_equal [13, -19], diff[7].offsets + + assert_equal [24, -8], diff[9].offsets + assert_equal [24, -8], diff[10].offsets + + assert_equal [37, -1], diff[12].offsets + assert_equal [37, -1], diff[13].offsets + + assert_equal [0, -38], diff[15].offsets + assert_equal [0, -38], diff[16].offsets + end + + def test_side_by_side_partials + diff = Redmine::UnifiedDiff.new(read_diff_fixture('partials.diff'), :type => 'sbs') + assert_equal 1, diff.size + diff = diff.first + assert_equal 32, diff.size + + assert_equal [51, -1], diff[0].offsets + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', diff[0].html_line_left + assert_equal 'Lorem ipsum dolor sit amet, consectetur adipiscing xx', diff[0].html_line_right + + assert_nil diff[1].offsets + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_left + assert_equal 'Praesent et sagittis dui. Vivamus ac diam diam', diff[1].html_line_right + + assert_equal [0, -14], diff[2].offsets + assert_equal 'Ut sed auctor justo', diff[2].html_line_left + assert_equal 'xxx auctor justo', diff[2].html_line_right + + assert_equal [13, -19], diff[4].offsets + assert_equal [24, -8], diff[6].offsets + assert_equal [37, -1], diff[8].offsets + assert_equal [0, -38], diff[10].offsets + + end + def test_line_starting_with_dashes diff = Redmine::UnifiedDiff.new(<<-DIFF --- old.txt Wed Nov 11 14:24:58 2009 @@ -60,8 +117,39 @@ DIFF assert_equal 1, diff.size end + def test_one_line_new_files + diff = Redmine::UnifiedDiff.new(<<-DIFF +diff -r 000000000000 -r ea98b14f75f0 README1 +--- /dev/null ++++ b/README1 +@@ -0,0 +1,1 @@ ++test1 +diff -r 000000000000 -r ea98b14f75f0 README2 +--- /dev/null ++++ b/README2 +@@ -0,0 +1,1 @@ ++test2 +diff -r 000000000000 -r ea98b14f75f0 README3 +--- /dev/null ++++ b/README3 +@@ -0,0 +1,3 @@ ++test4 ++test5 ++test6 +diff -r 000000000000 -r ea98b14f75f0 README4 +--- /dev/null ++++ b/README4 +@@ -0,0 +1,3 @@ ++test4 ++test5 ++test6 +DIFF + ) + assert_equal 4, diff.size + end + private - + def read_diff_fixture(filename) File.new(File.join(File.dirname(__FILE__), '/../../../fixtures/diffs', filename)).read end diff --git a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb index 72cb9dc5..fa5f8adb 100644 --- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb @@ -64,12 +64,18 @@ class Redmine::WikiFormatting::TextileFormatterTest < HelperTestCase '@@' => '<Location /redmine>' ) end - + def test_escaping assert_html_output( 'this is a