From 21b8e8b5e586e8e0b96c9077047a2f3f260c5731 Mon Sep 17 00:00:00 2001 From: Holger Just Date: Wed, 2 Mar 2011 10:54:44 +0100 Subject: [PATCH 01/42] [#250] Allow empty sets in query value lists --- app/models/query.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/models/query.rb b/app/models/query.rb index 1f033048ad..1772d68f1d 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -566,9 +566,19 @@ def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=f sql = '' case operator when "=" - sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" + if value.present? + sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" + else + # empty set of allowed values produces no result + sql = "0=1" + end when "!" - sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" + if value.present? + sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" + else + # empty set of forbidden values allows all results + sql = "1=1" + end when "!*" sql = "#{db_table}.#{db_field} IS NULL" sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter From ca9ff0e4712b27abe6528fc37148a0de3b2a1cc3 Mon Sep 17 00:00:00 2001 From: Holger Just Date: Wed, 2 Mar 2011 11:12:57 +0100 Subject: [PATCH 02/42] [#250] Add tests for empty sets of users in queries --- test/unit/query_test.rb | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index 4d85a2b2bd..a20c14e9a7 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -467,6 +467,7 @@ def test_editable_by @group2 = Group.generate!.reload @group2.users << @user_in_group2 + @empty_group = Group.generate!.reload end should "search assigned to for users in the group" do @@ -484,7 +485,6 @@ def test_editable_by # Users not in a group assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" assert_find_issues_with_query_is_successful @query - end should "search assigned to any group member (all)" do @@ -494,7 +494,22 @@ def test_editable_by # Only users in a group assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')" assert_find_issues_with_query_is_successful @query - + end + + should "return no results on empty set" do + @query = Query.new(:name => '_') + @query.add_filter('member_of_group', '=', [@empty_group.id.to_s]) + + assert_query_statement_includes @query, "(0=1)" + assert find_issues_with_query(@query).empty? + end + + should "return results on disallowed empty set" do + @query = Query.new(:name => '_') + @query.add_filter('member_of_group', '!', [@empty_group.id.to_s]) + + assert_query_statement_includes @query, "(1=1)" + assert_find_issues_with_query_is_successful @query end end @@ -507,6 +522,7 @@ def test_editable_by @manager_role = Role.generate!(:name => 'Manager') @developer_role = Role.generate!(:name => 'Developer') + @empty_role = Role.generate!(:name => 'Empty') @project = Project.generate! @manager = User.generate! @@ -540,6 +556,22 @@ def test_editable_by assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')" assert_find_issues_with_query_is_successful @query end + + should "return no results on empty set" do + @query = Query.new(:name => '_') + @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s]) + + assert_query_statement_includes @query, "(0=1)" + assert find_issues_with_query(@query).empty? + end + + should "return results on disallowed empty set" do + @query = Query.new(:name => '_') + @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s]) + + assert_query_statement_includes @query, "(1=1)" + assert_find_issues_with_query_is_successful @query + end end end From da45dfcc72b5a4235e9c1edb8f62364f026dd068 Mon Sep 17 00:00:00 2001 From: Gregor Schmidt Date: Wed, 2 Mar 2011 14:56:21 +0100 Subject: [PATCH 03/42] adding require 'thread' since active support needs it but rubygems 1.6 removed it fixes #251 --- config/boot.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/boot.rb b/config/boot.rb index a8e494ed84..bb84455861 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -158,5 +158,8 @@ def requirement end end +# working around deprecation in RubyGems 1.6 +require 'thread' + # All that for this: Rails.boot! From 61e20cb29dda5dc5d624ad8b1a34f6033599ee30 Mon Sep 17 00:00:00 2001 From: Gregor Schmidt Date: Mon, 7 Mar 2011 16:32:16 +0100 Subject: [PATCH 04/42] Fixing monkey patching of rubytree to be more robust --- lib/redmine/menu_manager.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/redmine/menu_manager.rb b/lib/redmine/menu_manager.rb index 996b1c5b4f..51f8ed64f9 100644 --- a/lib/redmine/menu_manager.rb +++ b/lib/redmine/menu_manager.rb @@ -23,12 +23,13 @@ def self.included(base) base.class_eval do attr_reader :last_items_count - alias :old_initilize :initialize - def initialize(name, content = nil) - old_initilize(name, content) + def initialize_with_redmine(name, content = nil) + extend InstanceMethods @last_items_count = 0 - extend(InstanceMethods) + + initialize_without_redmine(name, content) end + alias_method_chain :initialize, :redmine end end @@ -99,7 +100,9 @@ def position end end end -Tree::TreeNode.send(:include, TreeNodePatch) +unless Tree::TreeNode.included_modules.include?(TreeNodePatch) + Tree::TreeNode.send(:include, TreeNodePatch) +end module Redmine module MenuManager From c8e584e894955779924a3328b844e3a44923e44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Scha=CC=88fer?= Date: Mon, 7 Mar 2011 19:43:51 +0100 Subject: [PATCH 05/42] Add the custom_fields helper to JournalsController #267 --- app/controllers/journals_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/journals_controller.rb b/app/controllers/journals_controller.rb index d3b56e8cb8..1f65dc822f 100644 --- a/app/controllers/journals_controller.rb +++ b/app/controllers/journals_controller.rb @@ -27,6 +27,7 @@ class JournalsController < ApplicationController include QueriesHelper helper :sort include SortHelper + helper :custom_fields def index retrieve_query From 6ad92b659fb018f28e643f961235bc1c3f9b328e Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 13 Mar 2011 16:48:34 -0700 Subject: [PATCH 06/42] [#278] Start issue autocompletes with one character --- public/javascripts/application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 1679d83d9f..26671685ff 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -210,7 +210,7 @@ function observeParentIssueField(url) { new Ajax.Autocompleter('issue_parent_issue_id', 'parent_issue_candidates', url, - { minChars: 3, + { minChars: 1, frequency: 0.5, paramName: 'q', updateElement: function(value) { @@ -222,7 +222,7 @@ function observeRelatedIssueField(url) { new Ajax.Autocompleter('relation_issue_to_id', 'related_issue_candidates', url, - { minChars: 3, + { minChars: 1, frequency: 0.5, paramName: 'q', updateElement: function(value) { From ae0851ddb43a6b213c133f50706023b04f4c075d Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 13 Mar 2011 17:11:04 -0700 Subject: [PATCH 07/42] [#280] Include matching issue ids in the auto_completer. Using SQL CAST() to convert the issue ids to strings so LIKE will work properly. --- app/controllers/auto_completes_controller.rb | 5 ++++- .../auto_completes_controller_test.rb | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/controllers/auto_completes_controller.rb b/app/controllers/auto_completes_controller.rb index feb1cb2317..021d5367b8 100644 --- a/app/controllers/auto_completes_controller.rb +++ b/app/controllers/auto_completes_controller.rb @@ -9,7 +9,10 @@ def issues @issues << query.visible.find_by_id(q.to_i) end unless q.blank? - @issues += query.visible.find(:all, :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE ?", "%#{q.downcase}%"], :limit => 10) + @issues += query.visible.find(:all, + :limit => 10, + :order => "#{Issue.table_name}.id ASC", + :conditions => ["LOWER(#{Issue.table_name}.subject) LIKE :q OR CAST(#{Issue.table_name}.id AS CHAR(13)) LIKE :q", {:q => "%#{q.downcase}%" }]) end @issues.compact! render :layout => false diff --git a/test/functional/auto_completes_controller_test.rb b/test/functional/auto_completes_controller_test.rb index bad8c3bd6d..db246c30fa 100644 --- a/test/functional/auto_completes_controller_test.rb +++ b/test/functional/auto_completes_controller_test.rb @@ -16,6 +16,24 @@ def test_issues_should_return_issue_with_given_id assert_not_nil assigns(:issues) assert assigns(:issues).include?(Issue.find(13)) end + + test 'should return issues matching a given id' do + @project = Project.find('subproject1') + @issue_21 = Issue.generate_for_project!(@project, :id => 21) + @issue_101 = Issue.generate_for_project!(@project, :id => 101) + @issue_102 = Issue.generate_for_project!(@project, :id => 102) + @issue_with_subject = Issue.generate_for_project!(@project, :subject => 'This has 1 in the subject') + + get :issues, :project_id => @project.id, :q => '1' + + assert_response :success + assert_not_nil assigns(:issues) + assert assigns(:issues).include?(Issue.find(13)) + assert assigns(:issues).include?(@issue_21) + assert assigns(:issues).include?(@issue_101) + assert assigns(:issues).include?(@issue_102) + assert assigns(:issues).include?(@issue_with_subject) + end def test_auto_complete_with_scope_all_and_cross_project_relations Setting.cross_project_issue_relations = '1' From 1e66dd90437aee1537007396ee62469b09ff404a Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 13 Mar 2011 16:33:56 -0700 Subject: [PATCH 08/42] [#276] Add a link to log time to Project > Overview --- app/views/projects/show.rhtml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/views/projects/show.rhtml b/app/views/projects/show.rhtml index 414358acaf..bfd27b4ae4 100644 --- a/app/views/projects/show.rhtml +++ b/app/views/projects/show.rhtml @@ -67,8 +67,13 @@ <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>

<%= l(:label_spent_time) %>

<%= l_hours(@total_hours) %>

-

<%= link_to(l(:label_details), {:controller => 'timelog', :action => 'index', :project_id => @project}) %> | - <%= link_to(l(:label_report), {:controller => 'time_entry_reports', :action => 'report', :project_id => @project}) %>

+

+ <%= link_to(l(:label_details), {:controller => 'timelog', :action => 'index', :project_id => @project}) %> | + <%= link_to(l(:label_report), {:controller => 'time_entry_reports', :action => 'report', :project_id => @project}) %> + <% if authorize_for('timelog', 'new') %> + | <%= link_to l(:button_log_time), {:controller => 'timelog', :action => 'new', :project_id => @project} %> + <% end %> +

<% end %> <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %> <% end %> From 0db26f833702a80d1e4bfd770448bf0ff79fa11a Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 13 Mar 2011 17:27:20 -0700 Subject: [PATCH 09/42] [#199] Add view_issues_show_identifier hook Contributed by Yuki Sonoda --- app/views/issues/show.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index cfe0288ffb..afb0874782 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -1,6 +1,6 @@ <%= render :partial => 'action_menu' %> -

<%= @issue.tracker.name %> #<%= @issue.id %>

+

<%= @issue.tracker.name %> #<%= @issue.id %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %>

<%= avatar(@issue.author, :size => "50") %> From 430fc228b5421a27bf7df60f6df4949f9e5d0acb Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Thu, 24 Feb 2011 08:53:25 -0800 Subject: [PATCH 10/42] [#219] Add plugin hooks to the mailer layout --- app/views/layouts/mailer.text.html.erb | 2 ++ app/views/layouts/mailer.text.plain.erb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/views/layouts/mailer.text.html.erb b/app/views/layouts/mailer.text.html.erb index e4860fd523..53ccca0d07 100644 --- a/app/views/layouts/mailer.text.html.erb +++ b/app/views/layouts/mailer.text.html.erb @@ -26,7 +26,9 @@ hr { <%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_header) %> +<%= call_hook(:view_layouts_mailer_html_before_content, self.assigns) %> <%= yield %> +<%= call_hook(:view_layouts_mailer_html_after_content, self.assigns) %>
<%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer) %> diff --git a/app/views/layouts/mailer.text.plain.erb b/app/views/layouts/mailer.text.plain.erb index 4b8c0b4f58..d4e9c04d4d 100644 --- a/app/views/layouts/mailer.text.plain.erb +++ b/app/views/layouts/mailer.text.plain.erb @@ -1,4 +1,6 @@ <%= Setting.emails_header %> +<%= call_hook(:view_layouts_mailer_plain_before_content, self.assigns) %> <%= yield %> +<%= call_hook(:view_layouts_mailer_plain_after_content, self.assigns) %> -- <%= Setting.emails_footer %> From cdb80f697f7732610e65fd1cb3d42306f417e94a Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 13 Mar 2011 19:34:20 -0700 Subject: [PATCH 11/42] [#281] Show the project on cross-project issues --- app/views/versions/show.rhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/versions/show.rhtml b/app/views/versions/show.rhtml index 13769a5b97..25d93eaf22 100644 --- a/app/views/versions/show.rhtml +++ b/app/views/versions/show.rhtml @@ -37,7 +37,7 @@ From b5b36844735514fba89f217320a0ff07f6470523 Mon Sep 17 00:00:00 2001 From: Gregor Schmidt Date: Mon, 14 Mar 2011 17:49:19 +0100 Subject: [PATCH 12/42] [#282] Make safe_attributes work with sub classes --- lib/redmine/safe_attributes.rb | 9 ++++++-- test/unit/lib/redmine/safe_attributes_test.rb | 21 ++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/redmine/safe_attributes.rb b/lib/redmine/safe_attributes.rb index 6f87a233cb..8c49a437c5 100644 --- a/lib/redmine/safe_attributes.rb +++ b/lib/redmine/safe_attributes.rb @@ -31,14 +31,19 @@ module ClassMethods def safe_attributes(*args) @safe_attributes ||= [] if args.empty? - @safe_attributes + if superclass < Redmine::SafeAttributes + superclass.safe_attributes + @safe_attributes + else + @safe_attributes + end else options = args.last.is_a?(Hash) ? args.pop : {} @safe_attributes << [args, options] + safe_attributes end end end - + # Returns an array that can be safely set by user or current user # # Example: diff --git a/test/unit/lib/redmine/safe_attributes_test.rb b/test/unit/lib/redmine/safe_attributes_test.rb index 9498a438e7..bfad6606f8 100644 --- a/test/unit/lib/redmine/safe_attributes_test.rb +++ b/test/unit/lib/redmine/safe_attributes_test.rb @@ -35,11 +35,16 @@ class Person < Base end class Book < Base - attr_accessor :title + attr_accessor :title, :isbn include Redmine::SafeAttributes safe_attributes :title end + + class PublishedBook < Book + safe_attributes :isbn + end + def test_safe_attribute_names p = Person.new assert_equal ['firstname', 'lastname'], p.safe_attribute_names(User.anonymous) @@ -84,4 +89,18 @@ def test_set_safe_attributes_without_user assert_equal 'Smith', p.lastname assert_equal 'jsmith', p.login end + + def test_use_safe_attributes_in_subclasses + b = Book.new + p = PublishedBook.new + + b.safe_attributes = {'title' => 'My awesome Ruby Book', 'isbn' => '1221132343'} + p.safe_attributes = {'title' => 'The Pickaxe', 'isbn' => '1934356085'} + + assert_equal 'My awesome Ruby Book', b.title + assert_nil b.isbn + + assert_equal 'The Pickaxe', p.title + assert_equal '1934356085', p.isbn + end end From 0320517d2cff53bad7978ebb5126b0aea67166f2 Mon Sep 17 00:00:00 2001 From: Gregor Schmidt Date: Mon, 14 Mar 2011 17:50:12 +0100 Subject: [PATCH 13/42] [#282] Make safe_attributes work with symbol keys --- lib/redmine/safe_attributes.rb | 2 +- test/unit/lib/redmine/safe_attributes_test.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/redmine/safe_attributes.rb b/lib/redmine/safe_attributes.rb index 8c49a437c5..13db6a4505 100644 --- a/lib/redmine/safe_attributes.rb +++ b/lib/redmine/safe_attributes.rb @@ -67,7 +67,7 @@ def safe_attribute_names(user=User.current) # # => {'title' => 'My book'} def delete_unsafe_attributes(attrs, user=User.current) safe = safe_attribute_names(user) - attrs.dup.delete_if {|k,v| !safe.include?(k)} + attrs.dup.delete_if {|k,v| !safe.include?(k.to_s)} end # Sets attributes from attrs that are safe diff --git a/test/unit/lib/redmine/safe_attributes_test.rb b/test/unit/lib/redmine/safe_attributes_test.rb index bfad6606f8..195b444629 100644 --- a/test/unit/lib/redmine/safe_attributes_test.rb +++ b/test/unit/lib/redmine/safe_attributes_test.rb @@ -90,6 +90,13 @@ def test_set_safe_attributes_without_user assert_equal 'jsmith', p.login end + def test_with_indifferent_access + p = Person.new + p.safe_attributes = {'firstname' => 'Jack', :lastname => 'Miller'} + assert_equal 'Jack', p.firstname + assert_equal 'Miller', p.lastname + end + def test_use_safe_attributes_in_subclasses b = Book.new p = PublishedBook.new From 5fc8ff47f865468a6d53f33ff001fdcb898b0fe1 Mon Sep 17 00:00:00 2001 From: Ivan Evtuhovich Date: Fri, 18 Mar 2011 11:08:38 +0300 Subject: [PATCH 14/42] Move repositories_helper labels to translation, refs #209 --- app/helpers/repositories_helper.rb | 14 +++++++------- config/locales/en.yml | 9 ++++++++- config/locales/ru.yml | 7 +++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 63ea522384..bf3c045e16 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -172,27 +172,27 @@ def subversion_field_tags(form, repository) end def darcs_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) + content_tag('p', form.text_field(:url, :label => :label_darcs_path, :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) end def mercurial_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) + content_tag('p', form.text_field(:url, :label => :label_mercurial_path, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) end def git_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) + content_tag('p', form.text_field(:url, :label => :label_git_path, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) end def cvs_field_tags(form, repository) - content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) + - content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?)) + content_tag('p', form.text_field(:root_url, :label => :label_cvs_path, :size => 60, :required => true, :disabled => !repository.new_record?)) + + content_tag('p', form.text_field(:url, :label => :label_cvs_module, :size => 30, :required => true, :disabled => !repository.new_record?)) end def bazaar_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) + content_tag('p', form.text_field(:url, :label => :label_bazaar_path, :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) end def filesystem_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) + content_tag('p', form.text_field(:url, :label => :label_filesystem_path, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) end end diff --git a/config/locales/en.yml b/config/locales/en.yml index b55f9bb253..382273f5aa 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -793,7 +793,14 @@ en: 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:" - + label_git_path: Path to .git directory + label_darcs_path: Root directory + label_mercurial_path: Root directory + label_cvs_path: CVSROOT + label_cvs_module: Module + label_bazaar_path: Root directory + label_filesystem_path: Root directory + button_login: Login button_submit: Submit button_save: Save diff --git a/config/locales/ru.yml b/config/locales/ru.yml index e42b62f477..e2f5d2ba7f 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1052,3 +1052,10 @@ ru: 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: Модуль + label_filesystem_path: Каталог + label_darcs_path: Каталог + label_bazaar_path: Каталог + label_cvs_path: CVSROOT + label_git_path: Путь к каталогу .git + label_mercurial_path: Каталог From 3377e5b5b7671907df1185205c4196063abd1058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=A4fer?= Date: Sat, 19 Mar 2011 10:57:22 +0100 Subject: [PATCH 15/42] Add new translation strings to all locales. #209 --- config/locales/bg.yml | 7 +++++++ config/locales/bs.yml | 7 +++++++ config/locales/ca.yml | 7 +++++++ config/locales/cs.yml | 7 +++++++ config/locales/da.yml | 7 +++++++ config/locales/de.yml | 7 +++++++ config/locales/el.yml | 7 +++++++ config/locales/en-GB.yml | 7 +++++++ config/locales/es.yml | 7 +++++++ config/locales/eu.yml | 7 +++++++ config/locales/fi.yml | 7 +++++++ config/locales/fr.yml | 7 +++++++ config/locales/gl.yml | 7 +++++++ config/locales/he.yml | 7 +++++++ config/locales/hr.yml | 7 +++++++ config/locales/hu.yml | 7 +++++++ config/locales/id.yml | 7 +++++++ config/locales/it.yml | 7 +++++++ config/locales/ja.yml | 7 +++++++ config/locales/ko.yml | 7 +++++++ config/locales/lt.yml | 7 +++++++ config/locales/lv.yml | 7 +++++++ config/locales/mk.yml | 7 +++++++ config/locales/mn.yml | 7 +++++++ config/locales/nl.yml | 7 +++++++ config/locales/no.yml | 7 +++++++ config/locales/pl.yml | 7 +++++++ config/locales/pt-BR.yml | 7 +++++++ config/locales/pt.yml | 7 +++++++ config/locales/ro.yml | 7 +++++++ config/locales/sk.yml | 7 +++++++ config/locales/sl.yml | 7 +++++++ config/locales/sr-YU.yml | 7 +++++++ config/locales/sr.yml | 7 +++++++ config/locales/sv.yml | 7 +++++++ config/locales/th.yml | 7 +++++++ config/locales/tr.yml | 7 +++++++ config/locales/uk.yml | 7 +++++++ config/locales/vi.yml | 7 +++++++ config/locales/zh-TW.yml | 7 +++++++ config/locales/zh.yml | 7 +++++++ 41 files changed, 287 insertions(+) diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 47e4803c73..099d2a3076 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -939,3 +939,10 @@ bg: enumeration_system_activity: Системна активност text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 68229f2405..405188691e 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -953,3 +953,10 @@ bs: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/ca.yml b/config/locales/ca.yml index e9dd73ba01..e2a31df42b 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -942,3 +942,10 @@ ca: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 39bb5a0fb7..cd6b77ec33 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -939,3 +939,10 @@ cs: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/da.yml b/config/locales/da.yml index da3110c0f7..758ff93c9a 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -955,3 +955,10 @@ da: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/de.yml b/config/locales/de.yml index bcd401567d..6be4d0a797 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -956,3 +956,10 @@ de: notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden. text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/el.yml b/config/locales/el.yml index 934caa07e2..d9fe2b6afa 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -939,3 +939,10 @@ el: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 418b79795b..ef00034add 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -943,3 +943,10 @@ en-GB: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/es.yml b/config/locales/es.yml index 84cb87b1f3..f7b868a7fc 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -976,3 +976,10 @@ es: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/eu.yml b/config/locales/eu.yml index c48ec2fca7..67983023f8 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -943,3 +943,10 @@ eu: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 3705f972fb..628416f23d 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -960,3 +960,10 @@ fi: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 01c172abea..b9fd398242 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -957,3 +957,10 @@ fr: field_assigned_to_role: Rôle de l'assigné setting_emails_header: Emails header text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 51163c7755..827d32afd9 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -951,3 +951,10 @@ gl: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/he.yml b/config/locales/he.yml index 01ac9366c6..6b259570d5 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -944,3 +944,10 @@ he: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/hr.yml b/config/locales/hr.yml index 702db5938d..cfae5edd50 100644 --- a/config/locales/hr.yml +++ b/config/locales/hr.yml @@ -946,3 +946,10 @@ hr: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 82159ad0f0..e0616a0851 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -958,3 +958,10 @@ 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/id.yml b/config/locales/id.yml index 1587f40fa5..8a33b3935f 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -947,3 +947,10 @@ id: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/it.yml b/config/locales/it.yml index 456eb830df..33c8e75c11 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -940,3 +940,10 @@ it: notice_gantt_chart_truncated: Il grafico è stato troncato perchè eccede il numero di oggetti (%{max}) da visualizzare setting_gantt_items_limit: Massimo numero di oggetti da visualizzare sul diagramma di gantt text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/ja.yml b/config/locales/ja.yml index d55c51671b..02025268ce 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -960,3 +960,10 @@ ja: enumeration_activities: 作業分類 (時間トラッキング) enumeration_system_activity: システム作業分類 text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 019f84acbf..7772ac7a37 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -991,3 +991,10 @@ ko: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 56026ef468..a943f6104d 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -999,3 +999,10 @@ lt: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 73320eec61..8d47c896dd 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -934,3 +934,10 @@ lv: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/mk.yml b/config/locales/mk.yml index 0cbc914e17..2b39ba38b1 100644 --- a/config/locales/mk.yml +++ b/config/locales/mk.yml @@ -939,3 +939,10 @@ mk: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/mn.yml b/config/locales/mn.yml index 6bf504c519..186c313364 100644 --- a/config/locales/mn.yml +++ b/config/locales/mn.yml @@ -940,3 +940,10 @@ mn: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 389ddb2ec4..4dcf7cf9ba 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -921,3 +921,10 @@ nl: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/no.yml b/config/locales/no.yml index 776dfe9771..5120869e30 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -926,3 +926,10 @@ 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 338eec7ef8..97bc98012d 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -956,3 +956,10 @@ pl: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 5173b8fd29..5258667df0 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -959,3 +959,10 @@ pt-BR: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/pt.yml b/config/locales/pt.yml index d8474277c1..e9742865c5 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -943,3 +943,10 @@ pt: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 5ace8afb84..115ba9524c 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -932,3 +932,10 @@ ro: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 55cb83bbf6..f752a07aa6 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -934,3 +934,10 @@ sk: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 74fa1cae59..b4b4bc3442 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -935,3 +935,10 @@ sl: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/sr-YU.yml b/config/locales/sr-YU.yml index 59411b1f82..512070ca13 100644 --- a/config/locales/sr-YU.yml +++ b/config/locales/sr-YU.yml @@ -939,3 +939,10 @@ sr-YU: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 89b51a2a94..9ea33b659e 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -940,3 +940,10 @@ sr: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/sv.yml b/config/locales/sv.yml index f08f481c8a..cda64c85ce 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -980,3 +980,10 @@ sv: enumeration_activities: Aktiviteter (tidsuppföljning) enumeration_system_activity: Systemaktivitet text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/th.yml b/config/locales/th.yml index b46f74548a..5f174307d9 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -936,3 +936,10 @@ th: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 82d337924a..1e69821336 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -958,3 +958,10 @@ tr: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/uk.yml b/config/locales/uk.yml index aec46e55fc..bfa62a6054 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -935,3 +935,10 @@ uk: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 009296e63f..bc7d4d2500 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -990,3 +990,10 @@ vi: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index ae8dac2788..e98d03bad2 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1020,3 +1020,10 @@ enumeration_activities: 活動 (時間追蹤) enumeration_system_activity: 系統活動 text_powered_by: Powered by %{link} + label_cvs_module: Module + label_filesystem_path: Root directory + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 0f2fda6d6e..fb5dcf8584 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -953,3 +953,10 @@ zh: 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 + label_darcs_path: Root directory + label_bazaar_path: Root directory + label_cvs_path: CVSROOT + label_git_path: Path to .git directory + label_mercurial_path: Root directory From 61be2ea66962cc445bdf4161778873b61b1179fb Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Thu, 10 Mar 2011 11:18:37 +0100 Subject: [PATCH 16/42] [#270] bugfix: considering underscore a valid match in project identifier --- extra/svn/reposman.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/extra/svn/reposman.rb b/extra/svn/reposman.rb index c92e32c5da..c9b9b0f5c0 100755 --- a/extra/svn/reposman.rb +++ b/extra/svn/reposman.rb @@ -7,7 +7,7 @@ # == Usage # # reposman [OPTIONS...] -s [DIR] -r [HOST] -# +# # Examples: # reposman --svn-dir=/var/svn --redmine-host=redmine.example.net --scm subversion # reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git @@ -57,7 +57,7 @@ # -q, --quiet no log # # == References -# +# # You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos @@ -225,9 +225,9 @@ def other_read_right?(file) def owner_name(file) mswin? ? $svn_owner : - Etc.getpwuid( File.stat(file).uid ).name + Etc.getpwuid( File.stat(file).uid ).name end - + def mswin? (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i) end @@ -238,7 +238,7 @@ def mswin? if project.identifier.empty? log("\tno identifier for project #{project.name}") next - elsif not project.identifier.match(/^[a-z0-9\-]+$/) + elsif not project.identifier.match(/^[a-z0-9\-_]+$/) log("\tinvalid identifier for project #{project.name} : #{project.identifier}"); next; end @@ -309,4 +309,3 @@ def mswin? end end - \ No newline at end of file From 0c458be0405a0c040729415e2db68d5b4eb0eabb Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sat, 19 Mar 2011 13:21:39 -0700 Subject: [PATCH 17/42] [#283] Update the i18n deprecation message * Use Rails' Deprecation logger instead of the standard logger * Update the url to a message in the forum about the change --- config/initializers/10-patches.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb index 25b20523b6..ac53bf4a03 100644 --- a/config/initializers/10-patches.rb +++ b/config/initializers/10-patches.rb @@ -86,7 +86,7 @@ module Backend module Base def warn_syntax_deprecation!(*args) return if @skip_syntax_deprecation - warn "The {{key}} interpolation syntax in I18n messages is deprecated and will be removed in Redmine 1.2. Please use %{key} instead, see http://www.redmine.org/issues/7013 for more information." + ActiveSupport::Deprecation.warn "The {{key}} interpolation syntax in I18n messages is deprecated and will be removed in ChiliProject 2.0 Redmine 1.2. Please use %{key} instead. See the notice at https://www.chiliproject.org/boards/2/topics/243 for more information." @skip_syntax_deprecation = true end end From 904897ac90128a4a5d17b85e12be4d7209c3f7a7 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sat, 19 Mar 2011 13:33:02 -0700 Subject: [PATCH 18/42] [#283] Fix tpyo --- config/initializers/10-patches.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/10-patches.rb b/config/initializers/10-patches.rb index ac53bf4a03..7d201e8545 100644 --- a/config/initializers/10-patches.rb +++ b/config/initializers/10-patches.rb @@ -86,7 +86,7 @@ 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 Redmine 1.2. Please use %{key} instead. See the notice at https://www.chiliproject.org/boards/2/topics/243 for more information." + 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 end From 10dffcf3b907cda83d2c1a4a060c174c1a2d4c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=A4fer?= Date: Sun, 20 Mar 2011 10:18:17 +0100 Subject: [PATCH 19/42] Get the autologin cookie name from the config #273 --- app/controllers/account_controller.rb | 9 ++++----- app/controllers/application_controller.rb | 4 ++-- lib/redmine/configuration.rb | 6 +++++- test/integration/account_test.rb | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index 0c645e5559..5d6f890f93 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -129,7 +129,7 @@ def activate def logout_user if User.current.logged? - cookies.delete :autologin + cookies.delete Redmine::Configuration['autologin_cookie_name'] Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin']) self.logged_user = nil end @@ -211,15 +211,14 @@ def successful_authentication(user) def set_autologin_cookie(user) token = Token.create(:user => user, :action => 'autologin') - cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin' cookie_options = { :value => token.value, :expires => 1.year.from_now, - :path => (Redmine::Configuration['autologin_cookie_path'] || '/'), - :secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), + :path => Redmine::Configuration['autologin_cookie_path'], + :secure => Redmine::Configuration['autologin_cookie_secure'], :httponly => true } - cookies[cookie_name] = cookie_options + cookies[Redmine::Configuration['autologin_cookie_name']] = cookie_options end # Onthefly creation failed, display the registration form to fill/fix attributes diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 277b96218d..c8d32aa1af 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -63,9 +63,9 @@ def find_current_user if session[:user_id] # existing session (User.active.find(session[:user_id]) rescue nil) - elsif cookies[:autologin] && Setting.autologin? + elsif cookies[Redmine::Configuration['autologin_cookie_name']] && Setting.autologin? # auto-login feature starts a new session - user = User.try_to_autologin(cookies[:autologin]) + user = User.try_to_autologin(cookies[Redmine::Configuration['autologin_cookie_name']]) session[:user_id] = user.id if user user elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) diff --git a/lib/redmine/configuration.rb b/lib/redmine/configuration.rb index 1d2443a9c8..d48322723e 100644 --- a/lib/redmine/configuration.rb +++ b/lib/redmine/configuration.rb @@ -20,7 +20,11 @@ module Configuration # Configuration default values @defaults = { - 'email_delivery' => nil + 'email_delivery' => nil, + # Autologin cookie defaults: + 'autologin_cookie_name' => 'autologin', + 'autologin_cookie_path' => '/', + 'autologin_cookie_secure' => false, } @config = nil diff --git a/test/integration/account_test.rb b/test/integration/account_test.rb index 4943f862e8..178852f62a 100644 --- a/test/integration/account_test.rb +++ b/test/integration/account_test.rb @@ -50,7 +50,7 @@ def test_autologin assert_equal user, token.user assert_equal 'autologin', token.action assert_equal user.id, session[:user_id] - assert_equal token.value, cookies['autologin'] + assert_equal token.value, cookies[Redmine::Configuration['autologin_cookie_name']] # Session is cleared reset! @@ -60,7 +60,7 @@ def test_autologin assert_nil user.reload.last_login_on # User comes back with his autologin cookie - cookies[:autologin] = token.value + cookies[Redmine::Configuration['autologin_cookie_name']] = token.value get '/my/page' assert_response :success assert_template 'my/page' From 9fe45cfe1f49d9c2e9ba1949c87d6e6889e3a48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=A4fer?= Date: Sun, 20 Mar 2011 18:45:15 +0100 Subject: [PATCH 20/42] Add git-smart-http support to apache perl module #245 Latest patch from http://www.redmine.org/issues/4905 and other changes improvements, compare also with https://orga.fachschaften.org/projects/redmine-fsenorg/repository/revisions/fsenorg/changes/extra/svn/Redmine.pm . --- extra/svn/Redmine.pm | 120 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/extra/svn/Redmine.pm b/extra/svn/Redmine.pm index c96b248c58..e1e1e171aa 100644 --- a/extra/svn/Redmine.pm +++ b/extra/svn/Redmine.pm @@ -93,6 +93,84 @@ S And you need to upgrade at least reposman.rb (after r860). +=head1 GIT SMART HTTP SUPPORT + +Git's smart HTTP protocol (available since Git 1.7.0) will not work with the +above settings. Redmine.pm normally does access control depending on the HTTP +method used: read-only methods are OK for everyone in public projects and +members with read rights in private projects. The rest require membership with +commit rights in the project. + +However, this scheme doesn't work for Git's smart HTTP protocol, as it will use +POST even for a simple clone. Instead, read-only requests must be detected using +the full URL (including the query string): anything that doesn't belong to the +git-receive-pack service is read-only. + +To activate this mode of operation, add this line inside your +block: + + RedmineGitSmartHttp yes + +Here's a sample Apache configuration which integrates git-http-backend with +a MySQL database and this new option: + + SetEnv GIT_PROJECT_ROOT /var/www/git/ + SetEnv GIT_HTTP_EXPORT_ALL + ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ + + Order allow,deny + Allow from all + + AuthType Basic + AuthName Git + Require valid-user + + PerlAccessHandler Apache::Authn::Redmine::access_handler + PerlAuthenHandler Apache::Authn::Redmine::authen_handler + # for mysql + RedmineDSN "DBI:mysql:database=redmine;host=127.0.0.1" + RedmineDbUser "redmine" + RedmineDbPass "xxx" + RedmineGitSmartHttp yes + + +Make sure that all the names of the repositories under /var/www/git/ match +exactly the identifier for some project: /var/www/git/myproject.git won't work, +due to the way this module extracts the identifier from the URL. +/var/www/git/myproject will work, though. You can put both bare and non-bare +repositories in /var/www/git, though bare repositories are strongly +recommended. You should create them with the rights of the user running Redmine, +like this: + + cd /var/www/git + sudo -u user-running-redmine mkdir myproject + cd myproject + sudo -u user-running-redmine git init --bare + +Once you have activated this option, you have three options when cloning a +repository: + +- Cloning using "http://user@host/git/repo" works, but will ask for the password + all the time. + +- Cloning with "http://user:pass@host/git/repo" does not have this problem, but + this could reveal accidentally your password to the console in some versions + of Git, and you would have to ensure that .git/config is not readable except + by the owner for each of your projects. + +- Use "http://host/git/repo", and store your credentials in the ~/.netrc + file. This is the recommended solution, as you only have one file to protect + and passwords will not be leaked accidentally to the console. + + IMPORTANT NOTE: It is *very important* that the file cannot be read by other + users, as it will contain your password in cleartext. To create the file, you + can use the following commands, replacing yourhost, youruser and yourpassword + with the right values: + + touch ~/.netrc + chmod 600 ~/.netrc + echo -e "machine yourhost\nlogin youruser\npassword yourpassword" > ~/.netrc + =cut use strict; @@ -142,6 +220,11 @@ my @directives = ( args_how => TAKE1, errmsg => 'RedmineCacheCredsMax must be decimal number', }, + { + name => 'RedmineGitSmartHttp', + req_override => OR_AUTHCFG, + args_how => TAKE1, + }, ); sub RedmineDSN { @@ -178,6 +261,17 @@ sub RedmineCacheCredsMax { } } +sub RedmineGitSmartHttp { + my ($self, $parms, $arg) = @_; + $arg = lc $arg; + + if ($arg eq "yes" || $arg eq "true") { + $self->{RedmineGitSmartHttp} = 1; + } else { + $self->{RedmineGitSmartHttp} = 0; + } +} + sub trim { my $string = shift; $string =~ s/\s{2,}/ /g; @@ -194,6 +288,23 @@ Apache2::Module::add(__PACKAGE__, \@directives); my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; +sub request_is_read_only { + my ($r) = @_; + my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config); + + # Do we use Git's smart HTTP protocol, or not? + if (defined $cfg->{RedmineGitSmartHttp} and $cfg->{RedmineGitSmartHttp}) { + my $uri = $r->unparsed_uri; + my $location = $r->location; + my $is_read_only = $uri !~ m{^$location/*[^/]+/+(info/refs\?service=)?git\-receive\-pack$}o; + return $is_read_only; + } else { + # Old behaviour: check the HTTP method + my $method = $r->method; + return defined $read_only_methods{$method}; + } +} + sub access_handler { my $r = shift; @@ -202,8 +313,7 @@ sub access_handler { return FORBIDDEN; } - my $method = $r->method; - return OK unless defined $read_only_methods{$method}; + return OK unless request_is_read_only($r); my $project_id = get_project_identifier($r); @@ -320,7 +430,7 @@ sub is_member { unless ($auth_source_id) { my $method = $r->method; - if ($hashed_password eq $pass_digest && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { + if ($hashed_password eq $pass_digest && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) { $ret = 1; last; } @@ -338,9 +448,7 @@ sub is_member { bindpw => $rowldap[4] ? $rowldap[4] : "", filter => "(".$rowldap[6]."=%s)" ); - my $method = $r->method; - $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/)); - + $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/)); } $sthldap->finish(); undef $sthldap; From 0ace77081df7e23441a2534126045c341b4b640b Mon Sep 17 00:00:00 2001 From: Stephan Eckardt Date: Thu, 24 Mar 2011 16:14:10 +0100 Subject: [PATCH 21/42] Backported changes to encloseSelection from latest DotClear release, less bugs in IE8 --- public/javascripts/jstoolbar/jstoolbar.js | 181 ++++++++++------------ 1 file changed, 79 insertions(+), 102 deletions(-) diff --git a/public/javascripts/jstoolbar/jstoolbar.js b/public/javascripts/jstoolbar/jstoolbar.js index 3c3a3433f5..bfef733e01 100644 --- a/public/javascripts/jstoolbar/jstoolbar.js +++ b/public/javascripts/jstoolbar/jstoolbar.js @@ -242,108 +242,85 @@ jsToolBar.prototype = { this.encloseSelection(stag,etag); }, - - encloseLineSelection: function(prefix, suffix, fn) { - this.textarea.focus(); - - prefix = prefix || ''; - suffix = suffix || ''; - - var start, end, sel, scrollPos, subst, res; - - if (typeof(document["selection"]) != "undefined") { - sel = document.selection.createRange().text; - } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { - start = this.textarea.selectionStart; - end = this.textarea.selectionEnd; - scrollPos = this.textarea.scrollTop; - // go to the start of the line - start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; - // go to the end of the line - end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length; - sel = this.textarea.value.substring(start, end); - } - - if (sel.match(/ $/)) { // exclude ending space char, if any - sel = sel.substring(0, sel.length - 1); - suffix = suffix + " "; - } - - if (typeof(fn) == 'function') { - res = (sel) ? fn.call(this,sel) : fn(''); - } else { - res = (sel) ? sel : ''; - } - - subst = prefix + res + suffix; - - if (typeof(document["selection"]) != "undefined") { - document.selection.createRange().text = subst; - var range = this.textarea.createTextRange(); - range.collapse(false); - range.move('character', -suffix.length); - range.select(); - } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { - this.textarea.value = this.textarea.value.substring(0, start) + subst + - this.textarea.value.substring(end); - if (sel) { - this.textarea.setSelectionRange(start + subst.length, start + subst.length); - } else { - this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); - } - this.textarea.scrollTop = scrollPos; - } - }, - - encloseSelection: function(prefix, suffix, fn) { - this.textarea.focus(); - - prefix = prefix || ''; - suffix = suffix || ''; - - var start, end, sel, scrollPos, subst, res; - - if (typeof(document["selection"]) != "undefined") { - sel = document.selection.createRange().text; - } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { - start = this.textarea.selectionStart; - end = this.textarea.selectionEnd; - scrollPos = this.textarea.scrollTop; - sel = this.textarea.value.substring(start, end); - } - - if (sel.match(/ $/)) { // exclude ending space char, if any - sel = sel.substring(0, sel.length - 1); - suffix = suffix + " "; - } - - if (typeof(fn) == 'function') { - res = (sel) ? fn.call(this,sel) : fn(''); - } else { - res = (sel) ? sel : ''; - } - - subst = prefix + res + suffix; - - if (typeof(document["selection"]) != "undefined") { - document.selection.createRange().text = subst; - var range = this.textarea.createTextRange(); - range.collapse(false); - range.move('character', -suffix.length); - range.select(); -// this.textarea.caretPos -= suffix.length; - } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { - this.textarea.value = this.textarea.value.substring(0, start) + subst + - this.textarea.value.substring(end); - if (sel) { - this.textarea.setSelectionRange(start + subst.length, start + subst.length); - } else { - this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); - } - this.textarea.scrollTop = scrollPos; - } - }, - + + encloseLineSelection: function (prefix, suffix, fn) { + this.textarea.focus(); + prefix = prefix || ''; + suffix = suffix || ''; + var start, end, sel, scrollPos, subst, res; + if (typeof(document["selection"]) != "undefined") { + sel = document.selection.createRange().text; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + scrollPos = this.textarea.scrollTop; + // go to the start of the line + start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; + // go to the end of the line + end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length; + sel = this.textarea.value.substring(start, end); + } + if (sel.match(/ $/)) { + sel = sel.substring(0, sel.length - 1); + suffix = suffix + " "; + } + if (typeof(fn) == 'function') { + res = (sel) ? fn.call(this, sel) : fn(''); + } else { + res = (sel) ? sel : ''; + } + subst = prefix + res + suffix; + if (typeof(document["selection"]) != "undefined") { + var range = document.selection.createRange().text = subst; + this.textarea.caretPos -= suffix.length; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end); + if (sel) { + this.textarea.setSelectionRange(start + subst.length, start + subst.length); + } else { + this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); + } + this.textarea.scrollTop = scrollPos; + } + }, + + encloseSelection: function (prefix, suffix, fn) { + this.textarea.focus(); + prefix = prefix || ''; + suffix = suffix || ''; + var start, end, sel, scrollPos, subst, res; + if (typeof(document["selection"]) != "undefined") { + sel = document.selection.createRange().text; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + scrollPos = this.textarea.scrollTop; + sel = this.textarea.value.substring(start, end); + } + if (sel.match(/ $/)) { + sel = sel.substring(0, sel.length - 1); + suffix = suffix + " "; + } + if (typeof(fn) == 'function') { + res = (sel) ? fn.call(this, sel) : fn(''); + } else { + res = (sel) ? sel : ''; + } + subst = prefix + res + suffix; + if (typeof(document["selection"]) != "undefined") { + var range = document.selection.createRange().text = subst; + this.textarea.caretPos -= suffix.length; + } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { + this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end); + if (sel) { + this.textarea.setSelectionRange(start + subst.length, start + subst.length); + } else { + this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); + } + this.textarea.scrollTop = scrollPos; + } + }, + stripBaseURL: function(url) { if (this.base_url != '') { var pos = url.indexOf(this.base_url); From 9e04bf83c05428b83c5b842cef7f3249eabd979e Mon Sep 17 00:00:00 2001 From: Stephan Eckardt Date: Thu, 24 Mar 2011 19:35:49 +0100 Subject: [PATCH 22/42] Fix jstoolbar to make buttons for h1, h2,
 etc.
 work in IE8

---
 public/javascripts/jstoolbar/jstoolbar.js | 26 +++++++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/public/javascripts/jstoolbar/jstoolbar.js b/public/javascripts/jstoolbar/jstoolbar.js
index bfef733e01..dbc973fed4 100644
--- a/public/javascripts/jstoolbar/jstoolbar.js
+++ b/public/javascripts/jstoolbar/jstoolbar.js
@@ -249,7 +249,29 @@ jsToolBar.prototype = {
       suffix = suffix || '';
       var start, end, sel, scrollPos, subst, res;
       if (typeof(document["selection"]) != "undefined") {
-          sel = document.selection.createRange().text;
+        // just makes it work in IE8 somehow
+        var range = document.selection.createRange();
+        var bookmark = range.getBookmark();
+        var origParent = range.parentElement();
+        // we move the starting point of the selection to the last newline
+        try {
+          while (range.text[0] != "\n" && range.text[0] != "\r") {
+            bookmark = range.getBookmark();
+            range.moveStart("character", -1);
+            if (origParent != range.parentElement()) {
+              throw "Outside of Textarea";
+            }
+          }
+          range.moveStart("character", 1);
+        } catch(err) {
+          if (err == "Outside of Textarea")
+            range.moveToBookmark(bookmark);
+          else
+            throw err;
+        }
+        if (range.text.match(/ $/))
+          range.moveEnd("character", -1);
+        sel = range.text;
       } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
           start = this.textarea.selectionStart;
           end = this.textarea.selectionEnd;
@@ -271,7 +293,7 @@ jsToolBar.prototype = {
       }
       subst = prefix + res + suffix;
       if (typeof(document["selection"]) != "undefined") {
-          var range = document.selection.createRange().text = subst;
+          range.text = subst;
           this.textarea.caretPos -= suffix.length;
       } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
           this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end);

From 8cfe498bd60cdb7fab3f953e1e14aed004361856 Mon Sep 17 00:00:00 2001
From: Holger Just 
Date: Wed, 23 Mar 2011 10:57:23 +0100
Subject: [PATCH 23/42] [#230] Make loadpath of themes configurable

Based on a987b74ed14 by meineerde
---
 config/configuration.yml.example | 14 +++++++++++++-
 lib/redmine/themes.rb            | 21 ++++++++++++++++-----
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/config/configuration.yml.example b/config/configuration.yml.example
index 628d279998..0858d1ca82 100644
--- a/config/configuration.yml.example
+++ b/config/configuration.yml.example
@@ -98,7 +98,19 @@ default:
   # attachments_storage_path: /var/chiliproject/files
   # attachments_storage_path: D:/chiliproject/files
   attachments_storage_path:
-  
+
+  # Path to the directories where themes are stored.
+  # Can be an absolute path or one relative to your ChiliProject instance.
+  # You can configure multiple paths.
+  # The default is the 'public/themes' directory in your ChiliProject instance.
+  # Examples:
+  # themes_storage_paths: public/themes
+  # themes_storage_paths:
+  #   - public/themes
+  #   - /opt/themes
+  #   - D:/chiliproject/themes
+  themes_storage_path:
+
   # Configuration of the autologin cookie.
   # autologin_cookie_name: the name of the cookie (default: autologin)
   # autologin_cookie_path: the cookie path (default: /)
diff --git a/lib/redmine/themes.rb b/lib/redmine/themes.rb
index 77833cb35e..697b4a3172 100644
--- a/lib/redmine/themes.rb
+++ b/lib/redmine/themes.rb
@@ -89,11 +89,22 @@ def assets(dir, ext)
     private
     
     def self.scan_themes
-      dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f|
-        # A theme should at least override application.css
-        File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
-      end
-      dirs.collect {|dir| Theme.new(dir)}.sort
+      theme_paths.inject([]) do |themes, path|
+        dirs = Dir.glob(File.join(path, '*')).select do |f|
+          # A theme should at least override application.css
+          File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
+        end
+        themes += dirs.collect { |dir| Theme.new(dir) }
+      end.sort
+    end
+
+    def self.theme_paths
+      paths = Redmine::Configuration['themes_storage_path']
+      paths = [paths] unless paths.is_a?(Array)
+      paths.flatten!; paths.compact!
+
+      paths = ["#{Rails.public_path}/themes"] if paths.empty?
+      paths.collect { |p| File.expand_path(p, Rails.root) }
     end
   end
 end

From 2c67f6103b0e8207e0fe9b86fcab852abea18a53 Mon Sep 17 00:00:00 2001
From: Stephan Eckardt 
Date: Thu, 10 Mar 2011 13:46:48 +0100
Subject: [PATCH 24/42] replaced auth_source_id.nil? with
 change_password_allowed? in users controller

---
 app/controllers/users_controller.rb | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 2a213f5e85..c7dadf0404 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -92,7 +92,7 @@ def create
     @user.safe_attributes = params[:user]
     @user.admin = params[:user][:admin] || false
     @user.login = params[:user][:login]
-    @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
+    @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] if @user.change_password_allowed?
 
     # TODO: Similar to My#account
     @user.pref.attributes = params[:pref]
@@ -135,10 +135,10 @@ def edit
   def update
     @user.admin = params[:user][:admin] if params[:user][:admin]
     @user.login = params[:user][:login] if params[:user][:login]
-    if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
+    @user.safe_attributes = params[:user]
+    if params[:user][:password].present? && @user.change_password_allowed?
       @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
     end
-    @user.safe_attributes = params[:user]
     # Was the account actived ? (do it before User#save clears the change)
     was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
     # TODO: Similar to My#account
@@ -151,7 +151,7 @@ def update
 
       if was_activated
         Mailer.deliver_account_activated(@user)
-      elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.auth_source_id.nil?
+      elsif @user.active? && params[:send_information] && !params[:user][:password].blank? && @user.change_password_allowed?
         Mailer.deliver_account_information(@user, params[:user][:password])
       end
       

From 1aa20b17b4579df6b43f3660da4c7f3f47db5571 Mon Sep 17 00:00:00 2001
From: Eric Davis 
Date: Thu, 24 Mar 2011 14:22:21 -0700
Subject: [PATCH 25/42] [#302] Protect ApplicationController methods so they
 are not considered actions

---
 app/controllers/application_controller.rb     |  3 +++
 .../functional/application_controller_test.rb | 26 +++++++++----------
 2 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c8d32aa1af..10c7b6533f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -19,6 +19,9 @@
 require 'cgi'
 
 class ApplicationController < ActionController::Base
+
+  protected
+  
   include Redmine::I18n
 
   layout 'base'
diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb
index 57f3e091cd..5f94a96e64 100644
--- a/test/functional/application_controller_test.rb
+++ b/test/functional/application_controller_test.rb
@@ -47,55 +47,55 @@ def test_call_hook_mixed_in
   context "test_api_offset_and_limit" do
     context "without params" do
       should "return 0, 25" do
-        assert_equal [0, 25], @controller.api_offset_and_limit({})
+        assert_equal [0, 25], @controller.send(:api_offset_and_limit, {})
       end
     end
     
     context "with limit" do
       should "return 0, limit" do
-        assert_equal [0, 30], @controller.api_offset_and_limit({:limit => 30})
+        assert_equal [0, 30], @controller.send(:api_offset_and_limit, {:limit => 30})
       end
       
       should "not exceed 100" do
-        assert_equal [0, 100], @controller.api_offset_and_limit({:limit => 120})
+        assert_equal [0, 100], @controller.send(:api_offset_and_limit, {:limit => 120})
       end
 
       should "not be negative" do
-        assert_equal [0, 25], @controller.api_offset_and_limit({:limit => -10})
+        assert_equal [0, 25], @controller.send(:api_offset_and_limit, {:limit => -10})
       end
     end
     
     context "with offset" do
       should "return offset, 25" do
-        assert_equal [10, 25], @controller.api_offset_and_limit({:offset => 10})
+        assert_equal [10, 25], @controller.send(:api_offset_and_limit, {:offset => 10})
       end
 
       should "not be negative" do
-        assert_equal [0, 25], @controller.api_offset_and_limit({:offset => -10})
+        assert_equal [0, 25], @controller.send(:api_offset_and_limit, {:offset => -10})
       end
       
       context "and limit" do
         should "return offset, limit" do
-          assert_equal [10, 50], @controller.api_offset_and_limit({:offset => 10, :limit => 50})
+          assert_equal [10, 50], @controller.send(:api_offset_and_limit, {:offset => 10, :limit => 50})
         end
       end
     end
     
     context "with page" do
       should "return offset, 25" do
-        assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1})
-        assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3})
+        assert_equal [0, 25], @controller.send(:api_offset_and_limit, {:page => 1})
+        assert_equal [50, 25], @controller.send(:api_offset_and_limit, {:page => 3})
       end
 
       should "not be negative" do
-        assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0})
-        assert_equal [0, 25], @controller.api_offset_and_limit({:page => -2})
+        assert_equal [0, 25], @controller.send(:api_offset_and_limit, {:page => 0})
+        assert_equal [0, 25], @controller.send(:api_offset_and_limit, {:page => -2})
       end
       
       context "and limit" do
         should "return offset, limit" do
-          assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100})
-          assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100})
+          assert_equal [0, 100], @controller.send(:api_offset_and_limit, {:page => 1, :limit => 100})
+          assert_equal [200, 100], @controller.send(:api_offset_and_limit, {:page => 3, :limit => 100})
         end
       end
     end

From 3edbab734b76ab3938b384ba1c77f8a162818ff1 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Thu, 10 Feb 2011 18:13:10 +0000
Subject: [PATCH 26/42] scm: subversion: prepare version string unit lib test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4803 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 .../scm/adapters/subversion_adapter_test.rb   | 28 +++++++++++++------
 1 file changed, 20 insertions(+), 8 deletions(-)

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 99e8ceb220..966a829204 100644
--- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb
@@ -17,15 +17,27 @@
 
 require File.expand_path('../../../../../../test_helper', __FILE__)
 
-class SubversionAdapterTest < ActiveSupport::TestCase
+begin
+  require 'mocha'
+
+  class SubversionAdapterTest < ActiveSupport::TestCase
   
-  if repository_configured?('subversion')
-    def test_client_version
-      v = Redmine::Scm::Adapters::SubversionAdapter.client_version
-      assert v.is_a?(Array)
+    if repository_configured?('subversion')
+      def setup
+      end
+
+      def test_client_version
+        v = Redmine::Scm::Adapters::SubversionAdapter.client_version
+        assert v.is_a?(Array)
+      end
+    else
+      puts "Subversion test repository NOT FOUND. Skipping unit tests !!!"
+      def test_fake; assert true end
     end
-  else
-    puts "Subversion test repository NOT FOUND. Skipping unit tests !!!"
-    def test_fake; assert true end
+  end
+
+rescue LoadError
+  class SubversionMochaFake < ActiveSupport::TestCase
+    def test_fake; assert(false, "Requires mocha to run those tests")  end
   end
 end

From 09e0aa32b8b80351dadd66f05e50b9f4c8d18fed Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 15:29:56 +0000
Subject: [PATCH 27/42] scm: cvs: code clean up.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4804 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 lib/redmine/scm/adapters/cvs_adapter.rb | 48 ++++++++++++-------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb
index 5c81e639f5..803af604cc 100644
--- a/lib/redmine/scm/adapters/cvs_adapter.rb
+++ b/lib/redmine/scm/adapters/cvs_adapter.rb
@@ -55,7 +55,7 @@ def info
         def get_previous_revision(revision)
           CvsRevisionHelper.new(revision).prevRev
         end
