Commit 84998d22 authored by Eric Davis's avatar Eric Davis

Sync to v1.2.0's latest code. Merge branch 'master' into unstable.

parents f16c5bcb 7f47401a
/.project
/.loadpath
/config/additional_environment.rb /config/additional_environment.rb
/config/configuration.yml /config/configuration.yml
/config/database.yml /config/database.yml
......
syntax: glob syntax: glob
.project
.loadpath
config/additional_environment.rb config/additional_environment.rb
config/configuration.yml config/configuration.yml
config/database.yml config/database.yml
......
...@@ -129,7 +129,7 @@ class AccountController < ApplicationController ...@@ -129,7 +129,7 @@ class AccountController < ApplicationController
def logout_user def logout_user
if User.current.logged? 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']) Token.delete_all(["user_id = ? AND action = ?", User.current.id, 'autologin'])
self.logged_user = nil self.logged_user = nil
end end
...@@ -211,15 +211,14 @@ class AccountController < ApplicationController ...@@ -211,15 +211,14 @@ class AccountController < ApplicationController
def set_autologin_cookie(user) def set_autologin_cookie(user)
token = Token.create(:user => user, :action => 'autologin') token = Token.create(:user => user, :action => 'autologin')
cookie_name = Redmine::Configuration['autologin_cookie_name'] || 'autologin'
cookie_options = { cookie_options = {
:value => token.value, :value => token.value,
:expires => 1.year.from_now, :expires => 1.year.from_now,
:path => (Redmine::Configuration['autologin_cookie_path'] || '/'), :path => Redmine::Configuration['autologin_cookie_path'],
:secure => (Redmine::Configuration['autologin_cookie_secure'] ? true : false), :secure => Redmine::Configuration['autologin_cookie_secure'],
:httponly => true :httponly => true
} }
cookies[cookie_name] = cookie_options cookies[Redmine::Configuration['autologin_cookie_name']] = cookie_options
end end
# Onthefly creation failed, display the registration form to fill/fix attributes # Onthefly creation failed, display the registration form to fill/fix attributes
......
...@@ -19,6 +19,9 @@ require 'uri' ...@@ -19,6 +19,9 @@ require 'uri'
require 'cgi' require 'cgi'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
protected
include Redmine::I18n include Redmine::I18n
layout 'base' layout 'base'
...@@ -63,9 +66,9 @@ class ApplicationController < ActionController::Base ...@@ -63,9 +66,9 @@ class ApplicationController < ActionController::Base
if session[:user_id] if session[:user_id]
# existing session # existing session
(User.active.find(session[:user_id]) rescue nil) (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 # 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 session[:user_id] = user.id if user
user user
elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action]) elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
......
...@@ -9,7 +9,10 @@ class AutoCompletesController < ApplicationController ...@@ -9,7 +9,10 @@ class AutoCompletesController < ApplicationController
@issues << query.visible.find_by_id(q.to_i) @issues << query.visible.find_by_id(q.to_i)
end end
unless q.blank? 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 end
@issues.compact! @issues.compact!
render :layout => false render :layout => false
......
...@@ -67,13 +67,13 @@ class RepositoriesController < ApplicationController ...@@ -67,13 +67,13 @@ class RepositoriesController < ApplicationController
redirect_to :action => 'committers', :id => @project redirect_to :action => 'committers', :id => @project
end end
end end
def destroy def destroy
@repository.destroy @repository.destroy
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
end end
def show def show
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty?
@entries = @repository.entries(@path, @rev) @entries = @repository.entries(@path, @rev)
...@@ -88,7 +88,7 @@ class RepositoriesController < ApplicationController ...@@ -88,7 +88,7 @@ class RepositoriesController < ApplicationController
end end
alias_method :browse, :show alias_method :browse, :show
def changes def changes
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
...@@ -96,23 +96,23 @@ class RepositoriesController < ApplicationController ...@@ -96,23 +96,23 @@ class RepositoriesController < ApplicationController
@properties = @repository.properties(@path, @rev) @properties = @repository.properties(@path, @rev)
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
end end
def revisions def revisions
@changeset_count = @repository.changesets.count @changeset_count = @repository.changesets.count
@changeset_pages = Paginator.new self, @changeset_count, @changeset_pages = Paginator.new self, @changeset_count,
per_page_option, per_page_option,
params['page'] params['page']
@changesets = @repository.changesets.find(:all, @changesets = @repository.changesets.find(:all,
:limit => @changeset_pages.items_per_page, :limit => @changeset_pages.items_per_page,
:offset => @changeset_pages.current.offset, :offset => @changeset_pages.current.offset,
:include => [:user, :repository]) :include => [:user, :repository])
respond_to do |format| respond_to do |format|
format.html { render :layout => false if request.xhr? } format.html { render :layout => false if request.xhr? }
format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") } format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
end end
end end
def entry def entry
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
...@@ -122,9 +122,10 @@ class RepositoriesController < ApplicationController ...@@ -122,9 +122,10 @@ class RepositoriesController < ApplicationController
@content = @repository.cat(@path, @rev) @content = @repository.cat(@path, @rev)
(show_error_not_found; return) unless @content (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 # Force the download
send_data @content, :filename => @path.split('/').last send_data @content, :filename => filename_for_content_disposition(@path.split('/').last)
else else
# Prevent empty lines when displaying a file with Windows style eol # Prevent empty lines when displaying a file with Windows style eol
@content.gsub!("\r\n", "\n") @content.gsub!("\r\n", "\n")
...@@ -135,7 +136,7 @@ class RepositoriesController < ApplicationController ...@@ -135,7 +136,7 @@ class RepositoriesController < ApplicationController
def annotate def annotate
@entry = @repository.entry(@path, @rev) @entry = @repository.entry(@path, @rev)
(show_error_not_found; return) unless @entry (show_error_not_found; return) unless @entry
@annotate = @repository.scm.annotate(@path, @rev) @annotate = @repository.scm.annotate(@path, @rev)
(render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty? (render_error l(:error_scm_annotate); return) if @annotate.nil? || @annotate.empty?
@changeset = @repository.find_changeset_by_name(@rev) @changeset = @repository.find_changeset_by_name(@rev)
...@@ -153,7 +154,7 @@ class RepositoriesController < ApplicationController ...@@ -153,7 +154,7 @@ class RepositoriesController < ApplicationController
rescue ChangesetNotFound rescue ChangesetNotFound
show_error_not_found show_error_not_found
end end
def diff def diff
if params[:format] == 'diff' if params[:format] == 'diff'
@diff = @repository.diff(@path, @rev, @rev_to) @diff = @repository.diff(@path, @rev, @rev_to)
...@@ -185,11 +186,11 @@ class RepositoriesController < ApplicationController ...@@ -185,11 +186,11 @@ class RepositoriesController < ApplicationController
end end
end end
def stats def stats
end end
def graph def graph
data = nil data = nil
case params[:graph] case params[:graph]
when "commits_per_month" when "commits_per_month"
data = graph_commits_per_month(@repository) data = graph_commits_per_month(@repository)
......
...@@ -92,7 +92,7 @@ class UsersController < ApplicationController ...@@ -92,7 +92,7 @@ class UsersController < ApplicationController
@user.safe_attributes = params[:user] @user.safe_attributes = params[:user]
@user.admin = params[:user][:admin] || false @user.admin = params[:user][:admin] || false
@user.login = params[:user][:login] @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 # TODO: Similar to My#account
@user.pref.attributes = params[:pref] @user.pref.attributes = params[:pref]
...@@ -135,10 +135,10 @@ class UsersController < ApplicationController ...@@ -135,10 +135,10 @@ class UsersController < ApplicationController
def update def update
@user.admin = params[:user][:admin] if params[:user][:admin] @user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login] @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] @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
end end
@user.safe_attributes = params[:user]
# Was the account actived ? (do it before User#save clears the change) # Was the account actived ? (do it before User#save clears the change)
was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE]) was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
# TODO: Similar to My#account # TODO: Similar to My#account
...@@ -151,7 +151,7 @@ class UsersController < ApplicationController ...@@ -151,7 +151,7 @@ class UsersController < ApplicationController
if was_activated if was_activated
Mailer.deliver_account_activated(@user) 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]) Mailer.deliver_account_information(@user, params[:user][:password])
end end
......
...@@ -172,27 +172,27 @@ module RepositoriesHelper ...@@ -172,27 +172,27 @@ module RepositoriesHelper
end end
def darcs_field_tags(form, repository) 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 end
def mercurial_field_tags(form, repository) 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 end
def git_field_tags(form, repository) 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 end
def cvs_field_tags(form, repository) 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(:root_url, :label => :label_cvs_path, :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(:url, :label => :label_cvs_module, :size => 30, :required => true, :disabled => !repository.new_record?))
end end
def bazaar_field_tags(form, repository) 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 end
def filesystem_field_tags(form, repository) 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
end end
<%= render :partial => 'action_menu' %> <%= render :partial => 'action_menu' %>
<h2><%= @issue.tracker.name %> #<%= @issue.id %></h2> <h2><%= @issue.tracker.name %> #<%= @issue.id %><%= call_hook(:view_issues_show_identifier, :issue => @issue) %></h2>
<div class="<%= @issue.css_classes %> details"> <div class="<%= @issue.css_classes %> details">
<%= avatar(@issue.author, :size => "50") %> <%= avatar(@issue.author, :size => "50") %>
......
...@@ -26,7 +26,9 @@ hr { ...@@ -26,7 +26,9 @@ hr {
</head> </head>
<body> <body>
<span class="header"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_header) %></span> <span class="header"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_header) %></span>
<%= call_hook(:view_layouts_mailer_html_before_content, self.assigns) %>
<%= yield %> <%= yield %>
<%= call_hook(:view_layouts_mailer_html_after_content, self.assigns) %>
<hr /> <hr />
<span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer) %></span> <span class="footer"><%= Redmine::WikiFormatting.to_html(Setting.text_formatting, Setting.emails_footer) %></span>
</body> </body>
......
<%= Setting.emails_header %> <%= Setting.emails_header %>
<%= call_hook(:view_layouts_mailer_plain_before_content, self.assigns) %>
<%= yield %> <%= yield %>
<%= call_hook(:view_layouts_mailer_plain_after_content, self.assigns) %>
-- --
<%= Setting.emails_footer %> <%= Setting.emails_footer %>
...@@ -67,8 +67,13 @@ ...@@ -67,8 +67,13 @@
<% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %> <% if @total_hours && User.current.allowed_to?(:view_time_entries, @project) %>
<h3><%= l(:label_spent_time) %></h3> <h3><%= l(:label_spent_time) %></h3>
<p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p> <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p>
<p><%= link_to(l(:label_details), {:controller => 'timelog', :action => 'index', :project_id => @project}) %> | <p>
<%= link_to(l(:label_report), {:controller => 'time_entry_reports', :action => 'report', :project_id => @project}) %></p> <%= 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 %>
</p>
<% end %> <% end %>
<%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %> <%= call_hook(:view_projects_show_sidebar_bottom, :project => @project) %>
<% end %> <% end %>
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<p><%= setting_check_box :gravatar_enabled %></p> <p><%= setting_check_box :gravatar_enabled %></p>
<p><%= setting_select :gravatar_default, [["Wavatars", 'wavatar'], ["Identicons", 'identicon'], ["Monster ids", 'monsterid']], :blank => :label_none %></p> <p><%= setting_select :gravatar_default, [["Wavatars", 'wavatar'], ["Identicons", 'identicon'], ["Monster ids", 'monsterid'], ["Retro", "retro"]], :blank => :label_none %></p>
</div> </div>
<%= submit_tag l(:button_save) %> <%= submit_tag l(:button_save) %>
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
<fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend> <fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
<ul> <ul>
<% @issues.each do |issue| -%> <% @issues.each do |issue| -%>
<li><%= link_to_issue(issue) %></li> <li><%= link_to_issue(issue, :project => issue.project != @version.project) %></li>
<% end -%> <% end -%>
</ul> </ul>
</fieldset> </fieldset>
......
...@@ -98,7 +98,19 @@ default: ...@@ -98,7 +98,19 @@ default:
# attachments_storage_path: /var/chiliproject/files # attachments_storage_path: /var/chiliproject/files
# attachments_storage_path: D:/chiliproject/files # attachments_storage_path: D:/chiliproject/files
attachments_storage_path: 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. # Configuration of the autologin cookie.
# autologin_cookie_name: the name of the cookie (default: autologin) # autologin_cookie_name: the name of the cookie (default: autologin)
# autologin_cookie_path: the cookie path (default: /) # autologin_cookie_path: the cookie path (default: /)
......
...@@ -939,3 +939,10 @@ bg: ...@@ -939,3 +939,10 @@ bg:
enumeration_system_activity: Системна активност enumeration_system_activity: Системна активност
text_powered_by: Powered by %{link} 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
...@@ -953,3 +953,10 @@ bs: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -942,3 +942,10 @@ ca: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -939,3 +939,10 @@ cs: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -955,3 +955,10 @@ da: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -956,3 +956,10 @@ de: ...@@ -956,3 +956,10 @@ de:
notice_gantt_chart_truncated: Die Grafik ist unvollständig, da das Maximum der anzeigbaren Aufgaben überschritten wurde (%{max}) 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. setting_gantt_items_limit: Maximale Anzahl von Aufgaben die im Gantt-Chart angezeigt werden.
text_powered_by: Powered by %{link} 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
...@@ -939,3 +939,10 @@ el: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -943,3 +943,10 @@ en-GB: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: "Powered by %{link}" 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
...@@ -793,7 +793,14 @@ en: ...@@ -793,7 +793,14 @@ en:
label_project_copy_notifications: Send email notifications during the project copy label_project_copy_notifications: Send email notifications during the project copy
label_principal_search: "Search for user or group:" label_principal_search: "Search for user or group:"
label_user_search: "Search for user:" 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_login: Login
button_submit: Submit button_submit: Submit
button_save: Save button_save: Save
......
...@@ -976,3 +976,10 @@ es: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -943,3 +943,10 @@ eu: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -960,3 +960,10 @@ fi: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -957,3 +957,10 @@ fr: ...@@ -957,3 +957,10 @@ fr:
field_assigned_to_role: Rôle de l'assigné field_assigned_to_role: Rôle de l'assigné
setting_emails_header: Emails header setting_emails_header: Emails header
text_powered_by: Powered by %{link} 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
...@@ -951,3 +951,10 @@ gl: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -944,3 +944,10 @@ he: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -946,3 +946,10 @@ hr: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -958,3 +958,10 @@ ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -947,3 +947,10 @@ id: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -940,3 +940,10 @@ it: ...@@ -940,3 +940,10 @@ it:
notice_gantt_chart_truncated: Il grafico è stato troncato perchè eccede il numero di oggetti (%{max}) da visualizzare 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 setting_gantt_items_limit: Massimo numero di oggetti da visualizzare sul diagramma di gantt
text_powered_by: Powered by %{link} 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
...@@ -960,3 +960,10 @@ ja: ...@@ -960,3 +960,10 @@ ja:
enumeration_activities: 作業分類 (時間トラッキング) enumeration_activities: 作業分類 (時間トラッキング)
enumeration_system_activity: システム作業分類 enumeration_system_activity: システム作業分類
text_powered_by: Powered by %{link} 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
...@@ -991,3 +991,10 @@ ko: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -999,3 +999,10 @@ lt: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -934,3 +934,10 @@ lv: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -939,3 +939,10 @@ mk: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -940,3 +940,10 @@ mn: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -921,3 +921,10 @@ nl: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -926,3 +926,10 @@ ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -956,3 +956,10 @@ pl: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -959,3 +959,10 @@ pt-BR: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -943,3 +943,10 @@ pt: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -932,3 +932,10 @@ ro: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -1052,3 +1052,10 @@ ru: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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: Каталог
...@@ -934,3 +934,10 @@ sk: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -935,3 +935,10 @@ sl: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -939,3 +939,10 @@ sr-YU: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -940,3 +940,10 @@ sr: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -980,3 +980,10 @@ sv: ...@@ -980,3 +980,10 @@ sv:
enumeration_activities: Aktiviteter (tidsuppföljning) enumeration_activities: Aktiviteter (tidsuppföljning)
enumeration_system_activity: Systemaktivitet enumeration_system_activity: Systemaktivitet
text_powered_by: Powered by %{link} 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
...@@ -936,3 +936,10 @@ th: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -958,3 +958,10 @@ tr: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -935,3 +935,10 @@ uk: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -990,3 +990,10 @@ vi: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
...@@ -1020,3 +1020,10 @@ ...@@ -1020,3 +1020,10 @@
enumeration_activities: 活動 (時間追蹤) enumeration_activities: 活動 (時間追蹤)
enumeration_system_activity: 系統活動 enumeration_system_activity: 系統活動
text_powered_by: Powered by %{link} 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
...@@ -953,3 +953,10 @@ zh: ...@@ -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}) 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 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
text_powered_by: Powered by %{link} 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
= ChiliProject changelog = 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 == 2011-02-27 v1.1.0
* Bug #109: Backport fix to display full TOC with present < p r e > tags * Bug #109: Backport fix to display full TOC with present < p r e > tags
......
...@@ -93,6 +93,84 @@ S<them :> ...@@ -93,6 +93,84 @@ S<them :>
And you need to upgrade at least reposman.rb (after r860). 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 <Location /git>
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/
<Location /git>
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
</Location>
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 =cut
use strict; use strict;
...@@ -142,6 +220,11 @@ my @directives = ( ...@@ -142,6 +220,11 @@ my @directives = (
args_how => TAKE1, args_how => TAKE1,
errmsg => 'RedmineCacheCredsMax must be decimal number', errmsg => 'RedmineCacheCredsMax must be decimal number',
}, },
{
name => 'RedmineGitSmartHttp',
req_override => OR_AUTHCFG,
args_how => TAKE1,
},
); );
sub RedmineDSN { sub RedmineDSN {
...@@ -178,6 +261,17 @@ sub RedmineCacheCredsMax { ...@@ -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 { sub trim {
my $string = shift; my $string = shift;
$string =~ s/\s{2,}/ /g; $string =~ s/\s{2,}/ /g;
...@@ -194,6 +288,23 @@ Apache2::Module::add(__PACKAGE__, \@directives); ...@@ -194,6 +288,23 @@ Apache2::Module::add(__PACKAGE__, \@directives);
my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/; 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 { sub access_handler {
my $r = shift; my $r = shift;
...@@ -202,8 +313,7 @@ sub access_handler { ...@@ -202,8 +313,7 @@ sub access_handler {
return FORBIDDEN; return FORBIDDEN;
} }
my $method = $r->method; return OK unless request_is_read_only($r);
return OK unless defined $read_only_methods{$method};
my $project_id = get_project_identifier($r); my $project_id = get_project_identifier($r);
...@@ -320,7 +430,7 @@ sub is_member { ...@@ -320,7 +430,7 @@ sub is_member {
unless ($auth_source_id) { unless ($auth_source_id) {
my $method = $r->method; 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; $ret = 1;
last; last;
} }
...@@ -338,9 +448,7 @@ sub is_member { ...@@ -338,9 +448,7 @@ sub is_member {
bindpw => $rowldap[4] ? $rowldap[4] : "", bindpw => $rowldap[4] ? $rowldap[4] : "",
filter => "(".$rowldap[6]."=%s)" filter => "(".$rowldap[6]."=%s)"
); );
my $method = $r->method; $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((request_is_read_only($r) && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
$ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
} }
$sthldap->finish(); $sthldap->finish();
undef $sthldap; undef $sthldap;
......
...@@ -20,7 +20,11 @@ module Redmine ...@@ -20,7 +20,11 @@ module Redmine
# Configuration default values # Configuration default values
@defaults = { @defaults = {
'email_delivery' => nil 'email_delivery' => nil,
# Autologin cookie defaults:
'autologin_cookie_name' => 'autologin',
'autologin_cookie_path' => '/',
'autologin_cookie_secure' => false,
} }
@config = nil @config = nil
......
...@@ -31,14 +31,19 @@ module Redmine ...@@ -31,14 +31,19 @@ module Redmine
def safe_attributes(*args) def safe_attributes(*args)
@safe_attributes ||= [] @safe_attributes ||= []
if args.empty? if args.empty?
@safe_attributes if superclass < Redmine::SafeAttributes
superclass.safe_attributes + @safe_attributes
else
@safe_attributes
end
else else
options = args.last.is_a?(Hash) ? args.pop : {} options = args.last.is_a?(Hash) ? args.pop : {}
@safe_attributes << [args, options] @safe_attributes << [args, options]
safe_attributes
end end
end end
end end
# Returns an array that can be safely set by user or current user # Returns an array that can be safely set by user or current user
# #
# Example: # Example:
...@@ -62,7 +67,7 @@ module Redmine ...@@ -62,7 +67,7 @@ module Redmine
# # => {'title' => 'My book'} # # => {'title' => 'My book'}
def delete_unsafe_attributes(attrs, user=User.current) def delete_unsafe_attributes(attrs, user=User.current)
safe = safe_attribute_names(user) 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 end
# Sets attributes from attrs that are safe # Sets attributes from attrs that are safe
......
...@@ -25,6 +25,10 @@ module Redmine ...@@ -25,6 +25,10 @@ module Redmine
class AbstractAdapter #:nodoc: class AbstractAdapter #:nodoc:
class << self class << self
def client_command
""
end
# Returns the version of the scm client # Returns the version of the scm client
# Eg: [1, 5, 0] or [] if unknown # Eg: [1, 5, 0] or [] if unknown
def client_version def client_version
...@@ -45,8 +49,20 @@ module Redmine ...@@ -45,8 +49,20 @@ module Redmine
def client_version_above?(v, options={}) def client_version_above?(v, options={})
((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
end end
def client_available
true
end
def shell_quote(str)
if Redmine::Platform.mswin?
'"' + str.gsub(/"/, '\\"') + '"'
else
"'" + str.gsub(/'/, "'\"'\"'") + "'"
end
end
end end
def initialize(url, root_url=nil, login=nil, password=nil) def initialize(url, root_url=nil, login=nil, password=nil)
@url = url @url = url
@login = login if login && !login.empty? @login = login if login && !login.empty?
...@@ -138,7 +154,7 @@ module Redmine ...@@ -138,7 +154,7 @@ module Redmine
path ||= '' path ||= ''
(path[-1,1] == "/") ? path : "#{path}/" (path[-1,1] == "/") ? path : "#{path}/"
end end
def without_leading_slash(path) def without_leading_slash(path)
path ||= '' path ||= ''
path.gsub(%r{^/+}, '') path.gsub(%r{^/+}, '')
...@@ -148,13 +164,9 @@ module Redmine ...@@ -148,13 +164,9 @@ module Redmine
path ||= '' path ||= ''
(path[-1,1] == "/") ? path[0..-2] : path (path[-1,1] == "/") ? path[0..-2] : path
end end
def shell_quote(str) def shell_quote(str)
if Redmine::Platform.mswin? self.class.shell_quote(str)
'"' + str.gsub(/"/, '\\"') + '"'
else
"'" + str.gsub(/'/, "'\"'\"'") + "'"
end
end end
private private
...@@ -168,11 +180,11 @@ module Redmine ...@@ -168,11 +180,11 @@ module Redmine
base = path.match(/^\//) ? root_url : url base = path.match(/^\//) ? root_url : url
shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, '')) shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
end end
def logger def logger
self.class.logger self.class.logger
end end
def shellout(cmd, &block) def shellout(cmd, &block)
self.class.shellout(cmd, &block) self.class.shellout(cmd, &block)
end end
......
...@@ -19,15 +19,25 @@ require 'redmine/scm/adapters/abstract_adapter' ...@@ -19,15 +19,25 @@ require 'redmine/scm/adapters/abstract_adapter'
module Redmine module Redmine
module Scm module Scm
module Adapters module Adapters
class BazaarAdapter < AbstractAdapter class BazaarAdapter < AbstractAdapter
# Bazaar executable name # Bazaar executable name
BZR_BIN = Redmine::Configuration['scm_bazaar_command'] || "bzr" 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 # Get info about the repository
def info def info
cmd = "#{BZR_BIN} revno #{target('')}" cmd = "#{self.class.sq_bin} revno #{target('')}"
info = nil info = nil
shellout(cmd) do |io| shellout(cmd) do |io|
if io.read =~ %r{^(\d+)\r?$} if io.read =~ %r{^(\d+)\r?$}
...@@ -43,13 +53,13 @@ module Redmine ...@@ -43,13 +53,13 @@ module Redmine
rescue CommandFailed rescue CommandFailed
return nil return nil
end end
# Returns an Entries collection # Returns an Entries collection
# or nil if the given path doesn't exist in the repository # or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
path ||= '' path ||= ''
entries = Entries.new 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 identifier = -1 unless identifier && identifier.to_i > 0
cmd << " -r#{identifier.to_i}" cmd << " -r#{identifier.to_i}"
cmd << " #{target(path)}" cmd << " #{target(path)}"
...@@ -71,13 +81,13 @@ module Redmine ...@@ -71,13 +81,13 @@ module Redmine
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
entries.sort_by_name entries.sort_by_name
end end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= '' path ||= ''
identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : 'last:1' 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 identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1
revisions = Revisions.new 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| shellout(cmd) do |io|
revision = nil revision = nil
parsing = nil parsing = nil
...@@ -132,7 +142,7 @@ module Redmine ...@@ -132,7 +142,7 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
revisions revisions
end end
def diff(path, identifier_from, identifier_to=nil) def diff(path, identifier_from, identifier_to=nil)
path ||= '' path ||= ''
if identifier_to if identifier_to
...@@ -143,7 +153,7 @@ module Redmine ...@@ -143,7 +153,7 @@ module Redmine
if identifier_from if identifier_from
identifier_from = identifier_from.to_i identifier_from = identifier_from.to_i
end 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 = [] diff = []
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
...@@ -153,9 +163,9 @@ module Redmine ...@@ -153,9 +163,9 @@ module Redmine
#return nil if $? && $?.exitstatus != 0 #return nil if $? && $?.exitstatus != 0
diff diff
end end
def cat(path, identifier=nil) 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 << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
cmd << " #{target(path)}" cmd << " #{target(path)}"
cat = nil cat = nil
...@@ -166,9 +176,9 @@ module Redmine ...@@ -166,9 +176,9 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
cat cat
end end
def annotate(path, identifier=nil) 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 << " -r#{identifier.to_i}" if identifier && identifier.to_i > 0
cmd << " #{target(path)}" cmd << " #{target(path)}"
blame = Annotate.new blame = Annotate.new
......
...@@ -24,7 +24,17 @@ module Redmine ...@@ -24,7 +24,17 @@ module Redmine
# CVS executable name # CVS executable name
CVS_BIN = Redmine::Configuration['scm_cvs_command'] || "cvs" 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: # Guidelines for the input:
# url -> the project-path, relative to the cvsroot (eg. module name) # url -> the project-path, relative to the cvsroot (eg. module name)
# root_url -> the good old, sometimes damned, CVSROOT # root_url -> the good old, sometimes damned, CVSROOT
...@@ -38,24 +48,24 @@ module Redmine ...@@ -38,24 +48,24 @@ module Redmine
raise CommandFailed if root_url.blank? raise CommandFailed if root_url.blank?
@root_url = root_url @root_url = root_url
end end
def root_url def root_url
@root_url @root_url
end end
def url def url
@url @url
end end
def info def info
logger.debug "<cvs> info" logger.debug "<cvs> info"
Info.new({:root_url => @root_url, :lastrev => nil}) Info.new({:root_url => @root_url, :lastrev => nil})
end end
def get_previous_revision(revision) def get_previous_revision(revision)
CvsRevisionHelper.new(revision).prevRev CvsRevisionHelper.new(revision).prevRev
end end
# Returns an Entries collection # Returns an Entries collection
# or nil if the given path doesn't exist in the repository # or nil if the given path doesn't exist in the repository
# this method is used by the repository-browser (aka LIST) # this method is used by the repository-browser (aka LIST)
...@@ -63,14 +73,14 @@ module Redmine ...@@ -63,14 +73,14 @@ module Redmine
logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'" logger.debug "<cvs> entries '#{path}' with identifier '#{identifier}'"
path_with_project="#{url}#{with_leading_slash(path)}" path_with_project="#{url}#{with_leading_slash(path)}"
entries = Entries.new 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 << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
cmd << " #{shell_quote path_with_project}" cmd << " #{shell_quote path_with_project}"
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line(){|line| io.each_line(){|line|
fields=line.chop.split('/',-1) fields=line.chop.split('/',-1)
logger.debug(">>InspectLine #{fields.inspect}") logger.debug(">>InspectLine #{fields.inspect}")
if fields[0]!="D" if fields[0]!="D"
entries << Entry.new({:name => fields[-5], entries << Entry.new({:name => fields[-5],
#:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]), #:path => fields[-4].include?(path)?fields[-4]:(path + "/"+ fields[-4]),
...@@ -96,19 +106,19 @@ module Redmine ...@@ -96,19 +106,19 @@ module Redmine
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
entries.sort_by_name entries.sort_by_name
end end
STARTLOG="----------------------------" STARTLOG="----------------------------"
ENDLOG ="=============================================================================" ENDLOG ="============================================================================="
# Returns all revisions found between identifier_from and identifier_to # Returns all revisions found between identifier_from and identifier_to
# in the repository. both identifier have to be dates or nil. # in the repository. both identifier have to be dates or nil.
# these method returns nothing but yield every result in block # these method returns nothing but yield every result in block
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}, &block)
logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" logger.debug "<cvs> revisions path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
path_with_project="#{url}#{with_leading_slash(path)}" 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 << " -d\">#{time_to_cvstime_rlog(identifier_from)}\"" if identifier_from
cmd << " #{shell_quote path_with_project}" cmd << " #{shell_quote path_with_project}"
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -123,7 +133,7 @@ module Redmine ...@@ -123,7 +133,7 @@ module Redmine
file_state=nil file_state=nil
branch_map=nil branch_map=nil
io.each_line() do |line| io.each_line() do |line|
if state!="revision" && /^#{ENDLOG}/ =~ line if state!="revision" && /^#{ENDLOG}/ =~ line
commit_log=String.new commit_log=String.new
...@@ -162,9 +172,9 @@ module Redmine ...@@ -162,9 +172,9 @@ module Redmine
end end
next next
elsif state=="revision" elsif state=="revision"
if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line if /^#{ENDLOG}/ =~ line || /^#{STARTLOG}/ =~ line
if revision if revision
revHelper=CvsRevisionHelper.new(revision) revHelper=CvsRevisionHelper.new(revision)
revBranch="HEAD" revBranch="HEAD"
...@@ -176,7 +186,7 @@ module Redmine ...@@ -176,7 +186,7 @@ module Redmine
logger.debug("********** YIELD Revision #{revision}::#{revBranch}") logger.debug("********** YIELD Revision #{revision}::#{revBranch}")
yield Revision.new({ yield Revision.new({
:time => date, :time => date,
:author => author, :author => author,
:message=>commit_log.chomp, :message=>commit_log.chomp,
...@@ -188,9 +198,9 @@ module Redmine ...@@ -188,9 +198,9 @@ module Redmine
:kind=>'file', :kind=>'file',
:action=>file_state :action=>file_state
}] }]
}) })
end end
commit_log=String.new commit_log=String.new
revision=nil revision=nil
...@@ -199,7 +209,7 @@ module Redmine ...@@ -199,7 +209,7 @@ module Redmine
end end
next next
end end
if /^branches: (.+)$/ =~ line if /^branches: (.+)$/ =~ line
#TODO: version.branch = $1 #TODO: version.branch = $1
elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line elsif /^revision (\d+(?:\.\d+)+).*$/ =~ line
...@@ -216,20 +226,20 @@ module Redmine ...@@ -216,20 +226,20 @@ module Redmine
# version.line_minus = linechanges[2] # version.line_minus = linechanges[2]
# else # else
# version.line_plus = 0 # version.line_plus = 0
# version.line_minus = 0 # version.line_minus = 0
# end # end
else else
commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/ commit_log << line unless line =~ /^\*\*\* empty log message \*\*\*/
end end
end end
end end
end end
end end
def diff(path, identifier_from, identifier_to=nil) def diff(path, identifier_from, identifier_to=nil)
logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}" logger.debug "<cvs> diff path:'#{path}',identifier_from #{identifier_from}, identifier_to #{identifier_to}"
path_with_project="#{url}#{with_leading_slash(path)}" 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 = [] diff = []
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
...@@ -238,28 +248,29 @@ module Redmine ...@@ -238,28 +248,29 @@ module Redmine
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
diff diff
end end
def cat(path, identifier=nil) def cat(path, identifier=nil)
identifier = (identifier) ? identifier : "HEAD" identifier = (identifier) ? identifier : "HEAD"
logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}" logger.debug "<cvs> cat path:'#{path}',identifier #{identifier}"
path_with_project="#{url}#{with_leading_slash(path)}" 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 << " -D \"#{time_to_cvstime(identifier)}\"" if identifier
cmd << " -p #{shell_quote path_with_project}" cmd << " -p #{shell_quote path_with_project}"
cat = nil cat = nil
shellout(cmd) do |io| shellout(cmd) do |io|
io.binmode
cat = io.read cat = io.read
end end
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
cat cat
end end
def annotate(path, identifier=nil) def annotate(path, identifier=nil)
identifier = (identifier) ? identifier.to_i : "HEAD" identifier = (identifier) ? identifier.to_i : "HEAD"
logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}" logger.debug "<cvs> annotate path:'#{path}',identifier #{identifier}"
path_with_project="#{url}#{with_leading_slash(path)}" 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 blame = Annotate.new
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
...@@ -270,9 +281,9 @@ module Redmine ...@@ -270,9 +281,9 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
blame blame
end end
private private
# Returns the root url without the connexion string # Returns the root url without the connexion string
# :pserver:anonymous@foo.bar:/path => /path # :pserver:anonymous@foo.bar:/path => /path
# :ext:cvsservername:/path => /path # :ext:cvsservername:/path => /path
......
...@@ -20,16 +20,24 @@ require 'rexml/document' ...@@ -20,16 +20,24 @@ require 'rexml/document'
module Redmine module Redmine
module Scm module Scm
module Adapters module Adapters
class DarcsAdapter < AbstractAdapter class DarcsAdapter < AbstractAdapter
# Darcs executable name # Darcs executable name
DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs" DARCS_BIN = Redmine::Configuration['scm_darcs_command'] || "darcs"
class << self class << self
def client_command
@@bin ||= DARCS_BIN
end
def sq_bin
@@sq_bin ||= shell_quote(DARCS_BIN)
end
def client_version def client_version
@@client_version ||= (darcs_binary_version || []) @@client_version ||= (darcs_binary_version || [])
end end
def darcs_binary_version def darcs_binary_version
darcsversion = darcs_binary_version_from_command_line darcsversion = darcs_binary_version_from_command_line
if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)}) if m = darcsversion.match(%r{\A(.*?)((\d+\.)+\d+)})
...@@ -38,7 +46,7 @@ module Redmine ...@@ -38,7 +46,7 @@ module Redmine
end end
def darcs_binary_version_from_command_line 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
end end
...@@ -57,7 +65,7 @@ module Redmine ...@@ -57,7 +65,7 @@ module Redmine
rev = revisions(nil,nil,nil,{:limit => 1}) rev = revisions(nil,nil,nil,{:limit => 1})
rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
end end
# Returns an Entries collection # Returns an Entries collection
# or nil if the given path doesn't exist in the repository # or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
...@@ -66,7 +74,7 @@ module Redmine ...@@ -66,7 +74,7 @@ module Redmine
path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' ) path = ( self.class.client_version_above?([2, 2, 0]) ? @url : '.' )
end end
entries = Entries.new 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 << " --match #{shell_quote("hash #{identifier}")}" if identifier
cmd << " #{shell_quote path}" cmd << " #{shell_quote path}"
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -86,11 +94,11 @@ module Redmine ...@@ -86,11 +94,11 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
entries.compact.sort_by_name entries.compact.sort_by_name
end end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path = '.' if path.blank? path = '.' if path.blank?
revisions = Revisions.new 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 << " --from-match #{shell_quote("hash #{identifier_from}")}" if identifier_from
cmd << " --last #{options[:limit].to_i}" if options[:limit] cmd << " --last #{options[:limit].to_i}" if options[:limit]
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -113,10 +121,10 @@ module Redmine ...@@ -113,10 +121,10 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
revisions revisions
end end
def diff(path, identifier_from, identifier_to=nil) def diff(path, identifier_from, identifier_to=nil)
path = '*' if path.blank? 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? if identifier_to.nil?
cmd << " --match #{shell_quote("hash #{identifier_from}")}" cmd << " --match #{shell_quote("hash #{identifier_from}")}"
else else
...@@ -133,9 +141,9 @@ module Redmine ...@@ -133,9 +141,9 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
diff diff
end end
def cat(path, identifier=nil) 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 << " --match #{shell_quote("hash #{identifier}")}" if identifier
cmd << " #{shell_quote path}" cmd << " #{shell_quote path}"
cat = nil cat = nil
...@@ -148,7 +156,7 @@ module Redmine ...@@ -148,7 +156,7 @@ module Redmine
end end
private private
# Returns an Entry from the given XML element # Returns an Entry from the given XML element
# or nil if the entry was deleted # or nil if the entry was deleted
def entry_from_xml(element, path_prefix) def entry_from_xml(element, path_prefix)
...@@ -196,10 +204,10 @@ module Redmine ...@@ -196,10 +204,10 @@ module Redmine
end end
paths paths
end end
# Retrieve changed paths for a single patch # Retrieve changed paths for a single patch
def get_paths_for_patch_raw(hash) 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}")} " cmd << " --match #{shell_quote("hash #{hash}")} "
paths = [] paths = []
shellout(cmd) do |io| shellout(cmd) do |io|
......
...@@ -25,7 +25,12 @@ module Redmine ...@@ -25,7 +25,12 @@ module Redmine
module Scm module Scm
module Adapters module Adapters
class FilesystemAdapter < AbstractAdapter class FilesystemAdapter < AbstractAdapter
class << self
def client_available
true
end
end
def initialize(url, root_url=nil, login=nil, password=nil) def initialize(url, root_url=nil, login=nil, password=nil)
@url = with_trailling_slash(url) @url = with_trailling_slash(url)
......
...@@ -19,11 +19,25 @@ require 'redmine/scm/adapters/abstract_adapter' ...@@ -19,11 +19,25 @@ require 'redmine/scm/adapters/abstract_adapter'
module Redmine module Redmine
module Scm module Scm
module Adapters module Adapters
class GitAdapter < AbstractAdapter class GitAdapter < AbstractAdapter
# Git executable name # Git executable name
GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" 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 def info
begin begin
Info.new(:root_url => url, :lastrev => lastrev('',nil)) Info.new(:root_url => url, :lastrev => lastrev('',nil))
...@@ -35,7 +49,7 @@ module Redmine ...@@ -35,7 +49,7 @@ module Redmine
def branches def branches
return @branches if @branches return @branches if @branches
@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| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
@branches << line.match('\s*\*?\s*(.*)$')[1] @branches << line.match('\s*\*?\s*(.*)$')[1]
...@@ -46,7 +60,7 @@ module Redmine ...@@ -46,7 +60,7 @@ module Redmine
def tags def tags
return @tags if @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| shellout(cmd) do |io|
@tags = io.readlines.sort!.map{|t| t.strip} @tags = io.readlines.sort!.map{|t| t.strip}
end end
...@@ -59,7 +73,7 @@ module Redmine ...@@ -59,7 +73,7 @@ module Redmine
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
path ||= '' path ||= ''
entries = Entries.new 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("HEAD:" + path) if identifier.nil?
cmd << shell_quote(identifier + ":" + path) if identifier cmd << shell_quote(identifier + ":" + path) if identifier
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -86,7 +100,7 @@ module Redmine ...@@ -86,7 +100,7 @@ module Redmine
def lastrev(path,rev) def lastrev(path,rev)
return nil if path.nil? 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 rev} " if rev
cmd << "-- #{shell_quote path} " unless path.empty? cmd << "-- #{shell_quote path} " unless path.empty?
lines = [] lines = []
...@@ -114,7 +128,7 @@ module Redmine ...@@ -114,7 +128,7 @@ module Redmine
def revisions(path, identifier_from, identifier_to, options={}) def revisions(path, identifier_from, identifier_to, options={})
revisions = Revisions.new 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 << " --reverse " if options[:reverse]
cmd << " --all " if options[:all] cmd << " --all " if options[:all]
cmd << " -n #{options[:limit].to_i} " if options[:limit] cmd << " -n #{options[:limit].to_i} " if options[:limit]
...@@ -209,9 +223,9 @@ module Redmine ...@@ -209,9 +223,9 @@ module Redmine
path ||= '' path ||= ''
if identifier_to 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 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 end
cmd << " -- #{shell_quote path}" unless path.empty? cmd << " -- #{shell_quote path}" unless path.empty?
...@@ -227,7 +241,7 @@ module Redmine ...@@ -227,7 +241,7 @@ module Redmine
def annotate(path, identifier=nil) def annotate(path, identifier=nil)
identifier = 'HEAD' if identifier.blank? 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 blame = Annotate.new
content = nil content = nil
shellout(cmd) { |io| io.binmode; content = io.read } shellout(cmd) { |io| io.binmode; content = io.read }
...@@ -255,7 +269,7 @@ module Redmine ...@@ -255,7 +269,7 @@ module Redmine
if identifier.nil? if identifier.nil?
identifier = 'HEAD' identifier = 'HEAD'
end 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 cat = nil
shellout(cmd) do |io| shellout(cmd) do |io|
io.binmode io.binmode
......
...@@ -20,7 +20,7 @@ require 'cgi' ...@@ -20,7 +20,7 @@ require 'cgi'
module Redmine module Redmine
module Scm module Scm
module Adapters module Adapters
class MercurialAdapter < AbstractAdapter class MercurialAdapter < AbstractAdapter
# Mercurial executable name # Mercurial executable name
...@@ -30,11 +30,23 @@ module Redmine ...@@ -30,11 +30,23 @@ module Redmine
TEMPLATE_EXTENSION = "tmpl" TEMPLATE_EXTENSION = "tmpl"
class << self class << self
def client_command
@@bin ||= HG_BIN
end
def sq_bin
@@sq_bin ||= shell_quote(HG_BIN)
end
def client_version def client_version
@@client_version ||= (hgversion || []) @@client_version ||= (hgversion || [])
end end
def hgversion def client_available
!client_version.empty?
end
def hgversion
# The hg version is expressed either as a # The hg version is expressed either as a
# release number (eg 0.9.5 or 1.0) or as a revision # release number (eg 0.9.5 or 1.0) or as a revision
# id composed of 12 hexa characters. # id composed of 12 hexa characters.
...@@ -45,7 +57,7 @@ module Redmine ...@@ -45,7 +57,7 @@ module Redmine
end end
def hgversion_from_command_line def hgversion_from_command_line
shellout("#{HG_BIN} --version") { |io| io.read }.to_s shellout("#{sq_bin} --version") { |io| io.read }.to_s
end end
def template_path def template_path
...@@ -63,7 +75,7 @@ module Redmine ...@@ -63,7 +75,7 @@ module Redmine
end end
def info def info
cmd = "#{HG_BIN} -R #{target('')} root" cmd = "#{self.class.sq_bin} -R #{target('')} root"
root_url = nil root_url = nil
shellout(cmd) do |io| shellout(cmd) do |io|
root_url = io.read root_url = io.read
...@@ -80,7 +92,7 @@ module Redmine ...@@ -80,7 +92,7 @@ module Redmine
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
path ||= '' path ||= ''
entries = Entries.new 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 << " -r #{hgrev(identifier)}"
cmd << " " + shell_quote("path:#{path}") unless path.empty? cmd << " " + shell_quote("path:#{path}") unless path.empty?
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -106,7 +118,7 @@ module Redmine ...@@ -106,7 +118,7 @@ module Redmine
# makes Mercurial produce a xml output. # makes Mercurial produce a xml output.
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
revisions = Revisions.new 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 if identifier_from && identifier_to
cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}" cmd << " -r #{hgrev(identifier_from)}:#{hgrev(identifier_to)}"
elsif identifier_from elsif identifier_from
...@@ -164,7 +176,7 @@ module Redmine ...@@ -164,7 +176,7 @@ module Redmine
return [] return []
end end
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? cmd << " -I #{target(path)}" unless path.empty?
shellout(cmd) do |io| shellout(cmd) do |io|
io.each_line do |line| io.each_line do |line|
...@@ -176,7 +188,7 @@ module Redmine ...@@ -176,7 +188,7 @@ module Redmine
end end
def cat(path, identifier=nil) def cat(path, identifier=nil)
cmd = "#{HG_BIN} -R #{target('')} cat" cmd = "#{self.class.sq_bin} -R #{target('')} cat"
cmd << " -r #{hgrev(identifier)}" cmd << " -r #{hgrev(identifier)}"
cmd << " #{target(path)}" cmd << " #{target(path)}"
cat = nil cat = nil
...@@ -190,7 +202,7 @@ module Redmine ...@@ -190,7 +202,7 @@ module Redmine
def annotate(path, identifier=nil) def annotate(path, identifier=nil)
path ||= '' path ||= ''
cmd = "#{HG_BIN} -R #{target('')}" cmd = "#{self.class.sq_bin} -R #{target('')}"
cmd << " annotate -ncu" cmd << " annotate -ncu"
cmd << " -r #{hgrev(identifier)}" cmd << " -r #{hgrev(identifier)}"
cmd << " #{target(path)}" cmd << " #{target(path)}"
......
...@@ -20,19 +20,27 @@ require 'uri' ...@@ -20,19 +20,27 @@ require 'uri'
module Redmine module Redmine
module Scm module Scm
module Adapters module Adapters
class SubversionAdapter < AbstractAdapter class SubversionAdapter < AbstractAdapter
# SVN executable name # SVN executable name
SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn" SVN_BIN = Redmine::Configuration['scm_subversion_command'] || "svn"
class << self class << self
def client_command
@@bin ||= SVN_BIN
end
def sq_bin
@@sq_bin ||= shell_quote(SVN_BIN)
end
def client_version def client_version
@@client_version ||= (svn_binary_version || []) @@client_version ||= (svn_binary_version || [])
end end
def svn_binary_version def svn_binary_version
cmd = "#{SVN_BIN} --version" cmd = "#{sq_bin} --version"
version = nil version = nil
shellout(cmd) do |io| shellout(cmd) do |io|
# Read svn version in first returned line # Read svn version in first returned line
...@@ -44,10 +52,10 @@ module Redmine ...@@ -44,10 +52,10 @@ module Redmine
version version
end end
end end
# Get info about the svn repository # Get info about the svn repository
def info def info
cmd = "#{SVN_BIN} info --xml #{target}" cmd = "#{self.class.sq_bin} info --xml #{target}"
cmd << credentials_string cmd << credentials_string
info = nil info = nil
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -70,14 +78,14 @@ module Redmine ...@@ -70,14 +78,14 @@ module Redmine
rescue CommandFailed rescue CommandFailed
return nil return nil
end end
# Returns an Entries collection # Returns an Entries collection
# or nil if the given path doesn't exist in the repository # or nil if the given path doesn't exist in the repository
def entries(path=nil, identifier=nil) def entries(path=nil, identifier=nil)
path ||= '' path ||= ''
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD"
entries = Entries.new entries = Entries.new
cmd = "#{SVN_BIN} list --xml #{target(path)}@#{identifier}" cmd = "#{self.class.sq_bin} list --xml #{target(path)}@#{identifier}"
cmd << credentials_string cmd << credentials_string
shellout(cmd) do |io| shellout(cmd) do |io|
output = io.read output = io.read
...@@ -110,13 +118,13 @@ module Redmine ...@@ -110,13 +118,13 @@ module Redmine
logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug?
entries.sort_by_name entries.sort_by_name
end end
def properties(path, identifier=nil) def properties(path, identifier=nil)
# proplist xml output supported in svn 1.5.0 and higher # proplist xml output supported in svn 1.5.0 and higher
return nil unless self.class.client_version_above?([1, 5, 0]) return nil unless self.class.client_version_above?([1, 5, 0])
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 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 cmd << credentials_string
properties = {} properties = {}
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -132,13 +140,13 @@ module Redmine ...@@ -132,13 +140,13 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
properties properties
end end
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
path ||= '' path ||= ''
identifier_from = (identifier_from && identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" 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 identifier_to = (identifier_to && identifier_to.to_i > 0) ? identifier_to.to_i : 1
revisions = Revisions.new 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 << credentials_string
cmd << " --verbose " if options[:with_paths] cmd << " --verbose " if options[:with_paths]
cmd << " --limit #{options[:limit].to_i}" if options[:limit] cmd << " --limit #{options[:limit].to_i}" if options[:limit]
...@@ -171,13 +179,13 @@ module Redmine ...@@ -171,13 +179,13 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
revisions revisions
end end
def diff(path, identifier_from, identifier_to=nil, type="inline") def diff(path, identifier_from, identifier_to=nil, type="inline")
path ||= '' path ||= ''
identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' 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) 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_to}:"
cmd << "#{identifier_from}" cmd << "#{identifier_from}"
cmd << " #{target(path)}@#{identifier_from}" cmd << " #{target(path)}@#{identifier_from}"
...@@ -191,10 +199,10 @@ module Redmine ...@@ -191,10 +199,10 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
diff diff
end end
def cat(path, identifier=nil) def cat(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 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 cmd << credentials_string
cat = nil cat = nil
shellout(cmd) do |io| shellout(cmd) do |io|
...@@ -204,10 +212,10 @@ module Redmine ...@@ -204,10 +212,10 @@ module Redmine
return nil if $? && $?.exitstatus != 0 return nil if $? && $?.exitstatus != 0
cat cat
end end
def annotate(path, identifier=nil) def annotate(path, identifier=nil)
identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" 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 cmd << credentials_string
blame = Annotate.new blame = Annotate.new
shellout(cmd) do |io| shellout(cmd) do |io|
......
...@@ -89,11 +89,22 @@ module Redmine ...@@ -89,11 +89,22 @@ module Redmine
private private
def self.scan_themes def self.scan_themes
dirs = Dir.glob("#{Rails.public_path}/themes/*").select do |f| theme_paths.inject([]) do |themes, path|
# A theme should at least override application.css dirs = Dir.glob(File.join(path, '*')).select do |f|
File.directory?(f) && File.exist?("#{f}/stylesheets/application.css") # A theme should at least override application.css
end File.directory?(f) && File.exist?("#{f}/stylesheets/application.css")
dirs.collect {|dir| Theme.new(dir)}.sort 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 end
end end
......
...@@ -3,18 +3,10 @@ require 'rexml/document' ...@@ -3,18 +3,10 @@ require 'rexml/document'
module Redmine module Redmine
module VERSION #:nodoc: module VERSION #:nodoc:
MAJOR = 1 MAJOR = 1
MINOR = 1 MINOR = 2
PATCH = 0 PATCH = 0
TINY = PATCH # Redmine compat 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 def self.revision
revision = nil revision = nil
entries_path = "#{RAILS_ROOT}/.svn/entries" entries_path = "#{RAILS_ROOT}/.svn/entries"
...@@ -37,7 +29,7 @@ module Redmine ...@@ -37,7 +29,7 @@ module Redmine
end end
REVISION = self.revision REVISION = self.revision
ARRAY = [MAJOR, MINOR, PATCH, BRANCH, REVISION].compact ARRAY = [MAJOR, MINOR, PATCH, REVISION].compact
STRING = ARRAY.join('.') STRING = ARRAY.join('.')
def self.to_a; ARRAY end def self.to_a; ARRAY end
......
...@@ -51,7 +51,8 @@ namespace :test do ...@@ -51,7 +51,8 @@ namespace :test do
(supported_scms - [:subversion, :mercurial]).each do |scm| (supported_scms - [:subversion, :mercurial]).each do |scm|
desc "Creates a test #{scm} repository" desc "Creates a test #{scm} repository"
task scm => :create_dir do 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
end end
......
...@@ -210,7 +210,7 @@ function observeParentIssueField(url) { ...@@ -210,7 +210,7 @@ function observeParentIssueField(url) {
new Ajax.Autocompleter('issue_parent_issue_id', new Ajax.Autocompleter('issue_parent_issue_id',
'parent_issue_candidates', 'parent_issue_candidates',
url, url,
{ minChars: 3, { minChars: 1,
frequency: 0.5, frequency: 0.5,
paramName: 'q', paramName: 'q',
updateElement: function(value) { updateElement: function(value) {
...@@ -222,7 +222,7 @@ function observeRelatedIssueField(url) { ...@@ -222,7 +222,7 @@ function observeRelatedIssueField(url) {
new Ajax.Autocompleter('relation_issue_to_id', new Ajax.Autocompleter('relation_issue_to_id',
'related_issue_candidates', 'related_issue_candidates',
url, url,
{ minChars: 3, { minChars: 1,
frequency: 0.5, frequency: 0.5,
paramName: 'q', paramName: 'q',
updateElement: function(value) { updateElement: function(value) {
......
...@@ -242,108 +242,107 @@ jsToolBar.prototype = { ...@@ -242,108 +242,107 @@ jsToolBar.prototype = {
this.encloseSelection(stag,etag); this.encloseSelection(stag,etag);
}, },
encloseLineSelection: function(prefix, suffix, fn) { encloseLineSelection: function (prefix, suffix, fn) {
this.textarea.focus(); this.textarea.focus();
prefix = prefix || '';
prefix = prefix || ''; suffix = suffix || '';
suffix = suffix || ''; var start, end, sel, scrollPos, subst, res;
if (typeof(document["selection"]) != "undefined") {
var start, end, sel, scrollPos, subst, res; // just makes it work in IE8 somehow
var range = document.selection.createRange();
if (typeof(document["selection"]) != "undefined") { var bookmark = range.getBookmark();
sel = document.selection.createRange().text; var origParent = range.parentElement();
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { // we move the starting point of the selection to the last newline
start = this.textarea.selectionStart; try {
end = this.textarea.selectionEnd; while (range.text[0] != "\n" && range.text[0] != "\r") {
scrollPos = this.textarea.scrollTop; bookmark = range.getBookmark();
// go to the start of the line range.moveStart("character", -1);
start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length; if (origParent != range.parentElement()) {
// go to the end of the line throw "Outside of Textarea";
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); }
} range.moveStart("character", 1);
} catch(err) {
if (sel.match(/ $/)) { // exclude ending space char, if any if (err == "Outside of Textarea")
sel = sel.substring(0, sel.length - 1); range.moveToBookmark(bookmark);
suffix = suffix + " "; else
} throw err;
}
if (typeof(fn) == 'function') { if (range.text.match(/ $/))
res = (sel) ? fn.call(this,sel) : fn(''); range.moveEnd("character", -1);
} else { sel = range.text;
res = (sel) ? sel : ''; } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
} start = this.textarea.selectionStart;
end = this.textarea.selectionEnd;
subst = prefix + res + suffix; scrollPos = this.textarea.scrollTop;
// go to the start of the line
if (typeof(document["selection"]) != "undefined") { start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length;
document.selection.createRange().text = subst; // go to the end of the line
var range = this.textarea.createTextRange(); end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length;
range.collapse(false); sel = this.textarea.value.substring(start, end);
range.move('character', -suffix.length); }
range.select(); if (sel.match(/ $/)) {
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { sel = sel.substring(0, sel.length - 1);
this.textarea.value = this.textarea.value.substring(0, start) + subst + suffix = suffix + " ";
this.textarea.value.substring(end); }
if (sel) { if (typeof(fn) == 'function') {
this.textarea.setSelectionRange(start + subst.length, start + subst.length); res = (sel) ? fn.call(this, sel) : fn('');
} else { } else {
this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); res = (sel) ? sel : '';
} }
this.textarea.scrollTop = scrollPos; subst = prefix + res + suffix;
} if (typeof(document["selection"]) != "undefined") {
}, range.text = subst;
this.textarea.caretPos -= suffix.length;
encloseSelection: function(prefix, suffix, fn) { } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
this.textarea.focus(); this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end);
if (sel) {
prefix = prefix || ''; this.textarea.setSelectionRange(start + subst.length, start + subst.length);
suffix = suffix || ''; } else {
this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
var start, end, sel, scrollPos, subst, res; }
this.textarea.scrollTop = scrollPos;
if (typeof(document["selection"]) != "undefined") { }
sel = document.selection.createRange().text; },
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
start = this.textarea.selectionStart; encloseSelection: function (prefix, suffix, fn) {
end = this.textarea.selectionEnd; this.textarea.focus();
scrollPos = this.textarea.scrollTop; prefix = prefix || '';
sel = this.textarea.value.substring(start, end); suffix = suffix || '';
} var start, end, sel, scrollPos, subst, res;
if (typeof(document["selection"]) != "undefined") {
if (sel.match(/ $/)) { // exclude ending space char, if any sel = document.selection.createRange().text;
sel = sel.substring(0, sel.length - 1); } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
suffix = suffix + " "; start = this.textarea.selectionStart;
} end = this.textarea.selectionEnd;
scrollPos = this.textarea.scrollTop;
if (typeof(fn) == 'function') { sel = this.textarea.value.substring(start, end);
res = (sel) ? fn.call(this,sel) : fn(''); }
} else { if (sel.match(/ $/)) {
res = (sel) ? sel : ''; sel = sel.substring(0, sel.length - 1);
} suffix = suffix + " ";
}
subst = prefix + res + suffix; if (typeof(fn) == 'function') {
res = (sel) ? fn.call(this, sel) : fn('');
if (typeof(document["selection"]) != "undefined") { } else {
document.selection.createRange().text = subst; res = (sel) ? sel : '';
var range = this.textarea.createTextRange(); }
range.collapse(false); subst = prefix + res + suffix;
range.move('character', -suffix.length); if (typeof(document["selection"]) != "undefined") {
range.select(); var range = document.selection.createRange().text = subst;
// this.textarea.caretPos -= suffix.length; this.textarea.caretPos -= suffix.length;
} else if (typeof(this.textarea["setSelectionRange"]) != "undefined") { } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value = this.textarea.value.substring(0, start) + subst + this.textarea.value.substring(end);
this.textarea.value.substring(end); if (sel) {
if (sel) { this.textarea.setSelectionRange(start + subst.length, start + subst.length);
this.textarea.setSelectionRange(start + subst.length, start + subst.length); } else {
} else { this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
this.textarea.setSelectionRange(start + prefix.length, start + prefix.length); }
} this.textarea.scrollTop = scrollPos;
this.textarea.scrollTop = scrollPos; }
} },
},
stripBaseURL: function(url) { stripBaseURL: function(url) {
if (this.base_url != '') { if (this.base_url != '') {
var pos = url.indexOf(this.base_url); var pos = url.indexOf(this.base_url);
......
...@@ -47,55 +47,55 @@ class ApplicationControllerTest < ActionController::TestCase ...@@ -47,55 +47,55 @@ class ApplicationControllerTest < ActionController::TestCase
context "test_api_offset_and_limit" do context "test_api_offset_and_limit" do
context "without params" do context "without params" do
should "return 0, 25" 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
end end
context "with limit" do context "with limit" do
should "return 0, 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 end
should "not exceed 100" do 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 end
should "not be negative" do 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
end end
context "with offset" do context "with offset" do
should "return offset, 25" 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 end
should "not be negative" do 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 end
context "and limit" do context "and limit" do
should "return offset, 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 end
end end
context "with page" do context "with page" do
should "return offset, 25" do should "return offset, 25" do
assert_equal [0, 25], @controller.api_offset_and_limit({:page => 1}) assert_equal [0, 25], @controller.send(:api_offset_and_limit, {:page => 1})
assert_equal [50, 25], @controller.api_offset_and_limit({:page => 3}) assert_equal [50, 25], @controller.send(:api_offset_and_limit, {:page => 3})
end end
should "not be negative" do should "not be negative" do
assert_equal [0, 25], @controller.api_offset_and_limit({:page => 0}) assert_equal [0, 25], @controller.send(: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 => -2})
end end
context "and limit" do context "and limit" do
should "return offset, limit" do should "return offset, limit" do
assert_equal [0, 100], @controller.api_offset_and_limit({:page => 1, :limit => 100}) assert_equal [0, 100], @controller.send(:api_offset_and_limit, {:page => 1, :limit => 100})
assert_equal [200, 100], @controller.api_offset_and_limit({:page => 3, :limit => 100}) assert_equal [200, 100], @controller.send(:api_offset_and_limit, {:page => 3, :limit => 100})
end end
end end
end end
......
...@@ -16,6 +16,24 @@ class AutoCompletesControllerTest < ActionController::TestCase ...@@ -16,6 +16,24 @@ class AutoCompletesControllerTest < ActionController::TestCase
assert_not_nil assigns(:issues) assert_not_nil assigns(:issues)
assert assigns(:issues).include?(Issue.find(13)) assert assigns(:issues).include?(Issue.find(13))
end 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 def test_auto_complete_with_scope_all_and_cross_project_relations
Setting.cross_project_issue_relations = '1' Setting.cross_project_issue_relations = '1'
......
...@@ -50,7 +50,7 @@ class AccountTest < ActionController::IntegrationTest ...@@ -50,7 +50,7 @@ class AccountTest < ActionController::IntegrationTest
assert_equal user, token.user assert_equal user, token.user
assert_equal 'autologin', token.action assert_equal 'autologin', token.action
assert_equal user.id, session[:user_id] 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 # Session is cleared
reset! reset!
...@@ -60,7 +60,7 @@ class AccountTest < ActionController::IntegrationTest ...@@ -60,7 +60,7 @@ class AccountTest < ActionController::IntegrationTest
assert_nil user.reload.last_login_on assert_nil user.reload.last_login_on
# User comes back with his autologin cookie # User comes back with his autologin cookie
cookies[:autologin] = token.value cookies[Redmine::Configuration['autologin_cookie_name']] = token.value
get '/my/page' get '/my/page'
assert_response :success assert_response :success
assert_template 'my/page' assert_template 'my/page'
......
...@@ -35,11 +35,16 @@ class Redmine::SafeAttributesTest < ActiveSupport::TestCase ...@@ -35,11 +35,16 @@ class Redmine::SafeAttributesTest < ActiveSupport::TestCase
end end
class Book < Base class Book < Base
attr_accessor :title attr_accessor :title, :isbn
include Redmine::SafeAttributes include Redmine::SafeAttributes
safe_attributes :title safe_attributes :title
end end
class PublishedBook < Book
safe_attributes :isbn
end
def test_safe_attribute_names def test_safe_attribute_names
p = Person.new p = Person.new
assert_equal ['firstname', 'lastname'], p.safe_attribute_names(User.anonymous) assert_equal ['firstname', 'lastname'], p.safe_attribute_names(User.anonymous)
...@@ -84,4 +89,25 @@ class Redmine::SafeAttributesTest < ActiveSupport::TestCase ...@@ -84,4 +89,25 @@ class Redmine::SafeAttributesTest < ActiveSupport::TestCase
assert_equal 'Smith', p.lastname assert_equal 'Smith', p.lastname
assert_equal 'jsmith', p.login assert_equal 'jsmith', p.login
end 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
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 end
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'
if File.directory?(REPOSITORY_PATH)
def setup
@adapter = Redmine::Scm::Adapters::BazaarAdapter.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
...@@ -5,6 +5,7 @@ begin ...@@ -5,6 +5,7 @@ begin
class CvsAdapterTest < ActiveSupport::TestCase class CvsAdapterTest < ActiveSupport::TestCase
REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository' REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/cvs_repository'
REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
MODULE_NAME = 'test' MODULE_NAME = 'test'
if File.directory?(REPOSITORY_PATH) if File.directory?(REPOSITORY_PATH)
......
# encoding: utf-8 # encoding: utf-8
# This file includes UTF-8 "Felix Schäfer".
# We need to consider Ruby 1.9 compatibility.
require File.expand_path('../../../../../../test_helper', __FILE__) require File.expand_path('../../../../../../test_helper', __FILE__)
begin
require 'mocha'
class GitAdapterTest < ActiveSupport::TestCase class GitAdapterTest < ActiveSupport::TestCase
REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository' REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
if File.directory?(REPOSITORY_PATH) if File.directory?(REPOSITORY_PATH)
def setup def setup
@adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH) @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH)
end end
def test_branches def test_branches
assert_equal @adapter.branches, ['master', 'test_branch'] assert_equal @adapter.branches, ['master', 'test_branch']
end end
def test_getting_all_revisions def test_getting_all_revisions
assert_equal 15, @adapter.revisions('',nil,nil,:all => true).length assert_equal 15, @adapter.revisions('',nil,nil,:all => true).length
end end
def test_getting_certain_revisions def test_getting_certain_revisions
assert_equal 1, @adapter.revisions('','899a15d^','899a15d').length assert_equal 1, @adapter.revisions('','899a15d^','899a15d').length
end end
def test_getting_revisions_with_spaces_in_filename def test_getting_revisions_with_spaces_in_filename
assert_equal 1, @adapter.revisions("filemane with spaces.txt", nil, nil, :all => true).length assert_equal 1, @adapter.revisions("filemane with spaces.txt",
end 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] def test_getting_revisions_with_leading_and_trailing_spaces_in_filename
end assert_equal " filename with a leading space.txt ",
@adapter.revisions(" filename with a leading space.txt ",
def test_getting_entries_with_leading_and_trailing_spaces_in_filename nil, nil, :all => true)[0].paths[0][:path]
assert_equal " filename with a leading space.txt ", @adapter.entries('', '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c')[3].name end
end
def test_getting_entries_with_leading_and_trailing_spaces_in_filename
def test_annotate assert_equal " filename with a leading space.txt ",
annotate = @adapter.annotate('sources/watchers_controller.rb') @adapter.entries('',
assert_kind_of Redmine::Scm::Adapters::Annotate, annotate '83ca5fd546063a3c7dc2e568ba3355661a9e2b2c')[3].name
assert_equal 41, annotate.lines.size end
assert_equal "# This program is free software; you can redistribute it and/or", annotate.lines[4].strip
assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", annotate.revisions[4].identifier def test_annotate
assert_equal "jsmith", annotate.revisions[4].author annotate = @adapter.annotate('sources/watchers_controller.rb')
end assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
assert_equal 41, annotate.lines.size
def test_annotate_moved_file assert_equal "# This program is free software; you can redistribute it and/or", annotate.lines[4].strip
annotate = @adapter.annotate('renamed_test.txt') assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518",
assert_kind_of Redmine::Scm::Adapters::Annotate, annotate annotate.revisions[4].identifier
assert_equal 2, annotate.lines.size assert_equal "jsmith", annotate.revisions[4].author
end end
def test_last_rev def test_annotate_moved_file
last_rev = @adapter.lastrev("README", "4f26664364207fa8b1af9f8722647ab2d4ac5d43") annotate = @adapter.annotate('renamed_test.txt')
assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.scmid assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.identifier assert_equal 2, annotate.lines.size
assert_equal "Adam Soltys <asoltys@gmail.com>", last_rev.author end
assert_equal "2009-06-24 05:27:38".to_time, last_rev.time
end def test_last_rev
last_rev = @adapter.lastrev("README",
def test_last_rev_with_spaces_in_filename "4f26664364207fa8b1af9f8722647ab2d4ac5d43")
last_rev = @adapter.lastrev("filemane with spaces.txt", "ed5bb786bbda2dee66a2d50faf51429dbc043a7b") assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.scmid
assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.scmid assert_equal "4a07fe31bffcf2888791f3e6cbc9c4545cefe3e8", last_rev.identifier
assert_equal "ed5bb786bbda2dee66a2d50faf51429dbc043a7b", last_rev.identifier assert_equal "Adam Soltys <asoltys@gmail.com>", last_rev.author
assert_equal "Felix Schäfer <felix@fachschaften.org>", last_rev.author assert_equal "2009-06-24 05:27:38".to_time, last_rev.time
assert_equal "2010-09-18 19:59:46".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 <felix@fachschaften.org>",
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 end
else end
puts "Git test repository NOT FOUND. Skipping unit tests !!!"
def test_fake; assert true end rescue LoadError
class GitMochaFake < ActiveSupport::TestCase
def test_fake; assert(false, "Requires mocha to run those tests") end
end end
end end
...@@ -17,15 +17,27 @@ ...@@ -17,15 +17,27 @@
require File.expand_path('../../../../../../test_helper', __FILE__) require File.expand_path('../../../../../../test_helper', __FILE__)
class SubversionAdapterTest < ActiveSupport::TestCase begin
require 'mocha'
class SubversionAdapterTest < ActiveSupport::TestCase
if repository_configured?('subversion') if repository_configured?('subversion')
def test_client_version def setup
v = Redmine::Scm::Adapters::SubversionAdapter.client_version end
assert v.is_a?(Array)
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 end
else end
puts "Subversion test repository NOT FOUND. Skipping unit tests !!!"
def test_fake; assert true end rescue LoadError
class SubversionMochaFake < ActiveSupport::TestCase
def test_fake; assert(false, "Requires mocha to run those tests") end
end end
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment