Compare commits

...

16 Commits
0.9.2 ... 0.9.3

Author SHA1 Message Date
Jean-Philippe Lang
fc6ccc1ea2 tagged version 0.9.3
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/0.9.3@3515 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 15:12:09 +00:00
Jean-Philippe Lang
bfd9164c0a Merged r3506 for 0.9.3 release.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3507 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 10:21:33 +00:00
Jean-Philippe Lang
d80fb751fd Merged r3469, r3472 and r3473 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3505 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 10:10:55 +00:00
Jean-Philippe Lang
aacabbe645 Merged r3498 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3504 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 09:59:31 +00:00
Jean-Philippe Lang
8eafcbede9 Merged r3481 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3503 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 09:58:33 +00:00
Jean-Philippe Lang
43c1481998 Merged r3446 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3502 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 09:56:55 +00:00
Jean-Philippe Lang
17f60af490 Merged r3394, r3466, r3467, r3468 and r3471 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3501 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-28 09:51:10 +00:00
Jean-Philippe Lang
e5e5ad6b7a Merged r3398 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3465 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-20 10:40:02 +00:00
Jean-Philippe Lang
13fb739b56 Merged r3409 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3464 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-20 10:26:49 +00:00
Jean-Philippe Lang
73e849be58 Merged r3451 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3463 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-20 10:22:06 +00:00
Jean-Philippe Lang
0b7d4e818a Merged r3452 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3462 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-20 10:20:52 +00:00
Jean-Philippe Lang
40a4b111fa Merged r3447 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3461 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-20 10:19:02 +00:00
Jean-Philippe Lang
7d913f93c3 Merged r3448 and r3455 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3460 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-20 10:16:08 +00:00
Jean-Philippe Lang
864fd9c6a1 Merged bug fixes r3423 and r3427.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3433 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-14 15:57:56 +00:00
Jean-Philippe Lang
57dcbd7376 Merged bug fixes r3412 to r3414.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3432 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-14 15:55:00 +00:00
Jean-Philippe Lang
0ef11ef4fe Merged bug fixes r3402, r3405 to r3408 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/0.9-stable@3431 e93f8b46-1217-0410-a6f0-8f06a7374b81
2010-02-14 15:48:28 +00:00
46 changed files with 432 additions and 136 deletions

View File

@@ -21,8 +21,8 @@ class IssueRelationsController < ApplicationController
def new
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
if params[:relation] && !params[:relation][:issue_to_id].blank?
@relation.issue_to = Issue.visible.find_by_id(params[:relation][:issue_to_id])
if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end
@relation.save if request.post?
respond_to do |format|

View File

@@ -320,13 +320,9 @@ class ProjectsController < ApplicationController
@issues_by_version = {}
unless @selected_tracker_ids.empty?
@versions.each do |version|
conditions = {:tracker_id => @selected_tracker_ids}
if !@project.versions.include?(version)
conditions.merge!(:project_id => project_ids)
end
issues = version.fixed_issues.visible.find(:all,
:include => [:project, :status, :tracker, :priority],
:conditions => conditions,
:conditions => {:tracker_id => @selected_tracker_ids, :project_id => project_ids},
:order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
@issues_by_version[version] = issues
end

View File

@@ -506,17 +506,17 @@ module ApplicationHelper
# Forum messages:
# message#1218 -> Link to message with id 1218
text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
link = nil
if esc.nil?
if prefix.nil? && sep == 'r'
if project && (changeset = project.changesets.find_by_revision(oid))
link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
if project && (changeset = project.changesets.find_by_revision(identifier))
link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
:class => 'changeset',
:title => truncate_single_line(changeset.comments, :length => 100))
end
elsif sep == '#'
oid = oid.to_i
oid = identifier.to_i
case prefix
when nil
if issue = Issue.visible.find_by_id(oid, :include => :status)
@@ -547,7 +547,7 @@ module ApplicationHelper
end
elsif sep == ':'
# removes the double quotes if any
name = oid.gsub(%r{^"(.*)"$}, "\\1")
name = identifier.gsub(%r{^"(.*)"$}, "\\1")
case prefix
when 'document'
if project && document = project.documents.find_by_title(name)
@@ -584,7 +584,7 @@ module ApplicationHelper
end
end
end
leading + (link || "#{prefix}#{sep}#{oid}")
leading + (link || "#{prefix}#{sep}#{identifier}")
end
text

View File

@@ -1,5 +1,5 @@
# Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang
# Copyright (C) 2006-2010 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -57,6 +57,10 @@ class Changeset < ActiveRecord::Base
super
end
def committer=(arg)
write_attribute(:committer, self.class.to_utf8(arg.to_s))
end
def project
repository.project
end
@@ -80,9 +84,6 @@ class Changeset < ActiveRecord::Base
ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
# keywords used to fix issues
fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
# status and optional done ratio applied
fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
return if kw_regexp.blank?
@@ -100,7 +101,7 @@ class Changeset < ActiveRecord::Base
action = match[0]
target_issue_ids = match[1].scan(/\d+/)
target_issues = find_referenced_issues_by_id(target_issue_ids)
if fix_status && fix_keywords.include?(action.downcase)
if fix_keywords.include?(action.downcase) && fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
# update status of issues
logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
target_issues.each do |issue|
@@ -114,7 +115,9 @@ class Changeset < ActiveRecord::Base
end
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
issue.status = fix_status
issue.done_ratio = done_ratio if done_ratio
unless Setting.commit_fix_done_ratio.blank?
issue.done_ratio = Setting.commit_fix_done_ratio.to_i
end
Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
{ :changeset => self, :issue => issue })
issue.save
@@ -123,7 +126,8 @@ class Changeset < ActiveRecord::Base
referenced_issues += target_issues
end
self.issues = referenced_issues.uniq
referenced_issues.uniq!
self.issues = referenced_issues unless referenced_issues.empty?
end
def short_comments
@@ -154,6 +158,7 @@ class Changeset < ActiveRecord::Base
# Finds issues that can be referenced by the commit message
# i.e. issues that belong to the repository project, a subproject or a parent project
def find_referenced_issues_by_id(ids)
return [] if ids.compact.empty?
Issue.find_all_by_id(ids, :include => :project).select {|issue|
project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
}
@@ -171,11 +176,12 @@ class Changeset < ActiveRecord::Base
encoding = Setting.commit_logs_encoding.to_s.strip
unless encoding.blank? || encoding == 'UTF-8'
begin
return Iconv.conv('UTF-8', encoding, str)
str = Iconv.conv('UTF-8', encoding, str)
rescue Iconv::Failure
# do nothing here
end
end
str
# removes invalid UTF8 sequences
Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
end
end

View File

@@ -159,7 +159,8 @@ class Issue < ActiveRecord::Base
end
send :attributes_without_tracker_first=, new_attributes, *args
end
alias_method_chain :attributes=, :tracker_first
# Do not redefine alias chain on reload (see #4838)
alias_method_chain(:attributes=, :tracker_first) unless method_defined?(:attributes_without_tracker_first=)
def estimated_hours=(h)
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)

View File

@@ -56,7 +56,7 @@ class Project < ActiveRecord::Base
:delete_permission => :manage_files
acts_as_customizable
acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
:url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
:author => nil
@@ -249,7 +249,7 @@ class Project < ActiveRecord::Base
return @allowed_parents if @allowed_parents
@allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
@allowed_parents = @allowed_parents - self_and_descendants
if User.current.allowed_to?(:add_project, nil, :global => true)
if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
@allowed_parents << nil
end
unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
@@ -512,11 +512,23 @@ class Project < ActiveRecord::Base
unless project.wiki.nil?
self.wiki ||= Wiki.new
wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
wiki_pages_map = {}
project.wiki.pages.each do |page|
# Skip pages without content
next if page.content.nil?
new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
new_wiki_page.content = new_wiki_content
wiki.pages << new_wiki_page
wiki_pages_map[page.id] = new_wiki_page
end
wiki.save
# Reproduce page hierarchy
project.wiki.pages.each do |page|
if page.parent_id && wiki_pages_map[page.id]
wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
wiki_pages_map[page.id].save
end
end
end
end

View File

@@ -210,6 +210,10 @@ class Query < ActiveRecord::Base
add_custom_fields_filters(@project.all_issue_custom_fields)
else
# global filters for cross project issue list
system_shared_versions = Version.visible.find_all_by_sharing('system')
unless system_shared_versions.empty?
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
end
@available_filters

View File

@@ -136,6 +136,7 @@ class Repository < ActiveRecord::Base
end
end
@committers = nil
@found_committer_users = nil
true
else
false
@@ -146,24 +147,34 @@ class Repository < ActiveRecord::Base
# It will return nil if the committer is not yet mapped and if no User
# with the same username or email was found
def find_committer_user(committer)
if committer
unless committer.blank?
@found_committer_users ||= {}
return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
user = nil
c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
if c && c.user
c.user
user = c.user
elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
username, email = $1.strip, $3
u = User.find_by_login(username)
u ||= User.find_by_mail(email) unless email.blank?
u
user = u
end
@found_committer_users[committer] = user
user
end
end
# fetch new changesets for all repositories
# can be called periodically by an external script
# Fetches new changesets for all repositories of active projects
# Can be called periodically by an external script
# eg. ruby script/runner "Repository.fetch_changesets"
def self.fetch_changesets
find(:all).each(&:fetch_changesets)
Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
if project.repository
project.repository.fetch_changesets
end
end
end
# scan changeset comments to find related and fixed issues for all repositories

View File

@@ -40,23 +40,26 @@ class Repository::Git < Repository
# With SCM's that have a sequential commit numbering, redmine is able to be
# clever and only fetch changesets going forward from the most recent one
# it knows about. However, with git, you never know if people have merged
# commits into the middle of the repository history, so we always have to
# parse the entire log.
# commits into the middle of the repository history, so we should parse
# the entire log. Since it's way too slow for large repositories, we only
# parse 1 week before the last known commit.
# The repository can still be fully reloaded by calling #clear_changesets
# before fetching changesets (eg. for offline resync)
def fetch_changesets
# Save ourselves an expensive operation if we're already up to date
return if scm.num_revisions == changesets.count
c = changesets.find(:first, :order => 'committed_on DESC')
since = (c ? c.committed_on - 7.days : nil)
revisions = scm.revisions('', nil, nil, :all => true)
revisions = scm.revisions('', nil, nil, :all => true, :since => since)
return if revisions.nil? || revisions.empty?
# Find revisions that redmine knows about already
existing_revisions = changesets.find(:all).map!{|c| c.scmid}
recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
# Clean out revisions that are no longer in git
Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", revisions.map{|r| r.scmid}, self.id])
recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
# Subtract revisions that redmine already knows about
revisions.reject!{|r| existing_revisions.include?(r.scmid)}
recent_revisions = recent_changesets.map{|c| c.scmid}
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
# Save the remaining ones to the database
revisions.each{|r| r.save(self)} unless revisions.nil?

View File

@@ -86,7 +86,6 @@ top = headers_height + 8
<%= link_to_issue i %>
<% else %>
<span class="icon icon-package">
<%= h("#{i.project} -") unless @project && @project == i.project %>
<%= link_to_version i %>
</span>
<% end %>
@@ -211,8 +210,7 @@ top = headers_height + 10
%>
<div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
<div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
<%= h("#{i.project} -") unless @project && @project == i.project %>
<strong><%=h i %></strong>
<strong><%= format_version_name i %></strong>
</div>
<% end %>
<% top = top + 20

View File

@@ -1,14 +1,14 @@
<h1><%= link_to "#{issue.tracker.name} ##{issue.id}: #{issue.subject}", issue_url %></h1>
<h1><%= link_to(h("#{issue.tracker.name} ##{issue.id}: #{issue.subject}"), issue_url) %></h1>
<ul>
<li><%=l(:field_author)%>: <%= issue.author %></li>
<li><%=l(:field_status)%>: <%= issue.status %></li>
<li><%=l(:field_priority)%>: <%= issue.priority %></li>
<li><%=l(:field_assigned_to)%>: <%= issue.assigned_to %></li>
<li><%=l(:field_category)%>: <%= issue.category %></li>
<li><%=l(:field_fixed_version)%>: <%= issue.fixed_version %></li>
<li><%=l(:field_author)%>: <%=h issue.author %></li>
<li><%=l(:field_status)%>: <%=h issue.status %></li>
<li><%=l(:field_priority)%>: <%=h issue.priority %></li>
<li><%=l(:field_assigned_to)%>: <%=h issue.assigned_to %></li>
<li><%=l(:field_category)%>: <%=h issue.category %></li>
<li><%=l(:field_fixed_version)%>: <%=h issue.fixed_version %></li>
<% issue.custom_values.each do |c| %>
<li><%= c.custom_field.name %>: <%= show_value(c) %></li>
<li><%=h c.custom_field.name %>: <%=h show_value(c) %></li>
<% end %>
</ul>

View File

@@ -1,2 +1,2 @@
<p><%= l(:mail_body_account_activation_request, @user.login) %></p>
<p><%= l(:mail_body_account_activation_request, h(@user.login)) %></p>
<p><%= link_to @url, @url %></p>

View File

@@ -1,10 +1,10 @@
<% if @user.auth_source %>
<p><%= l(:mail_body_account_information_external, @user.auth_source.name) %></p>
<p><%= l(:mail_body_account_information_external, h(@user.auth_source.name)) %></p>
<% else %>
<p><%= l(:mail_body_account_information) %>:</p>
<ul>
<li><%= l(:field_login) %>: <%= @user.login %></li>
<li><%= l(:field_password) %>: <%= @password %></li>
<li><%= l(:field_login) %>: <%=h @user.login %></li>
<li><%= l(:field_password) %>: <%=h @password %></li>
</ul>
<% end %>

View File

@@ -1,5 +1,5 @@
<%= link_to @added_to, @added_to_url %><br />
<ul><% @attachments.each do |attachment | %>
<li><%= attachment.filename %></li>
<li><%=h attachment.filename %></li>
<% end %></ul>

View File

@@ -1,3 +1,3 @@
<%= link_to @document.title, @document_url %> (<%= @document.category.name %>)<br />
<%= link_to(h(@document.title), @document_url) %> (<%=h @document.category.name %>)<br />
<br />
<%= textilizable(@document, :description, :only_path => false) %>

View File

@@ -1,3 +1,3 @@
<%= l(:text_issue_added, :id => "##{@issue.id}", :author => @issue.author) %>
<%= l(:text_issue_added, :id => "##{@issue.id}", :author => h(@issue.author)) %>
<hr />
<%= render :partial => "issue_text_html", :locals => { :issue => @issue, :issue_url => @issue_url } %>

View File

@@ -1,4 +1,4 @@
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => @journal.user) %>
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => h(@journal.user)) %>
<ul>
<% for detail in @journal.details %>

View File

@@ -1,4 +1,4 @@
<p><%= l(:mail_body_lost_password) %><br />
<%= auto_link(@url) %></p>
<p><%= l(:field_login) %>: <b><%= @token.user.login %></b></p>
<p><%= l(:field_login) %>: <b><%=h @token.user.login %></b></p>

View File

@@ -1,4 +1,4 @@
<h1><%=h @message.board.project.name %> - <%=h @message.board.name %>: <%= link_to @message.subject, @message_url %></h1>
<em><%= @message.author %></em>
<h1><%=h @message.board.project.name %> - <%=h @message.board.name %>: <%= link_to(h(@message.subject), @message_url) %></h1>
<em><%=h @message.author %></em>
<%= textilizable(@message, :content, :only_path => false) %>

View File

@@ -1,4 +1,4 @@
<h1><%= link_to @news.title, @news_url %></h1>
<em><%= @news.author.name %></em>
<h1><%= link_to(h(@news.title), @news_url) %></h1>
<em><%=h @news.author.name %></em>
<%= textilizable(@news, :description, :only_path => false) %>

View File

@@ -9,7 +9,7 @@
<div class="splitcontentleft">
<%= textilizable @project.description %>
<ul>
<% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= link_to(h(@project.homepage), @project.homepage) %></li><% end %>
<% unless @project.homepage.blank? %><li><%=l(:field_homepage)%>: <%= auto_link(h(@project.homepage)) %></li><% end %>
<% if @subprojects.any? %>
<li><%=l(:label_subproject_plural)%>:
<%= @subprojects.collect{|p| link_to(h(p), :action => 'show', :id => p)}.join(", ") %></li>

View File

@@ -1,4 +1,70 @@
# Outgoing email settings
# = Outgoing email settings
#
# Each environment has it's own configuration options. If you are only
# running in production, only the production block needs to be configured.
#
# == Common configurations
#
# === Sendmail command
#
# production:
# delivery_method: :sendmail
#
# === Simple SMTP server at localhost
#
# production:
# delivery_method: :smtp
# smtp_settings:
# address: "localhost"
# port: 25
#
# === SMTP server at example.com using LOGIN authentication and checking HELO for foo.com
#
# production:
# delivery_method: :smtp
# smtp_settings:
# address: "example.com"
# port: 25
# authentication: :login
# domain: 'foo.com'
# user_name: 'myaccount'
# password: 'password'
#
# === SMTP server at example.com using PLAIN authentication
#
# production:
# delivery_method: :smtp
# smtp_settings:
# address: "example.com"
# port: 25
# authentication: :plain
# domain: 'example.com'
# user_name: 'myaccount'
# password: 'password'
#
# === SMTP server at using TLS (GMail)
#
# This requires some additional configuration. See the article at:
# http://redmineblog.com/articles/setup-redmine-to-send-email-using-gmail/
#
# production:
# delivery_method: :smtp
# smtp_settings:
# tls: true
# address: "smtp.gmail.com"
# port: 587
# domain: "smtp.gmail.com" # 'your.domain.com' for GoogleApps
# authentication: :plain
# user_name: "your_email@gmail.com"
# password: "your_password"
#
#
# == More configuration options
#
# See the "Configuration options" at the following website for a list of the
# full options allowed:
#
# http://wiki.rubyonrails.org/rails/pages/HowToSendEmailsWithActionMailer
production:
delivery_method: :smtp

View File