-    
+
         # Returns an Entries collection
         # or nil if the given path doesn't exist in the repository
         # this method is used by the repository-browser (aka LIST)
@@ -70,7 +70,7 @@ def entries(path=nil, identifier=nil)
             io.each_line(){|line|
               fields=line.chop.split('/',-1)
               logger.debug(">>InspectLine #{fields.inspect}")
-              
+
               if fields[0]!="D"
                 entries << Entry.new({:name => fields[-5],
                   #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
@@ -96,17 +96,17 @@ def entries(path=nil, identifier=nil)
           end
           return nil if $? && $?.exitstatus != 0
           entries.sort_by_name
-        end  
+        end
 
         STARTLOG="----------------------------"
         ENDLOG  ="============================================================================="
-        
+
         # Returns all revisions found between identifier_from and identifier_to
         # in the repository. both identifier have to be dates or nil.
         # these method returns nothing but yield every result in block
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
           logger.debug " revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
-          
+
           path_with_project="#{url}#{with_leading_slash(path)}"
           cmd = "#{CVS_BIN} -d #{shell_quote root_url} rlog"
           cmd << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from
@@ -123,7 +123,7 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
             file_state=nil
             branch_map=nil
             
-            io.each_line() do |line|            
+            io.each_line() do |line|
               
               if state!="revision" && /^#{ENDLOG}/ =~ line
                 commit_log=String.new
@@ -162,9 +162,9 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
                 end
                 next
               elsif state=="revision"
-                if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line               
+                if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
                   if revision
-                    
+
                     revHelper=CvsRevisionHelper.new(revision)
                     revBranch="HEAD"
                     
@@ -176,7 +176,7 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
                     
                     logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
                     
-                    yield Revision.new({                    
+                    yield Revision.new({
                       :time => date,
                       :author => author,
                       :message=>commit_log.chomp,
@@ -188,9 +188,9 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
                         :kind=>'file',
                         :action=>file_state
                       }]
-                    })                 
+                    })
                   end
-    
+
                   commit_log=String.new
                   revision=nil
                   
@@ -199,7 +199,7 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
                   end
                   next
                 end
-                  
+
                 if /^branches: (.+)$/ =~ line
                   #TODO: version.branch = $1
                 elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
@@ -216,16 +216,16 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
                   #                  version.line_minus = linechanges[2]
                   #                else
                   #                  version.line_plus  = 0
-                  #                  version.line_minus = 0     
-                  #                end              
-                else            
+                  #                  version.line_minus = 0
+                  #                end
+                else
                   commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
-                end 
-              end 
+                end
+              end
             end
           end
-        end  
-        
+        end
+
         def diff(path, identifier_from, identifier_to=nil)
           logger.debug " diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
           path_with_project="#{url}#{with_leading_slash(path)}"
@@ -238,8 +238,8 @@ def diff(path, identifier_from, identifier_to=nil)
           end
           return nil if $? && $?.exitstatus != 0
           diff
-        end  
-        
+        end
+
         def cat(path, identifier=nil)
           identifier = (identifier) ? identifier : "HEAD"
           logger.debug " cat path:'#{path}',identifier #{identifier}"
@@ -253,7 +253,7 @@ def cat(path, identifier=nil)
           end
           return nil if $? && $?.exitstatus != 0
           cat
-        end  
+        end
 
         def annotate(path, identifier=nil)
           identifier = (identifier) ? identifier.to_i : "HEAD"
@@ -270,9 +270,9 @@ def annotate(path, identifier=nil)
           return nil if $? && $?.exitstatus != 0
           blame
         end
-         
+
         private
-        
+
         # Returns the root url without the connexion string
         # :pserver:anonymous@foo.bar:/path => /path
         # :ext:cvsservername:/path => /path

From dd798edd0458e6b9bf6c2dac68b2a0faf42d5ad9 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 15:30:22 +0000
Subject: [PATCH 28/42] scm: git: prepare version string unit lib test and git
 log encoding (#3396).

This file includes UTF-8 literal.
We need to consider Ruby 1.9 compatibity.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4805 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 .../redmine/scm/adapters/git_adapter_test.rb  | 143 ++++++++++--------
 1 file changed, 82 insertions(+), 61 deletions(-)

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 bc9959dc6d..972363f28f 100644
--- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
@@ -1,71 +1,92 @@
 # encoding: utf-8
 
+# This file includes UTF-8 "Felix Schäfer".
+# We need to consider Ruby 1.9 compatibity.
+
 require File.expand_path('../../../../../../test_helper', __FILE__)
+begin
+  require 'mocha'
 
-class GitAdapterTest < ActiveSupport::TestCase
-  REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
+  class GitAdapterTest < ActiveSupport::TestCase
+    REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
 
-  if File.directory?(REPOSITORY_PATH)  
-    def setup
-      @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH)
-    end
+    if File.directory?(REPOSITORY_PATH)
+      def setup
+        @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH)
+      end
 
-    def test_branches
-      assert_equal @adapter.branches, ['master', 'test_branch']
-    end
+      def test_branches
+        assert_equal @adapter.branches, ['master', 'test_branch']
+      end
 
-    def test_getting_all_revisions
-      assert_equal 15, @adapter.revisions('',nil,nil,:all => true).length
-    end
-    
-    def test_getting_certain_revisions
-      assert_equal 1, @adapter.revisions('','899a15d^','899a15d').length
-    end
-    
-    def test_getting_revisions_with_spaces_in_filename
-      assert_equal 1, @adapter.revisions("filemane with spaces.txt", nil, nil, :all => true).length
-    end
-    
-    def test_getting_revisions_with_leading_and_trailing_spaces_in_filename
-      assert_equal " filename with a leading space.txt ", @adapter.revisions(" filename with a leading space.txt ", nil, nil, :all => true)[0].paths[0][:path]
-    end
-    
-    def test_getting_entries_with_leading_and_trailing_spaces_in_filename
-      assert_equal " filename with a leading space.txt ", @adapter.entries('', '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c')[3].name
-    end
-    
-    def test_annotate
-      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 "7234cb2750b63f47bff735edc50a1c0a433c2518", annotate.revisions[4].identifier
-      assert_equal "jsmith", annotate.revisions[4].author
-    end
-    
-    def test_annotate_moved_file
-      annotate = @adapter.annotate('renamed_test.txt')
-      assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
-      assert_equal 2, annotate.lines.size
-    end
-    
-    def test_last_rev
-      last_rev = @adapter.lastrev("README", "4f26664364207fa8b1af9f8722647ab2d4ac5d43")
-      assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.scmid
-      assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.identifier
-      assert_equal "Adam Soltys ", last_rev.author
-      assert_equal "2009-06-24 05:27:38".to_time, last_rev.time
-    end
-    
-    def test_last_rev_with_spaces_in_filename
-      last_rev = @adapter.lastrev("filemane with spaces.txt", "ed5bb786bbda2dee66a2d50faf51429dbc043a7b")
-      assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid
-      assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier
-      assert_equal "Felix Schäfer ", last_rev.author
-      assert_equal "2010-09-18 19:59:46".to_time, last_rev.time
+      def test_getting_all_revisions
+        assert_equal 15, @adapter.revisions('',nil,nil,:all => true).length
+      end
+
+      def test_getting_certain_revisions
+        assert_equal 1, @adapter.revisions('','899a15d^','899a15d').length
+      end
+
+      def test_getting_revisions_with_spaces_in_filename
+        assert_equal 1, @adapter.revisions("filemane with spaces.txt",
+                                           nil, nil, :all => true).length
+      end
+
+      def test_getting_revisions_with_leading_and_trailing_spaces_in_filename
+        assert_equal " filename with a leading space.txt ",
+           @adapter.revisions(" filename with a leading space.txt ",
+                               nil, nil, :all => true)[0].paths[0][:path]
+      end
+
+      def test_getting_entries_with_leading_and_trailing_spaces_in_filename
+        assert_equal " filename with a leading space.txt ",
+           @adapter.entries('',
+                   '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c')[3].name
+      end
+
+      def test_annotate
+        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 "7234cb2750b63f47bff735edc50a1c0a433c2518",
+                      annotate.revisions[4].identifier
+        assert_equal "jsmith", annotate.revisions[4].author
+      end
+
+      def test_annotate_moved_file
+        annotate = @adapter.annotate('renamed_test.txt')
+        assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
+        assert_equal 2, annotate.lines.size
+      end
+
+      def test_last_rev
+        last_rev = @adapter.lastrev("README",
+                                    "4f26664364207fa8b1af9f8722647ab2d4ac5d43")
+        assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.scmid
+        assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.identifier
+        assert_equal "Adam Soltys ", last_rev.author
+        assert_equal "2009-06-24 05:27:38".to_time, last_rev.time
+      end
+
+      def test_last_rev_with_spaces_in_filename
+        last_rev = @adapter.lastrev("filemane with spaces.txt",
+                                    "ed5bb786bbda2dee66a2d50faf51429dbc043a7b")
+        assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid
+        assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier
+        assert_equal "Felix Schäfer ",
+                       last_rev.author
+        assert_equal "2010-09-18 19:59:46".to_time, last_rev.time
+      end
+    else
+      puts "Git test repository NOT FOUND. Skipping unit tests !!!"
+      def test_fake; assert true end
     end