@@ -25,5 +25,5 @@ config.action_controller.session = {
config.action_controller.allow_forgery_protection = false
config.gem "thoughtbot-shoulda", :lib => "shoulda", :source => "http://gems.github.com"
config.gem "nofxx-object_daddy", :lib => "object_daddy", :source => "http://gems.github.com"
config.gem "edavis10-object_daddy", :lib => "object_daddy"
config.gem "mocha"

View File

@@ -0,0 +1,9 @@
class AddIndexOnChangesetsScmid < ActiveRecord::Migration
def self.up
add_index :changesets, [:repository_id, :scmid], :name => :changesets_repos_scmid
end
def self.down
remove_index :changesets, :name => :changesets_repos_scmid
end
end

View File

@@ -4,6 +4,26 @@ Redmine - project management software
Copyright (C) 2006-2010 Jean-Philippe Lang
http://www.redmine.org/
== 2010-02-28 v0.9.3
Adds filter for system shared versions on the cross project issue list
Makes project identifiers searchable
Remove invalid utf8 sequences from commit comments and author name
Fixed: Wrong link when "http" not included in project "Homepage" link
Fixed: Escaping in html email templates
Fixed: Pound (#) followed by number with leading zero (0) removes leading zero when rendered in wiki
Fixed: Deselecting textile text formatting causes interning empty string errors
Fixed: error with postgres when entering a non-numeric id for an issue relation
Fixed: div.task incorrectly wrapping on Gantt Chart
Fixed: Project copy loses wiki pages hierarchy
Fixed: parent project field doesn't include blank value when a member with 'add subproject' permission edits a child project
Fixed: Repository.fetch_changesets tries to fetch changesets for archived projects
Fixed: Duplicated project name for subproject version on gantt chart
Fixed: roadmap shows subprojects issues even if subprojects is unchecked
Fixed: IndexError if all the :last menu items are deleted from a menu
Fixed: Very high CPU usage for a long time when fetching commits from a large Git repository
== 2010-02-07 v0.9.2
* Fixed: Sub-project repository commits not displayed on parent project issues

View File

@@ -7,7 +7,11 @@ http://www.redmine.org/
== Requirements
* Ruby on Rails 2.3.5
* Ruby 1.8.6 or 1.8.7
* Ruby on Rails 2.3.5 (official downloadable Redmine releases are packaged with
the appropriate Rails version)
* A database:
* MySQL (tested with MySQL 5)
* PostgreSQL (tested with PostgreSQL 8.1)
@@ -26,15 +30,15 @@ Optional:
3. Configure database parameters in config/database.yml
for "production" environment (default database is MySQL)
4. Create the database structure. Under the application main directory:
rake db:migrate RAILS_ENV="production"
It will create tables and an administrator account.
5. Generate a session store secret
4. Generate a session store secret
Redmine stores session data in cookies by default, which requires
a secret to be generated. Run:
rake config/initializers/session_store.rb
5. Create the database structure. Under the application main directory:
rake db:migrate RAILS_ENV="production"
It will create tables and an administrator account.
6. Setting up permissions
The user who runs Redmine must have write permission on the following
subdirectories: files, log, tmp (create the last one if not present).

View File

@@ -13,17 +13,17 @@ http://www.redmine.org/
and SMTP settings (RAILS_ROOT/config/email.yml)
into the new config directory
DO NOT REPLACE ANY OTHERS FILES.
3. Migrate your database (please make a backup before doing this):
rake db:migrate RAILS_ENV="production"
4. Copy the RAILS_ROOT/files directory content into your new installation
This directory contains all the attached files
5. Generate a session store secret
3. Generate a session store secret
Redmine stores session data in cookies by default, which requires
a secret to be generated. Run:
rake config/initializers/session_store.rb
4. Migrate your database (please make a backup before doing this):
rake db:migrate RAILS_ENV="production"
5. Copy the RAILS_ROOT/files directory content into your new installation
This directory contains all the attached files
== Notes

View File

@@ -84,6 +84,14 @@ module TreeNodePatch
end
# Wrapp remove! making sure to decrement the last_items counter if
# the removed child was a last item
def remove!(child)
@last_items_count -= +1 if child && child.last
super
end
# Will return the position (zero-based) of the current child in
# it's parent
def position
@@ -352,7 +360,7 @@ module Redmine
target_root.add(MenuItem.new(name, url, options))
end
elsif options.delete(:last)
elsif options[:last] # don't delete, needs to be stored
target_root.add_last(MenuItem.new(name, url, options))
else
target_root.add(MenuItem.new(name, url, options))
@@ -386,7 +394,7 @@ module Redmine
class MenuItem < Tree::TreeNode
include Redmine::I18n
attr_reader :name, :url, :param, :condition, :parent, :child_menus
attr_reader :name, :url, :param, :condition, :parent, :child_menus, :last
def initialize(name, url, options)
raise ArgumentError, "Invalid option :if for menu item '#{name}'" if options[:if] && !options[:if].respond_to?(:call)
@@ -403,6 +411,7 @@ module Redmine
@html_options[:class] = [@html_options[:class], @name.to_s.dasherize].compact.join(' ')
@parent = options[:parent]
@child_menus = options[:children]
@last = options[:last] || false
super @name.to_sym
end

View File

@@ -286,21 +286,23 @@ module Redmine
end
def save(repo)
if repo.changesets.find_by_scmid(scmid.to_s).nil?
changeset = Changeset.create!(
Changeset.transaction do
changeset = Changeset.new(
:repository => repo,
:revision => identifier,
:scmid => scmid,
:committer => author,
:committed_on => time,
:comments => message)
paths.each do |file|
Change.create!(
:changeset => changeset,
:action => file[:action],
:path => file[:path])
end
if changeset.save
paths.each do |file|
Change.create(
:changeset => changeset,
:action => file[:action],
:path => file[:path])
end
end
end
end
end

View File

@@ -33,21 +33,22 @@ module Redmine
end
def branches
branches = []
return @branches if @branches
@branches = []
cmd = "#{GIT_BIN} --git-dir #{target('')} branch"
shellout(cmd) do |io|
io.each_line do |line|
branches << line.match('\s*\*?\s*(.*)$')[1]
@branches << line.match('\s*\*?\s*(.*)$')[1]
end
end
branches.sort!
@branches.sort!
end
def tags
tags = []
return @tags if @tags
cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
shellout(cmd) do |io|
io.readlines.sort!.map{|t| t.strip}
@tags = io.readlines.sort!.map{|t| t.strip}
end
end
@@ -110,20 +111,16 @@ module Redmine
end
end
def num_revisions
cmd = "#{GIT_BIN} --git-dir #{target('')} log --all --pretty=format:'' | wc -l"
shellout(cmd) {|io| io.gets.chomp.to_i + 1}
end
def revisions(path, identifier_from, identifier_to, options={})
revisions = Revisions.new
cmd = "#{GIT_BIN} --git-dir #{target('')} log --find-copies-harder --raw --date=iso --pretty=fuller"
cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
cmd << " --reverse" if options[:reverse]
cmd << " --all" if options[:all]
cmd << " -n #{options[:limit]} " if options[:limit]
cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
cmd << " #{shell_quote identifier_to} " if identifier_to
cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
cmd << " -- #{path}" if path && !path.empty?
shellout(cmd) do |io|

View File

@@ -4,7 +4,7 @@ module Redmine
module VERSION #:nodoc:
MAJOR = 0
MINOR = 9
TINY = 2
TINY = 3
# Branch values:
# * official release: nil

View File

@@ -25,17 +25,17 @@ module Redmine
end
def register(name, formatter, helper)
raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name.to_sym]
@@formatters[name.to_sym] = {:formatter => formatter, :helper => helper}
raise ArgumentError, "format name '#{name}' is already taken" if @@formatters[name.to_s]
@@formatters[name.to_s] = {:formatter => formatter, :helper => helper}
end
def formatter_for(name)
entry = @@formatters[name.to_sym]
entry = @@formatters[name.to_s]
(entry && entry[:formatter]) || Redmine::WikiFormatting::NullFormatter::Formatter
end
def helper_for(name)
entry = @@formatters[name.to_sym]
entry = @@formatters[name.to_s]
(entry && entry[:helper]) || Redmine::WikiFormatting::NullFormatter::Helper
end

View File

@@ -728,6 +728,7 @@ background-image:url('../images/close_hl.png');
padding:0;
margin:0;
line-height:0.8em;
white-space:nowrap;
}
.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }

View File

@@ -1,6 +1,7 @@
class Issue < ActiveRecord::Base
generator_for :subject, :method => :next_subject
generator_for :author, :method => :next_author
generator_for :priority, :method => :fetch_priority
def self.next_subject
@last_subject ||= 'Subject 0'
@@ -12,4 +13,8 @@ class Issue < ActiveRecord::Base
User.generate_with_protected!
end
def self.fetch_priority
IssuePriority.first || IssuePriority.generate!
end
end

1
test/fixtures/encoding/iso-8859-1.txt vendored Normal file
View File

@@ -0,0 +1 @@
Texte encodé en ISO-8859-1.

View File

@@ -71,4 +71,28 @@ wiki_contents_006:
version: 1
author_id: 1
comments:
wiki_contents_007:
text: This is a child page
updated_on: 2007-03-08 00:18:07 +01:00
page_id: 7
id: 7
version: 1
author_id: 1
comments:
wiki_contents_008:
text: This is a parent page
updated_on: 2007-03-08 00:18:07 +01:00
page_id: 8
id: 8
version: 1
author_id: 1
comments:
wiki_contents_009:
text: This is a child page
updated_on: 2007-03-08 00:18:07 +01:00
page_id: 9
id: 9
version: 1
author_id: 1
comments:

View File