-  else
-    puts "Git test repository NOT FOUND. Skipping unit tests !!!"
-    def test_fake; assert true end
+  end
+
+rescue LoadError
+  class GitMochaFake < ActiveSupport::TestCase
+    def test_fake; assert(false, "Requires mocha to run those tests")  end
   end
 end
+

From 911fbefb453ea4bfa41c60f14fc8d0b64cb0f7e2 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 15:30:45 +0000
Subject: [PATCH 29/42] scm: change gunzip to tar -z option for scm repository
 setup in lib/tasks/testing.rake.

Pipe does not work on Mingw Ruby.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4806 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 lib/tasks/testing.rake | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/tasks/testing.rake b/lib/tasks/testing.rake
index 1f43a11568..266dcf9ca4 100644
--- a/lib/tasks/testing.rake
+++ b/lib/tasks/testing.rake
@@ -51,7 +51,8 @@ namespace :test do
       (supported_scms - [:subversion, :mercurial]).each do |scm|
         desc "Creates a test #{scm} repository"
         task scm => :create_dir do
-          system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test"
+          # system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test"
+          system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz"
         end
       end
       

From 6b6a44c83197749908604af45d2dc32317daa2a4 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 15:31:11 +0000
Subject: [PATCH 30/42] scm: bazaar: prepare version string unit lib test.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4807 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 .../scm/adapters/bazaar_adapter_test.rb       | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)
 create mode 100644 test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb

diff --git a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
new file mode 100644
index 0000000000..2fc8f49250
--- /dev/null
+++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
@@ -0,0 +1,26 @@
+require File.expand_path('../../../../../../test_helper', __FILE__)
+begin
+  require 'mocha'
+
+  class BazaarAdapterTest < ActiveSupport::TestCase
+
+    REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/bazaar_repository'
+    MODULE_NAME = 'test'
+
+    if File.directory?(REPOSITORY_PATH)  
+      def setup
+        @adapter = Redmine::Scm::Adapters::BazzarAdapter.new(MODULE_NAME, REPOSITORY_PATH)
+      end
+
+    else
+      puts "Bazaar test repository NOT FOUND. Skipping unit tests !!!"
+      def test_fake; assert true end
+    end
+  end
+
+rescue LoadError
+  class BazaarMochaFake < ActiveSupport::TestCase
+    def test_fake; assert(false, "Requires mocha to run those tests")  end
+  end
+end
+

From 64835367ab73e77c9ec5d055593adbe61ccf63e3 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 15:32:45 +0000
Subject: [PATCH 31/42] scm: cvs: fix unit lib test fails on Windows.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4808 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb | 1 +
 1 file changed, 1 insertion(+)

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 e063d44fdb..b6b4b6ee8f 100644
--- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb
@@ -5,6 +5,7 @@
   class CvsAdapterTest < ActiveSupport::TestCase
     
     REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
+    REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
     MODULE_NAME = 'test'
 
     if File.directory?(REPOSITORY_PATH)  

From 3110e97038b3b2c4aeccdd6adc87b6d5dc66fcc5 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 15:44:52 +0000
Subject: [PATCH 32/42] scm: git: fix typo of unit lib test.

This file includes UTF-8 "literal".
We need to consider Ruby 1.9 compatibility.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4809 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 test/unit/lib/redmine/scm/adapters/git_adapter_test.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 972363f28f..049296d9cb 100644
--- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb
@@ -1,7 +1,7 @@
 # encoding: utf-8
 
 # This file includes UTF-8 "Felix Schäfer".
-# We need to consider Ruby 1.9 compatibity.
+# We need to consider Ruby 1.9 compatibility.
 
 require File.expand_path('../../../../../../test_helper', __FILE__)
 begin

From 2b979920150a40d8e4490e33d2395b9ca36b9b26 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Fri, 11 Feb 2011 16:00:57 +0000
Subject: [PATCH 33/42] scm: bazaar: fix typo and mistakes.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4810 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

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 2fc8f49250..82372bbbd3 100644
--- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
+++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb
@@ -5,11 +5,10 @@
   class BazaarAdapterTest < ActiveSupport::TestCase
 
     REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/bazaar_repository'
-    MODULE_NAME = 'test'
 
     if File.directory?(REPOSITORY_PATH)  
       def setup
-        @adapter = Redmine::Scm::Adapters::BazzarAdapter.new(MODULE_NAME, REPOSITORY_PATH)
+        @adapter = Redmine::Scm::Adapters::BazaarAdapter.new(MODULE_NAME, REPOSITORY_PATH)
       end
 
     else

From 5356140f5f53840e7689639c2c92e65072819eab Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Barth 
Date: Sat, 12 Feb 2011 09:02:02 +0000
Subject: [PATCH 34/42] Ignore .project and .loadpath files for hg/git mirrors.
 #7497

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4813 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 .gitignore | 2 ++
 .hgignore  | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/.gitignore b/.gitignore
index ff5865c8a3..123812ccd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+/.project
+/.loadpath
 /config/additional_environment.rb
 /config/configuration.yml
 /config/database.yml
diff --git a/.hgignore b/.hgignore
index 5f41fda181..733faf9f46 100644
--- a/.hgignore
+++ b/.hgignore
@@ -1,5 +1,7 @@
 syntax: glob
 
+.project
+.loadpath
 config/additional_environment.rb
 config/configuration.yml
 config/database.yml