@@ -41,4 +41,25 @@ wiki_pages_006:
wiki_id: 1
protected: false
parent_id: 2
wiki_pages_007:
created_on: 2007-03-08 00:18:07 +01:00
title: Child_page_1
id: 7
wiki_id: 2
protected: false
parent_id: 8
wiki_pages_008:
created_on: 2007-03-08 00:18:07 +01:00
title: Parent_page
id: 8
wiki_id: 2
protected: false
parent_id:
wiki_pages_009:
created_on: 2007-03-08 00:18:07 +01:00
title: Child_page_2
id: 9
wiki_id: 2
protected: false
parent_id: 8

View File

@@ -40,6 +40,24 @@ class IssueRelationsControllerTest < ActionController::TestCase
end
end
def test_new_should_accept_id_with_hash
assert_difference 'IssueRelation.count' do
@request.session[:user_id] = 3
post :new, :issue_id => 1,
:relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''}
end
end
def test_new_should_not_break_with_non_numerical_id
assert_no_difference 'IssueRelation.count' do
assert_nothing_raised do
@request.session[:user_id] = 3
post :new, :issue_id => 1,
:relation => {:issue_to_id => 'foo', :relation_type => 'relates', :delay => ''}
end
end
end
def test_should_create_relations_with_visible_issues_only
Setting.cross_project_issue_relations = '1'
assert_nil Issue.visible(User.find(3)).find_by_id(4)

View File

@@ -1,30 +1,16 @@
module ObjectDaddyHelpers
# TODO: The gem or official version of ObjectDaddy doesn't set
# protected attributes so they need to be wrapped.
# TODO: Remove these three once everyone has ported their code to use the
# new object_daddy version with protected attribute support
def User.generate_with_protected(attributes={})
user = User.spawn_with_protected(attributes)
user.save
user
User.generate(attributes)
end
# TODO: The gem or official version of ObjectDaddy doesn't set
# protected attributes so they need to be wrapped.
def User.generate_with_protected!(attributes={})
user = User.spawn_with_protected(attributes)
user.save!
user
User.generate!(attributes)
end
# TODO: The gem or official version of ObjectDaddy doesn't set
# protected attributes so they need to be wrapped.
def User.spawn_with_protected(attributes={})
user = User.spawn(attributes) do |user|
user.login = User.next_login
attributes.each do |attr,v|
user.send("#{attr}=", v)
end
end
user
User.spawn(attributes)
end
# Generate the default Query

View File

@@ -1,5 +1,7 @@
# redMine - project management software
# Copyright (C) 2006-2007 Jean-Philippe Lang
# encoding: utf-8
#
# Redmine - project management software
# Copyright (C) 2006-2010 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -117,4 +119,18 @@ class ChangesetTest < ActiveSupport::TestCase
changeset = Changeset.find_by_revision('10')
assert_nil changeset.next
end
def test_comments_should_be_converted_to_utf8
with_settings :commit_logs_encoding => 'ISO-8859-1' do
c = Changeset.new
c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
assert_equal "Texte encodé en ISO-8859-1.", c.comments
end
end
def test_invalid_utf8_sequences_in_comments_should_be_stripped
c = Changeset.new
c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
assert_equal "Texte encod en ISO-8859-1.", c.comments
end
end

View File

@@ -193,6 +193,8 @@ RAW
'!version:1.0' => 'version:1.0',
'!version:"1.0"' => 'version:"1.0"',
'!source:/some/file' => 'source:/some/file',
# not found
'#0123456789' => '#0123456789',
# invalid expressions
'source:' => 'source:',
# url hash

View File

@@ -17,7 +17,7 @@
require File.dirname(__FILE__) + '/../../../../test_helper'
class Redmine::MenuManager::MapperTest < Test::Unit::TestCase
class Redmine::MenuManager::MapperTest < ActiveSupport::TestCase
context "Mapper#initialize" do
should "be tested"
end
@@ -163,4 +163,21 @@ class Redmine::MenuManager::MapperTest < Test::Unit::TestCase
menu_mapper = Redmine::MenuManager::Mapper.new(:test_menu, {})
assert_nil menu_mapper.delete(:test_missing)
end
test 'deleting all items' do
# Exposed by deleting :last items
Redmine::MenuManager.map :test_menu do |menu|
menu.push :not_last, Redmine::Info.help_url
menu.push :administration, { :controller => 'projects', :action => 'show'}, {:last => true}
menu.push :help, Redmine::Info.help_url, :last => true
end
assert_nothing_raised do
Redmine::MenuManager.map :test_menu do |menu|
menu.delete(:administration)
menu.delete(:help)
menu.push :test_overview, { :controller => 'projects', :action => 'show'}, {}
end
end
end
end