From 52822ad58e463e5719bc7f4db5957c75b1fd7d1f Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Sat, 12 Feb 2011 09:43:57 +0000
Subject: [PATCH 35/42] scm: cvs: fix most binary files become corrupted on
 Windows (#6090).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4815 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 lib/redmine/scm/adapters/cvs_adapter.rb | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb
index 803af604cc..1cda302506 100644
--- a/lib/redmine/scm/adapters/cvs_adapter.rb
+++ b/lib/redmine/scm/adapters/cvs_adapter.rb
@@ -249,6 +249,7 @@ def cat(path, identifier=nil)
           cmd << " -p #{shell_quote path_with_project}"
           cat = nil
           shellout(cmd) do |io|
+            io.binmode
             cat = io.read
           end
           return nil if $? && $?.exitstatus != 0

From 3d11a22624339e818067ba3839a46caccf6a0966 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Sat, 12 Feb 2011 09:44:23 +0000
Subject: [PATCH 36/42] scm: fix non ASCII filename downloaded from repo is
 broken on Internet Explorer (#7288).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4816 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/controllers/repositories_controller.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 50a6ff4a1b..74d420e5a7 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -124,7 +124,7 @@ def entry
     (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)
       # Force the download
-      send_data @content, :filename => @path.split('/').last
+      send_data @content, :filename => filename_for_content_disposition(@path.split('/').last)
     else
       # Prevent empty lines when displaying a file with Windows style eol
       @content.gsub!("\r\n", "\n")

From 6cd9bc5ca57213245d5cc85b3f20b14b1e24b91a Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Sat, 12 Feb 2011 09:44:46 +0000
Subject: [PATCH 37/42] scm: space and tab cleanup of
 app/controllers/repositories_controller.rb.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4817 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/controllers/repositories_controller.rb | 35 +++++++++++-----------
 1 file changed, 18 insertions(+), 17 deletions(-)

diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb
index 74d420e5a7..de44f1d08f 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -67,13 +67,13 @@ def committers
       redirect_to :action => 'committers', :id => @project
     end
   end
-  
+
   def destroy
     @repository.destroy
     redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
   end
-  
-  def show 
+
+  def show
     @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
 
     @entries = @repository.entries(@path, @rev)
@@ -88,7 +88,7 @@ def show
   end
 
   alias_method :browse, :show
-  
+
   def changes
     @entry = @repository.entry(@path, @rev)
     (show_error_not_found; return) unless @entry
@@ -96,23 +96,23 @@ def changes
     @properties = @repository.properties(@path, @rev)
     @changeset = @repository.find_changeset_by_name(@rev)
   end
-  
+
   def revisions
     @changeset_count = @repository.changesets.count
     @changeset_pages = Paginator.new self, @changeset_count,
-								      per_page_option,
-								      params['page']								
+                                     per_page_option,
+                                     params['page']
     @changesets = @repository.changesets.find(:all,
-						:limit  =>  @changeset_pages.items_per_page,
-						:offset =>  @changeset_pages.current.offset,
-            :include => [:user, :repository])
+                       :limit  =>  @changeset_pages.items_per_page,
+                       :offset =>  @changeset_pages.current.offset,
+                       :include => [:user, :repository])
 
     respond_to do |format|
       format.html { render :layout => false if request.xhr? }
       format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
     end
   end
-  
+
   def entry
     @entry = @repository.entry(@path, @rev)
     (show_error_not_found; return) unless @entry
@@ -122,7 +122,8 @@ def entry
 
     @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.is_binary_data? ||
+         (@entry.size && @entry.size > Setting.file_max_size_displayed.to_i.kilobyte)
       # Force the download
       send_data @content, :filename => filename_for_content_disposition(@path.split('/').last)
     else
@@ -135,7 +136,7 @@ def entry
   def annotate
     @entry = @repository.entry(@path, @rev)
     (show_error_not_found; return) unless @entry
-    
+
     @annotate = @repository.scm.annotate(@path, @rev)
     (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
     @changeset = @repository.find_changeset_by_name(@rev)
@@ -153,7 +154,7 @@ def revision
   rescue ChangesetNotFound
     show_error_not_found
   end
-  
+
   def diff
     if params[:format] == 'diff'
       @diff = @repository.diff(@path, @rev, @rev_to)
@@ -185,11 +186,11 @@ def diff
     end
   end
 
-  def stats  
+  def stats
   end
-  
+
   def graph
-    data = nil    
+    data = nil
     case params[:graph]
     when "commits_per_month"
       data = graph_commits_per_month(@repository)

From 6d0fcf556df746a9cd9bcb50a3e01ce68621f432 Mon Sep 17 00:00:00 2001
From: Toshi MARUYAMA 
Date: Mon, 14 Feb 2011 06:14:34 +0000
Subject: [PATCH 38/42] scm: use shell quote for scm command at adapter level
 (#7517, #4273).
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

"C:\Program Files\TortoiseHg\hg.exe" can be used in config/configuration.yml.

In Ruby 1.9 IO.popen, if cmd is an Array of String,
it will be used as the subprocess‘s argv bypassing a shell.

See http://www.ruby-doc.org/core/classes/IO.html

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4821 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 lib/redmine/scm/adapters/abstract_adapter.rb  | 32 +++++++++----
 lib/redmine/scm/adapters/bazaar_adapter.rb    | 38 +++++++++------
 lib/redmine/scm/adapters/cvs_adapter.rb       | 30 ++++++++----
 lib/redmine/scm/adapters/darcs_adapter.rb     | 38 +++++++++------
 .../scm/adapters/filesystem_adapter.rb        |  7 ++-
 lib/redmine/scm/adapters/git_adapter.rb       | 34 +++++++++----
 lib/redmine/scm/adapters/mercurial_adapter.rb | 30 ++++++++----
 .../scm/adapters/subversion_adapter.rb        | 48 +++++++++++--------
 8 files changed, 168 insertions(+), 89 deletions(-)

diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb
index f98e90958f..3442276b17 100644
--- a/lib/redmine/scm/adapters/abstract_adapter.rb
+++ b/lib/redmine/scm/adapters/abstract_adapter.rb
@@ -25,6 +25,10 @@ class CommandFailed < StandardError #:nodoc:
       
       class AbstractAdapter #:nodoc:
         class << self
+          def client_command
+            ""
+          end
+
           # Returns the version of the scm client
           # Eg: [1, 5, 0] or [] if unknown
           def client_version
@@ -45,8 +49,20 @@ def client_version_string
           def client_version_above?(v, options={})
             ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
           end
+
+          def client_available
+            true
+          end
+
+          def shell_quote(str)
+            if Redmine::Platform.mswin?
+              '"' + str.gsub(/"/, '\\"') + '"'
+            else
+              "'" + str.gsub(/'/, "'\"'\"'") + "'"
+            end
+          end
         end
-                
+
         def initialize(url, root_url=nil, login=nil, password=nil)
           @url = url
           @login = login if login && !login.empty?
@@ -138,7 +154,7 @@ def with_trailling_slash(path)
           path ||= ''
           (path[-1,1] == "/") ? path : "#{path}/"
         end
-        
+
         def without_leading_slash(path)
           path ||= ''
           path.gsub(%r{^/+}, '')
@@ -148,13 +164,9 @@ def without_trailling_slash(path)
           path ||= ''
           (path[-1,1] == "/") ? path[0..-2] : path
          end
-        
+
         def shell_quote(str)
-          if Redmine::Platform.mswin?
-            '"' + str.gsub(/"/, '\\"') + '"'
-          else
-            "'" + str.gsub(/'/, "'\"'\"'") + "'"
-          end
+          self.class.shell_quote(str)
         end
 
       private
@@ -168,11 +180,11 @@ def target(path)
           base = path.match(/^\//) ? root_url : url
           shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
         end
-            
+
         def logger
           self.class.logger
         end
-        
+
         def shellout(cmd, &block)
           self.class.shellout(cmd, &block)
         end
diff --git a/lib/redmine/scm/adapters/bazaar_adapter.rb b/lib/redmine/scm/adapters/bazaar_adapter.rb
index 96ec8693d2..1a11f639a3 100644
--- a/lib/redmine/scm/adapters/bazaar_adapter.rb
+++ b/lib/redmine/scm/adapters/bazaar_adapter.rb
@@ -19,15 +19,25 @@
 
 module Redmine
   module Scm
-    module Adapters    
+    module Adapters
       class BazaarAdapter < AbstractAdapter
-      
+
         # Bazaar executable name
         BZR_BIN = Redmine::Configuration['scm_bazaar_command'] || "bzr"
-        
+
+        class << self
+          def client_command
+            @@bin    ||= BZR_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote(BZR_BIN)
+          end
+        end
+
         # Get info about the repository
         def info
-          cmd = "#{BZR_BIN} revno #{target('')}"
+          cmd = "#{self.class.sq_bin} revno #{target('')}"
           info = nil
           shellout(cmd) do |io|
             if io.read =~ %r{^(\d+)\r?$}
@@ -43,13 +53,13 @@ def info
         rescue CommandFailed
           return nil
         end
-        
+
         # Returns an Entries collection
         # or nil if the given path doesn't exist in the repository
         def entries(path=nil, identifier=nil)
           path ||= ''
           entries = Entries.new
-          cmd = "#{BZR_BIN} ls -v --show-ids"
+          cmd = "#{self.class.sq_bin} ls -v --show-ids"
           identifier = -1 unless identifier && identifier.to_i > 0 
           cmd << " -r#{identifier.to_i}" 
           cmd << " #{target(path)}"
@@ -71,13 +81,13 @@ def entries(path=nil, identifier=nil)
           logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
           entries.sort_by_name
         end
-    
+
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           path ||= ''
           identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1'
           identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
           revisions = Revisions.new
-          cmd = "#{BZR_BIN} log -v --show-ids -r#{identifier_to}..#{identifier_from} #{target(path)}"
+          cmd = "#{self.class.sq_bin} log -v --show-ids -r#{identifier_to}..#{identifier_from} #{target(path)}"
           shellout(cmd) do |io|
             revision = nil
             parsing = nil
@@ -132,7 +142,7 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           return nil if $? && $?.exitstatus != 0
           revisions
         end
-        
+
         def diff(path, identifier_from, identifier_to=nil)
           path ||= ''
           if identifier_to
@@ -143,7 +153,7 @@ def diff(path, identifier_from, identifier_to=nil)
           if identifier_from
             identifier_from = identifier_from.to_i
           end
-          cmd = "#{BZR_BIN} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
+          cmd = "#{self.class.sq_bin} diff -r#{identifier_to}..#{identifier_from} #{target(path)}"
           diff = []
           shellout(cmd) do |io|
             io.each_line do |line|
@@ -153,9 +163,9 @@ def diff(path, identifier_from, identifier_to=nil)
           #return nil if $? && $?.exitstatus != 0
           diff
         end
-        
+
         def cat(path, identifier=nil)
-          cmd = "#{BZR_BIN} cat"
+          cmd = "#{self.class.sq_bin} cat"
           cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
           cmd << " #{target(path)}"
           cat = nil
@@ -166,9 +176,9 @@ def cat(path, identifier=nil)
           return nil if $? && $?.exitstatus != 0
           cat
         end
-        
+
         def annotate(path, identifier=nil)
-          cmd = "#{BZR_BIN} annotate --all"
+          cmd = "#{self.class.sq_bin} annotate --all"
           cmd << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
           cmd << " #{target(path)}"
           blame = Annotate.new
diff --git a/lib/redmine/scm/adapters/cvs_adapter.rb b/lib/redmine/scm/adapters/cvs_adapter.rb
index 1cda302506..c3d3bf7268 100644
--- a/lib/redmine/scm/adapters/cvs_adapter.rb
+++ b/lib/redmine/scm/adapters/cvs_adapter.rb
@@ -24,7 +24,17 @@ class CvsAdapter < AbstractAdapter
 
         # CVS executable name
         CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs"
-    
+
+        class << self
+          def client_command
+            @@bin    ||= CVS_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote(CVS_BIN)
+          end
+        end
+
         # Guidelines for the input:
         #  url -> the project-path, relative to the cvsroot (eg. module name)
         #  root_url -> the good old, sometimes damned, CVSROOT
@@ -38,20 +48,20 @@ def initialize(url, root_url=nil, login=nil, password=nil)
           raise CommandFailed if root_url.blank?
           @root_url = root_url
         end
-        
+
         def root_url
           @root_url
         end
-        
+
         def url
           @url
         end
-        
+
         def info
           logger.debug " info"
           Info.new({:root_url => @root_url, :lastrev => nil})
         end
-        
+
         def get_previous_revision(revision)
           CvsRevisionHelper.new(revision).prevRev
         end
@@ -63,7 +73,7 @@ def entries(path=nil, identifier=nil)
           logger.debug " entries '#{path}' with identifier '#{identifier}'"
           path_with_project="#{url}#{with_leading_slash(path)}"
           entries = Entries.new
-          cmd = "#{CVS_BIN} -d #{shell_quote root_url} rls -e"
+          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|
@@ -108,7 +118,7 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
           logger.debug " revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
 
           path_with_project="#{url}#{with_leading_slash(path)}"
-          cmd = "#{CVS_BIN} -d #{shell_quote root_url} rlog"
+          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|
@@ -229,7 +239,7 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &blo
         def diff(path, identifier_from, identifier_to=nil)
           logger.debug " diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
           path_with_project="#{url}#{with_leading_slash(path)}"
-          cmd = "#{CVS_BIN} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
+          cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rdiff -u -r#{identifier_to} -r#{identifier_from} #{shell_quote path_with_project}"
           diff = []
           shellout(cmd) do |io|
             io.each_line do |line|
@@ -244,7 +254,7 @@ def cat(path, identifier=nil)
           identifier = (identifier) ? identifier : "HEAD"
           logger.debug " cat path:'#{path}',identifier #{identifier}"
           path_with_project="#{url}#{with_leading_slash(path)}"
-          cmd = "#{CVS_BIN} -d #{shell_quote root_url} co"
+          cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} co"
           cmd << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
           cmd << " -p #{shell_quote path_with_project}"
           cat = nil
@@ -260,7 +270,7 @@ def annotate(path, identifier=nil)
           identifier = (identifier) ? identifier.to_i : "HEAD"
           logger.debug " annotate path:'#{path}',identifier #{identifier}"
           path_with_project="#{url}#{with_leading_slash(path)}"
-          cmd = "#{CVS_BIN} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
+          cmd = "#{self.class.sq_bin} -d #{shell_quote root_url} rannotate -r#{identifier} #{shell_quote path_with_project}"
           blame = Annotate.new
           shellout(cmd) do |io|
             io.each_line do |line|
diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb
index 4eaf4fffef..4eed61e96c 100644
--- a/lib/redmine/scm/adapters/darcs_adapter.rb
+++ b/lib/redmine/scm/adapters/darcs_adapter.rb
@@ -20,16 +20,24 @@
 
 module Redmine
   module Scm
-    module Adapters    
+    module Adapters
       class DarcsAdapter < AbstractAdapter      
         # Darcs executable name
         DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs"
-        
+
         class << self
+          def client_command
+            @@bin    ||= DARCS_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote(DARCS_BIN)
+          end
+
           def client_version
             @@client_version ||= (darcs_binary_version || [])
           end
-  	  
+
           def darcs_binary_version
             darcsversion = darcs_binary_version_from_command_line
             if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)})
@@ -38,7 +46,7 @@ def darcs_binary_version
           end
 
           def darcs_binary_version_from_command_line
-            shellout("#{DARCS_BIN} --version") { |io| io.read }.to_s
+            shellout("#{sq_bin} --version") { |io| io.read }.to_s
           end
         end
 
@@ -57,7 +65,7 @@ def info
           rev = revisions(nil,nil,nil,{:limit => 1})
           rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
         end
-        
+
         # Returns an Entries collection
         # or nil if the given path doesn't exist in the repository
         def entries(path=nil, identifier=nil)
@@ -66,7 +74,7 @@ def entries(path=nil, identifier=nil)
             path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' )
           end
           entries = Entries.new          
-          cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --xml-output"
+          cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --xml-output"
           cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
           cmd << " #{shell_quote path}"
           shellout(cmd) do |io|
@@ -86,11 +94,11 @@ def entries(path=nil, identifier=nil)
           return nil if $? && $?.exitstatus != 0
           entries.compact.sort_by_name
         end
-    
+
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           path = '.' if path.blank?
           revisions = Revisions.new
-          cmd = "#{DARCS_BIN} changes --repodir #{shell_quote @url} --xml-output"
+          cmd = "#{self.class.sq_bin} changes --repodir #{shell_quote @url} --xml-output"
           cmd << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
           cmd << " --last #{options[:limit].to_i}" if options[:limit]
           shellout(cmd) do |io|
@@ -113,10 +121,10 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           return nil if $? && $?.exitstatus != 0
           revisions
         end
-        
+
         def diff(path, identifier_from, identifier_to=nil)
           path = '*' if path.blank?
-          cmd = "#{DARCS_BIN} diff --repodir #{shell_quote @url}"
+          cmd = "#{self.class.sq_bin} diff --repodir #{shell_quote @url}"
           if identifier_to.nil?
             cmd << " --match #{shell_quote("hash #{identifier_from}")}"
           else
@@ -133,9 +141,9 @@ def diff(path, identifier_from, identifier_to=nil)
           return nil if $? && $?.exitstatus != 0
           diff
         end
-        
+
         def cat(path, identifier=nil)
-          cmd = "#{DARCS_BIN} show content --repodir #{shell_quote @url}"
+          cmd = "#{self.class.sq_bin} show content --repodir #{shell_quote @url}"
           cmd << " --match #{shell_quote("hash #{identifier}")}" if identifier
           cmd << " #{shell_quote path}"
           cat = nil
@@ -148,7 +156,7 @@ def cat(path, identifier=nil)
         end
 
         private
-        
+
         # Returns an Entry from the given XML element
         # or nil if the entry was deleted
         def entry_from_xml(element, path_prefix)
@@ -196,10 +204,10 @@ def get_paths_for_patch(hash)
           end
           paths
         end
-        
+
         # Retrieve changed paths for a single patch
         def get_paths_for_patch_raw(hash)
-          cmd = "#{DARCS_BIN} annotate --repodir #{shell_quote @url} --summary --xml-output"
+          cmd = "#{self.class.sq_bin} annotate --repodir #{shell_quote @url} --summary --xml-output"
           cmd << " --match #{shell_quote("hash #{hash}")} "
           paths = []
           shellout(cmd) do |io|
diff --git a/lib/redmine/scm/adapters/filesystem_adapter.rb b/lib/redmine/scm/adapters/filesystem_adapter.rb
index 99296a0909..b88892b5e4 100644
--- a/lib/redmine/scm/adapters/filesystem_adapter.rb
+++ b/lib/redmine/scm/adapters/filesystem_adapter.rb
@@ -25,7 +25,12 @@ module Redmine
   module Scm
     module Adapters    
       class FilesystemAdapter < AbstractAdapter
-        
+
+        class << self
+          def client_available
+            true
+          end
+        end
 
         def initialize(url, root_url=nil, login=nil, password=nil)
           @url = with_trailling_slash(url)
diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb
index a40f450791..a241f83937 100644
--- a/lib/redmine/scm/adapters/git_adapter.rb
+++ b/lib/redmine/scm/adapters/git_adapter.rb
@@ -19,11 +19,25 @@
 
 module Redmine
   module Scm
-    module Adapters    
+    module Adapters
       class GitAdapter < AbstractAdapter
         # Git executable name
         GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
 
+        class << self
+          def client_command
+            @@bin    ||= GIT_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote(GIT_BIN)
+          end
+
+          def client_available
+            !client_version.empty?
+          end
+        end
+
         def info
           begin
             Info.new(:root_url => url, :lastrev => lastrev('',nil))
@@ -35,7 +49,7 @@ def info
         def branches
           return @branches if @branches
           @branches = []
-          cmd = "#{GIT_BIN} --git-dir #{target('')} branch --no-color"
+          cmd = "#{self.class.sq_bin} --git-dir #{target('')} branch --no-color"
           shellout(cmd) do |io|
             io.each_line do |line|
               @branches << line.match('\s*\*?\s*(.*)$')[1]
@@ -46,7 +60,7 @@ def branches
 
         def tags
           return @tags if @tags
-          cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
+          cmd = "#{self.class.sq_bin} --git-dir #{target('')} tag"
           shellout(cmd) do |io|
             @tags = io.readlines.sort!.map{|t| t.strip}
           end
@@ -59,7 +73,7 @@ def default_branch
         def entries(path=nil, identifier=nil)
           path ||= ''
           entries = Entries.new
-          cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
+          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|
@@ -86,7 +100,7 @@ def entries(path=nil, identifier=nil)
 
         def lastrev(path,rev)
           return nil if path.nil?
-          cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 "
+          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?
           lines = []
@@ -114,7 +128,7 @@ def lastrev(path,rev)
         def revisions(path, identifier_from, identifier_to, options={})
           revisions = Revisions.new
 
-          cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller "
+          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]
@@ -209,9 +223,9 @@ def diff(path, identifier_from, identifier_to=nil)
           path ||= ''
 
           if identifier_to
-            cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}" 
+            cmd = "#{self.class.sq_bin} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}" 
           else
-            cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
+            cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
           end
 
           cmd << " -- #{shell_quote path}" unless path.empty?
@@ -227,7 +241,7 @@ def diff(path, identifier_from, identifier_to=nil)
         
         def annotate(path, identifier=nil)
           identifier = 'HEAD' if identifier.blank?
-          cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
+          cmd = "#{self.class.sq_bin} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
           blame = Annotate.new
           content = nil
           shellout(cmd) { |io| io.binmode; content = io.read }
@@ -255,7 +269,7 @@ def cat(path, identifier=nil)
           if identifier.nil?
             identifier = 'HEAD'
           end
-          cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
+          cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
           cat = nil
           shellout(cmd) do |io|
             io.binmode
diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb
index f64e70b211..f8f925d44e 100644
--- a/lib/redmine/scm/adapters/mercurial_adapter.rb
+++ b/lib/redmine/scm/adapters/mercurial_adapter.rb
@@ -20,7 +20,7 @@
 
 module Redmine
   module Scm
-    module Adapters    
+    module Adapters
       class MercurialAdapter < AbstractAdapter
 
         # Mercurial executable name
@@ -30,11 +30,23 @@ class MercurialAdapter < AbstractAdapter
         TEMPLATE_EXTENSION = "tmpl"
 
         class << self
+          def client_command
+            @@bin    ||= HG_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote(HG_BIN)
+          end
+
           def client_version
             @@client_version ||= (hgversion || [])
           end
 
-          def hgversion  
+          def client_available
+            !client_version.empty?
+          end
+
+          def hgversion
             # 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.
@@ -45,7 +57,7 @@ def hgversion
           end
 
           def hgversion_from_command_line
-            shellout("#{HG_BIN} --version") { |io| io.read }.to_s
+            shellout("#{sq_bin} --version") { |io| io.read }.to_s
           end
 
           def template_path
@@ -63,7 +75,7 @@ def template_path_for(version)
         end
 
         def info
-          cmd = "#{HG_BIN} -R #{target('')} root"
+          cmd = "#{self.class.sq_bin} -R #{target('')} root"
           root_url = nil
           shellout(cmd) do |io|
             root_url = io.read
@@ -80,7 +92,7 @@ def info
         def entries(path=nil, identifier=nil)
           path ||= ''
           entries = Entries.new
-          cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate"
+          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|
@@ -106,7 +118,7 @@ def entries(path=nil, identifier=nil)
         # makes Mercurial produce a xml output.
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})  
           revisions = Revisions.new
-          cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}"
+          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
@@ -164,7 +176,7 @@ def diff(path, identifier_from, identifier_to=nil)
               return []
             end
           end
-          cmd = "#{HG_BIN} -R #{target('')} --config diff.git=false diff --nodates #{diff_args}"
+          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|
             io.each_line do |line|
@@ -176,7 +188,7 @@ def diff(path, identifier_from, identifier_to=nil)
         end
 
         def cat(path, identifier=nil)
-          cmd = "#{HG_BIN} -R #{target('')} cat"
+          cmd = "#{self.class.sq_bin} -R #{target('')} cat"
           cmd << " -r #{hgrev(identifier)}"
           cmd << " #{target(path)}"
           cat = nil
@@ -190,7 +202,7 @@ def cat(path, identifier=nil)
 
         def annotate(path, identifier=nil)
           path ||= ''
-          cmd = "#{HG_BIN} -R #{target('')}"
+          cmd = "#{self.class.sq_bin} -R #{target('')}"
           cmd << " annotate -ncu"
           cmd << " -r #{hgrev(identifier)}"
           cmd << " #{target(path)}"
diff --git a/lib/redmine/scm/adapters/subversion_adapter.rb b/lib/redmine/scm/adapters/subversion_adapter.rb
index 28ea55e2f8..c17d7a5e41 100644
--- a/lib/redmine/scm/adapters/subversion_adapter.rb
+++ b/lib/redmine/scm/adapters/subversion_adapter.rb
@@ -20,19 +20,27 @@
 
 module Redmine
   module Scm
-    module Adapters    
+    module Adapters
       class SubversionAdapter < AbstractAdapter
-      
+
         # SVN executable name
         SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
-        
+
         class << self
+          def client_command
+            @@bin    ||= SVN_BIN
+          end
+
+          def sq_bin
+            @@sq_bin ||= shell_quote(SVN_BIN)
+          end
+
           def client_version
             @@client_version ||= (svn_binary_version || [])
           end
-          
+
           def svn_binary_version
-            cmd = "#{SVN_BIN} --version"
+            cmd = "#{sq_bin} --version"
             version = nil
             shellout(cmd) do |io|
               # Read svn version in first returned line
@@ -44,10 +52,10 @@ def svn_binary_version
             version
           end
         end
-        
+
         # Get info about the svn repository
         def info
-          cmd = "#{SVN_BIN} info --xml #{target}"
+          cmd = "#{self.class.sq_bin} info --xml #{target}"
           cmd << credentials_string
           info = nil
           shellout(cmd) do |io|
@@ -70,14 +78,14 @@ def info
         rescue CommandFailed
           return nil
         end
-        
+
         # Returns an Entries collection
         # or nil if the given path doesn't exist in the repository
         def entries(path=nil, identifier=nil)
           path ||= ''
           identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
           entries = Entries.new
-          cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}"
+          cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
           cmd << credentials_string
           shellout(cmd) do |io|
             output = io.read
@@ -110,13 +118,13 @@ def entries(path=nil, identifier=nil)
           logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
           entries.sort_by_name
         end
-        
+
         def properties(path, identifier=nil)
           # proplist xml output supported in svn 1.5.0 and higher
           return nil unless self.class.client_version_above?([1, 5, 0])
           
           identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
-          cmd = "#{SVN_BIN} proplist --verbose --xml #{target(path)}@#{identifier}"
+          cmd = "#{self.class.sq_bin} proplist --verbose --xml #{target(path)}@#{identifier}"
           cmd << credentials_string
           properties = {}
           shellout(cmd) do |io|
@@ -132,13 +140,13 @@ def properties(path, identifier=nil)
           return nil if $? && $?.exitstatus != 0
           properties
         end
-        
+
         def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           path ||= ''
           identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD"
           identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
           revisions = Revisions.new
-          cmd = "#{SVN_BIN} log --xml -r #{identifier_from}:#{identifier_to}"
+          cmd = "#{self.class.sq_bin} log --xml -r #{identifier_from}:#{identifier_to}"
           cmd << credentials_string
           cmd << " --verbose " if  options[:with_paths]
           cmd << " --limit #{options[:limit].to_i}" if options[:limit]
@@ -171,13 +179,13 @@ def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
           return nil if $? && $?.exitstatus != 0
           revisions
         end
-        
+
         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 = "#{SVN_BIN} diff -r "
+
+          cmd = "#{self.class.sq_bin} diff -r "
           cmd << "#{identifier_to}:"
           cmd << "#{identifier_from}"
           cmd << " #{target(path)}@#{identifier_from}"
@@ -191,10 +199,10 @@ def diff(path, identifier_from, identifier_to=nil, type="inline")
           return nil if $? && $?.exitstatus != 0
           diff
         end
-        
+
         def cat(path, identifier=nil)
           identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
-          cmd = "#{SVN_BIN} cat #{target(path)}@#{identifier}"
+          cmd = "#{self.class.sq_bin} cat #{target(path)}@#{identifier}"
           cmd << credentials_string
           cat = nil
           shellout(cmd) do |io|
@@ -204,10 +212,10 @@ def cat(path, identifier=nil)
           return nil if $? && $?.exitstatus != 0
           cat
         end
-        
+
         def annotate(path, identifier=nil)
           identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
-          cmd = "#{SVN_BIN} blame #{target(path)}@#{identifier}"
+          cmd = "#{self.class.sq_bin} blame #{target(path)}@#{identifier}"
           cmd << credentials_string
           blame = Annotate.new
           shellout(cmd) do |io|

From 04361864a7203f2f81117de38fa9b13d45c4ddb5 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Barth 
Date: Sat, 12 Feb 2011 08:16:37 +0000
Subject: [PATCH 39/42] Added 'retro' style for gravatars. #7608

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4811 e93f8b46-1217-0410-a6f0-8f06a7374b81
---
 app/views/settings/_display.rhtml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/views/settings/_display.rhtml b/app/views/settings/_display.rhtml
index 8e07b4ff69..c6fe8339ec 100644
--- a/app/views/settings/_display.rhtml
+++ b/app/views/settings/_display.rhtml
@@ -15,7 +15,7 @@
 
 

<%= setting_check_box :gravatar_enabled %>

-

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

+

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

<%= submit_tag l(:button_save) %> From 39c19958d75160313e11bc9274f72758a32b687a Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 27 Mar 2011 10:48:20 -0700 Subject: [PATCH 40/42] [#308] Remove Redmine::VERSION::BRANCH --- lib/redmine/version.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb index 8daf00f5a6..3ee445ede0 100644 --- a/lib/redmine/version.rb +++ b/lib/redmine/version.rb @@ -7,14 +7,6 @@ module VERSION #:nodoc: PATCH = 0 TINY = PATCH # Redmine compat - # Branches: - # * stable - released version - # * master - stable development - # * unstable - future development - # - # Source: https://www.chiliproject.org/projects/chiliproject/wiki/ChiliProject_Repository - BRANCH = 'master' - def self.revision revision = nil entries_path = "#{RAILS_ROOT}/.svn/entries" @@ -37,7 +29,7 @@ def self.revision end REVISION = self.revision - ARRAY = [MAJOR, MINOR, PATCH, BRANCH, REVISION].compact + ARRAY = [MAJOR, MINOR, PATCH, REVISION].compact STRING = ARRAY.join('.') def self.to_a; ARRAY end From eedf576b63a1365a4d940270c6ff79cc5ac88651 Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 27 Mar 2011 10:51:38 -0700 Subject: [PATCH 41/42] Update changelog for 1.2.0 --- doc/CHANGELOG.rdoc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/CHANGELOG.rdoc b/doc/CHANGELOG.rdoc index 3ef823e0f7..193f15a69c 100644 --- a/doc/CHANGELOG.rdoc +++ b/doc/CHANGELOG.rdoc @@ -1,5 +1,32 @@ = ChiliProject changelog +== 2011-03-27 v1.2.0 + +* Bug #209: Don't hardcode user viewable labels (like "Path to .git repository") +* Bug #225: Support spaces in scm commands +* Bug #250: Filter assignee group to Team leaders +* Bug #251: Make Chili work with RubyGems 1.6 +* Bug #266: Fix monkey patching of rubytree in lib/redmine/menu_manager.rb +* Bug #267: /issues/changes?format=atom is returning 500 Internal Error +* Bug #270: Reposman.rb does not consider underscore to be valid char for a project identifier +* Bug #273: custom autologin cookie name not read +* Bug #278: Issue Form: Parent autocomplete won't work with issues under 3 charactors +* Bug #280: Issues AutoComplete isn't searching issue ids +* Bug #281: Cross project issues aren't showing their project on the Version page +* Bug #282: Enhance Redmine::SafeAttributes to work for subclasses +* Bug #302: Protect methods in ApplicationController +* Bug #305: Toolbar for textile edit fields is buggy in IE8 +* Feature #199: [PATCH] Adding a hook in the heading on showing an issue +* Feature #219: Add plugin hooks to the mailer layout +* Feature #230: Allow the loadpaths of themes to be specified in configuration.yml +* Feature #245: Merge Redmine.pm git smart-http functionality +* Feature #271: Replace checks for "auth_source_id" with "change_password_allowed?" in UsersController +* Feature #276: Add Log Time link to the sidebar on Project Overview +* Feature #283: Check pre-i18n 0.4.2 depreciation +* Feature #307: Add retro style gravatars +* Task #246: Document git-smart-http integration +* Task #308: Remove Redmine::VERSION::BRANCH + == 2011-02-27 v1.1.0 * Bug #109: Backport fix to display full TOC with present < p r e > tags From 25b01805465c2e24090f6ef42dac190351cc660b Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 27 Mar 2011 11:05:08 -0700 Subject: [PATCH 42/42] Bump version to 1.2.0 --- lib/redmine/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redmine/version.rb b/lib/redmine/version.rb index 3ee445ede0..60f06efd37 100644 --- a/lib/redmine/version.rb +++ b/lib/redmine/version.rb @@ -3,7 +3,7 @@ module Redmine module VERSION #:nodoc: MAJOR = 1 - MINOR = 1 + MINOR = 2 PATCH = 0 TINY = PATCH # Redmine compat