View File

@@ -19,6 +19,16 @@ require File.dirname(__FILE__) + '/../../../test_helper'
class Redmine::WikiFormattingTest < ActiveSupport::TestCase
def test_textile_formatter
assert_equal Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting.formatter_for('textile')
assert_equal Redmine::WikiFormatting::Textile::Helper, Redmine::WikiFormatting.helper_for('textile')
end
def test_null_formatter
assert_equal Redmine::WikiFormatting::NullFormatter::Formatter, Redmine::WikiFormatting.formatter_for('')
assert_equal Redmine::WikiFormatting::NullFormatter::Helper, Redmine::WikiFormatting.helper_for('')
end
def test_should_link_urls_and_email_addresses
raw = <<-DIFF
This is a sample *text* with a link: http://www.redmine.org

View File

@@ -285,6 +285,48 @@ class ProjectTest < ActiveSupport::TestCase
assert Project.new.allowed_parents.compact.empty?
end
def test_allowed_parents_with_add_subprojects_permission
Role.find(1).remove_permission!(:add_project)
Role.find(1).add_permission!(:add_subprojects)
User.current = User.find(2)
# new project
assert !Project.new.allowed_parents.include?(nil)
assert Project.new.allowed_parents.include?(Project.find(1))
# existing root project
assert Project.find(1).allowed_parents.include?(nil)
# existing child
assert Project.find(3).allowed_parents.include?(Project.find(1))
assert !Project.find(3).allowed_parents.include?(nil)
end
def test_allowed_parents_with_add_project_permission
Role.find(1).add_permission!(:add_project)
Role.find(1).remove_permission!(:add_subprojects)
User.current = User.find(2)
# new project
assert Project.new.allowed_parents.include?(nil)
assert !Project.new.allowed_parents.include?(Project.find(1))
# existing root project
assert Project.find(1).allowed_parents.include?(nil)
# existing child
assert Project.find(3).allowed_parents.include?(Project.find(1))
assert Project.find(3).allowed_parents.include?(nil)
end
def test_allowed_parents_with_add_project_and_subprojects_permission
Role.find(1).add_permission!(:add_project)
Role.find(1).add_permission!(:add_subprojects)
User.current = User.find(2)
# new project
assert Project.new.allowed_parents.include?(nil)
assert Project.new.allowed_parents.include?(Project.find(1))
# existing root project
assert Project.find(1).allowed_parents.include?(nil)
# existing child
assert Project.find(3).allowed_parents.include?(Project.find(1))
assert Project.find(3).allowed_parents.include?(nil)
end
def test_users_by_role
users_by_role = Project.find(1).users_by_role
assert_kind_of Hash, users_by_role
@@ -555,7 +597,7 @@ class ProjectTest < ActiveSupport::TestCase
end
should "copy issues" do
@source_project.issues << Issue.generate!(:status_id => 5,
@source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
:subject => "copy issue status",
:tracker_id => 1,
:assigned_to_id => 2,
@@ -681,16 +723,24 @@ class ProjectTest < ActiveSupport::TestCase
assert_equal "Start page", @project.wiki.start_page
end
should "copy wiki pages and content" do
assert @project.copy(@source_project)
should "copy wiki pages and content with hierarchy" do
assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
assert @project.copy(@source_project)
end
assert @project.wiki
assert_equal 1, @project.wiki.pages.length
assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
@project.wiki.pages.each do |wiki_page|
assert wiki_page.content
assert !@source_project.wiki.pages.include?(wiki_page)
end
parent = @project.wiki.find_page('Parent_page')
child1 = @project.wiki.find_page('Child_page_1')
child2 = @project.wiki.find_page('Child_page_2')
assert_equal parent, child1.parent
assert_equal parent, child2.parent
end
should "copy issue categories" do

View File

@@ -26,6 +26,13 @@ class QueryTest < ActiveSupport::TestCase
assert !query.available_filters.has_key?('cf_3')
end
def test_system_shared_versions_should_be_available_in_global_queries
Version.find(2).update_attribute :sharing, 'system'
query = Query.new(:project => nil, :name => '_')
assert query.available_filters.has_key?('fixed_version_id')
assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
end
def find_issues_with_query(query)
Issue.find :all,
:include => [ :assigned_to, :status, :tracker, :project, :priority ],

View File

@@ -35,7 +35,7 @@ class RepositoryGitTest < ActiveSupport::TestCase
@repository.reload
assert_equal 12, @repository.changesets.count
assert_equal 20, @repository.changes.count
assert_equal 21, @repository.changes.count
commit = @repository.changesets.find(:first, :order => 'committed_on ASC')
assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments