##// END OF EJS Templates
Merged r2164 to r2167, r2188 and r2189 from trunk....
Jean-Philippe Lang -
r2214:dea10c54f9f9
parent child
Show More
@@ -1,495 +1,498
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => :new
19 menu_item :new_issue, :only => :new
20
20
21 before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
21 before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
22 before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
23 before_filter :find_project, :only => [:new, :update_form, :preview]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
24 before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
25 before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 accept_key_auth :index, :changes
26 accept_key_auth :index, :changes
27
27
28 helper :journals
28 helper :journals
29 helper :projects
29 helper :projects
30 include ProjectsHelper
30 include ProjectsHelper
31 helper :custom_fields
31 helper :custom_fields
32 include CustomFieldsHelper
32 include CustomFieldsHelper
33 helper :ifpdf
33 helper :ifpdf
34 include IfpdfHelper
34 include IfpdfHelper
35 helper :issue_relations
35 helper :issue_relations
36 include IssueRelationsHelper
36 include IssueRelationsHelper
37 helper :watchers
37 helper :watchers
38 include WatchersHelper
38 include WatchersHelper
39 helper :attachments
39 helper :attachments
40 include AttachmentsHelper
40 include AttachmentsHelper
41 helper :queries
41 helper :queries
42 helper :sort
42 helper :sort
43 include SortHelper
43 include SortHelper
44 include IssuesHelper
44 include IssuesHelper
45 helper :timelog
45 helper :timelog
46
46
47 def index
47 def index
48 retrieve_query
48 retrieve_query
49 sort_init 'id', 'desc'
49 sort_init 'id', 'desc'
50 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
50 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
51
51
52 if @query.valid?
52 if @query.valid?
53 limit = per_page_option
53 limit = per_page_option
54 respond_to do |format|
54 respond_to do |format|
55 format.html { }
55 format.html { }
56 format.atom { }
56 format.atom { }
57 format.csv { limit = Setting.issues_export_limit.to_i }
57 format.csv { limit = Setting.issues_export_limit.to_i }
58 format.pdf { limit = Setting.issues_export_limit.to_i }
58 format.pdf { limit = Setting.issues_export_limit.to_i }
59 end
59 end
60 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
61 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61 @issue_pages = Paginator.new self, @issue_count, limit, params['page']
62 @issues = Issue.find :all, :order => sort_clause,
62 @issues = Issue.find :all, :order => sort_clause,
63 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
64 :conditions => @query.statement,
64 :conditions => @query.statement,
65 :limit => limit,
65 :limit => limit,
66 :offset => @issue_pages.current.offset
66 :offset => @issue_pages.current.offset
67 respond_to do |format|
67 respond_to do |format|
68 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
69 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
69 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
70 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
70 format.csv { send_data(issues_to_csv(@issues, @project).read, :type => 'text/csv; header=present', :filename => 'export.csv') }
71 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
71 format.pdf { send_data(render(:template => 'issues/index.rfpdf', :layout => false), :type => 'application/pdf', :filename => 'export.pdf') }
72 end
72 end
73 else
73 else
74 # Send html if the query is not valid
74 # Send html if the query is not valid
75 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
75 render(:template => 'issues/index.rhtml', :layout => !request.xhr?)
76 end
76 end
77 rescue ActiveRecord::RecordNotFound
77 rescue ActiveRecord::RecordNotFound
78 render_404
78 render_404
79 end
79 end
80
80
81 def changes
81 def changes
82 retrieve_query
82 retrieve_query
83 sort_init 'id', 'desc'
83 sort_init 'id', 'desc'
84 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
84 sort_update({'id' => "#{Issue.table_name}.id"}.merge(@query.columns.inject({}) {|h, c| h[c.name.to_s] = c.sortable; h}))
85
85
86 if @query.valid?
86 if @query.valid?
87 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
87 @journals = Journal.find :all, :include => [ :details, :user, {:issue => [:project, :author, :tracker, :status]} ],
88 :conditions => @query.statement,
88 :conditions => @query.statement,
89 :limit => 25,
89 :limit => 25,
90 :order => "#{Journal.table_name}.created_on DESC"
90 :order => "#{Journal.table_name}.created_on DESC"
91 end
91 end
92 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
92 @title = (@project ? @project.name : Setting.app_title) + ": " + (@query.new_record? ? l(:label_changes_details) : @query.name)
93 render :layout => false, :content_type => 'application/atom+xml'
93 render :layout => false, :content_type => 'application/atom+xml'
94 rescue ActiveRecord::RecordNotFound
94 rescue ActiveRecord::RecordNotFound
95 render_404
95 render_404
96 end
96 end
97
97
98 def show
98 def show
99 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
100 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.each_with_index {|j,i| j.indice = i+1}
101 @journals.reverse! if User.current.wants_comments_in_reverse_order?
101 @journals.reverse! if User.current.wants_comments_in_reverse_order?
102 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
102 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
103 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
103 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
104 @priorities = Enumeration::get_values('IPRI')
104 @priorities = Enumeration::get_values('IPRI')
105 @time_entry = TimeEntry.new
105 @time_entry = TimeEntry.new
106 respond_to do |format|
106 respond_to do |format|
107 format.html { render :template => 'issues/show.rhtml' }
107 format.html { render :template => 'issues/show.rhtml' }
108 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
108 format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
109 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
109 format.pdf { send_data(render(:template => 'issues/show.rfpdf', :layout => false), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
110 end
110 end
111 end
111 end
112
112
113 # Add a new issue
113 # Add a new issue
114 # The new issue will be created from an existing one if copy_from parameter is given
114 # The new issue will be created from an existing one if copy_from parameter is given
115 def new
115 def new
116 @issue = Issue.new
116 @issue = Issue.new
117 @issue.copy_from(params[:copy_from]) if params[:copy_from]
117 @issue.copy_from(params[:copy_from]) if params[:copy_from]
118 @issue.project = @project
118 @issue.project = @project
119 # Tracker must be set before custom field values
119 # Tracker must be set before custom field values
120 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
120 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
121 if @issue.tracker.nil?
121 if @issue.tracker.nil?
122 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
122 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
123 render :nothing => true, :layout => true
123 render :nothing => true, :layout => true
124 return
124 return
125 end
125 end
126 @issue.attributes = params[:issue]
126 if params[:issue].is_a?(Hash)
127 @issue.attributes = params[:issue]
128 @issue.watcher_user_ids = params[:issue]['watcher_user_ids'] if User.current.allowed_to?(:add_issue_watchers, @project)
129 end
127 @issue.author = User.current
130 @issue.author = User.current
128
131
129 default_status = IssueStatus.default
132 default_status = IssueStatus.default
130 unless default_status
133 unless default_status
131 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
134 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
132 render :nothing => true, :layout => true
135 render :nothing => true, :layout => true
133 return
136 return
134 end
137 end
135 @issue.status = default_status
138 @issue.status = default_status
136 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
139 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
137
140
138 if request.get? || request.xhr?
141 if request.get? || request.xhr?
139 @issue.start_date ||= Date.today
142 @issue.start_date ||= Date.today
140 else
143 else
141 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
144 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
142 # Check that the user is allowed to apply the requested status
145 # Check that the user is allowed to apply the requested status
143 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
146 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
144 if @issue.save
147 if @issue.save
145 attach_files(@issue, params[:attachments])
148 attach_files(@issue, params[:attachments])
146 flash[:notice] = l(:notice_successful_create)
149 flash[:notice] = l(:notice_successful_create)
147 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
150 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
148 redirect_to :controller => 'issues', :action => 'show', :id => @issue
151 redirect_to :controller => 'issues', :action => 'show', :id => @issue
149 return
152 return
150 end
153 end
151 end
154 end
152 @priorities = Enumeration::get_values('IPRI')
155 @priorities = Enumeration::get_values('IPRI')
153 render :layout => !request.xhr?
156 render :layout => !request.xhr?
154 end
157 end
155
158
156 # Attributes that can be updated on workflow transition (without :edit permission)
159 # Attributes that can be updated on workflow transition (without :edit permission)
157 # TODO: make it configurable (at least per role)
160 # TODO: make it configurable (at least per role)
158 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
161 UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
159
162
160 def edit
163 def edit
161 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
164 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
162 @priorities = Enumeration::get_values('IPRI')
165 @priorities = Enumeration::get_values('IPRI')
163 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
166 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
164 @time_entry = TimeEntry.new
167 @time_entry = TimeEntry.new
165
168
166 @notes = params[:notes]
169 @notes = params[:notes]
167 journal = @issue.init_journal(User.current, @notes)
170 journal = @issue.init_journal(User.current, @notes)
168 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
171 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
169 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
172 if (@edit_allowed || !@allowed_statuses.empty?) && params[:issue]
170 attrs = params[:issue].dup
173 attrs = params[:issue].dup
171 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
174 attrs.delete_if {|k,v| !UPDATABLE_ATTRS_ON_TRANSITION.include?(k) } unless @edit_allowed
172 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
175 attrs.delete(:status_id) unless @allowed_statuses.detect {|s| s.id.to_s == attrs[:status_id].to_s}
173 @issue.attributes = attrs
176 @issue.attributes = attrs
174 end
177 end
175
178
176 if request.post?
179 if request.post?
177 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
180 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
178 @time_entry.attributes = params[:time_entry]
181 @time_entry.attributes = params[:time_entry]
179 attachments = attach_files(@issue, params[:attachments])
182 attachments = attach_files(@issue, params[:attachments])
180 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
183 attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
181
184
182 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
185 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})
183
186
184 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
187 if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
185 # Log spend time
188 # Log spend time
186 if current_role.allowed_to?(:log_time)
189 if current_role.allowed_to?(:log_time)
187 @time_entry.save
190 @time_entry.save
188 end
191 end
189 if !journal.new_record?
192 if !journal.new_record?
190 # Only send notification if something was actually changed
193 # Only send notification if something was actually changed
191 flash[:notice] = l(:notice_successful_update)
194 flash[:notice] = l(:notice_successful_update)
192 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
195 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
193 end
196 end
194 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
197 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
195 end
198 end
196 end
199 end
197 rescue ActiveRecord::StaleObjectError
200 rescue ActiveRecord::StaleObjectError
198 # Optimistic locking exception
201 # Optimistic locking exception
199 flash.now[:error] = l(:notice_locking_conflict)
202 flash.now[:error] = l(:notice_locking_conflict)
200 end
203 end
201
204
202 def reply
205 def reply
203 journal = Journal.find(params[:journal_id]) if params[:journal_id]
206 journal = Journal.find(params[:journal_id]) if params[:journal_id]
204 if journal
207 if journal
205 user = journal.user
208 user = journal.user
206 text = journal.notes
209 text = journal.notes
207 else
210 else
208 user = @issue.author
211 user = @issue.author
209 text = @issue.description
212 text = @issue.description
210 end
213 end
211 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
214 content = "#{ll(Setting.default_language, :text_user_wrote, user)}\\n> "
212 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
215 content << text.to_s.strip.gsub(%r{<pre>((.|\s)*?)</pre>}m, '[...]').gsub('"', '\"').gsub(/(\r?\n|\r\n?)/, "\\n> ") + "\\n\\n"
213 render(:update) { |page|
216 render(:update) { |page|
214 page.<< "$('notes').value = \"#{content}\";"
217 page.<< "$('notes').value = \"#{content}\";"
215 page.show 'update'
218 page.show 'update'
216 page << "Form.Element.focus('notes');"
219 page << "Form.Element.focus('notes');"
217 page << "Element.scrollTo('update');"
220 page << "Element.scrollTo('update');"
218 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
221 page << "$('notes').scrollTop = $('notes').scrollHeight - $('notes').clientHeight;"
219 }
222 }
220 end
223 end
221
224
222 # Bulk edit a set of issues
225 # Bulk edit a set of issues
223 def bulk_edit
226 def bulk_edit
224 if request.post?
227 if request.post?
225 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
228 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
226 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
229 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
227 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
230 assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
228 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
231 category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
229 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
232 fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
230
233
231 unsaved_issue_ids = []
234 unsaved_issue_ids = []
232 @issues.each do |issue|
235 @issues.each do |issue|
233 journal = issue.init_journal(User.current, params[:notes])
236 journal = issue.init_journal(User.current, params[:notes])
234 issue.priority = priority if priority
237 issue.priority = priority if priority
235 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
238 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
236 issue.category = category if category || params[:category_id] == 'none'
239 issue.category = category if category || params[:category_id] == 'none'
237 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
240 issue.fixed_version = fixed_version if fixed_version || params[:fixed_version_id] == 'none'
238 issue.start_date = params[:start_date] unless params[:start_date].blank?
241 issue.start_date = params[:start_date] unless params[:start_date].blank?
239 issue.due_date = params[:due_date] unless params[:due_date].blank?
242 issue.due_date = params[:due_date] unless params[:due_date].blank?
240 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
243 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
241 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
244 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
242 # Don't save any change to the issue if the user is not authorized to apply the requested status
245 # Don't save any change to the issue if the user is not authorized to apply the requested status
243 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
246 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
244 # Send notification for each issue (if changed)
247 # Send notification for each issue (if changed)
245 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
248 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
246 else
249 else
247 # Keep unsaved issue ids to display them in flash error
250 # Keep unsaved issue ids to display them in flash error
248 unsaved_issue_ids << issue.id
251 unsaved_issue_ids << issue.id
249 end
252 end
250 end
253 end
251 if unsaved_issue_ids.empty?
254 if unsaved_issue_ids.empty?
252 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
255 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
253 else
256 else
254 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
257 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
255 end
258 end
256 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
259 redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project})
257 return
260 return
258 end
261 end
259 # Find potential statuses the user could be allowed to switch issues to
262 # Find potential statuses the user could be allowed to switch issues to
260 @available_statuses = Workflow.find(:all, :include => :new_status,
263 @available_statuses = Workflow.find(:all, :include => :new_status,
261 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
264 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq.sort
262 end
265 end
263
266
264 def move
267 def move
265 @allowed_projects = []
268 @allowed_projects = []
266 # find projects to which the user is allowed to move the issue
269 # find projects to which the user is allowed to move the issue
267 if User.current.admin?
270 if User.current.admin?
268 # admin is allowed to move issues to any active (visible) project
271 # admin is allowed to move issues to any active (visible) project
269 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
272 @allowed_projects = Project.find(:all, :conditions => Project.visible_by(User.current), :order => 'name')
270 else
273 else
271 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
274 User.current.memberships.each {|m| @allowed_projects << m.project if m.role.allowed_to?(:move_issues)}
272 end
275 end
273 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
276 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id]
274 @target_project ||= @project
277 @target_project ||= @project
275 @trackers = @target_project.trackers
278 @trackers = @target_project.trackers
276 if request.post?
279 if request.post?
277 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
280 new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id])
278 unsaved_issue_ids = []
281 unsaved_issue_ids = []
279 @issues.each do |issue|
282 @issues.each do |issue|
280 issue.init_journal(User.current)
283 issue.init_journal(User.current)
281 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
284 unsaved_issue_ids << issue.id unless issue.move_to(@target_project, new_tracker)
282 end
285 end
283 if unsaved_issue_ids.empty?
286 if unsaved_issue_ids.empty?
284 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
287 flash[:notice] = l(:notice_successful_update) unless @issues.empty?
285 else
288 else
286 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
289 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, @issues.size, '#' + unsaved_issue_ids.join(', #'))
287 end
290 end
288 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
291 redirect_to :controller => 'issues', :action => 'index', :project_id => @project
289 return
292 return
290 end
293 end
291 render :layout => false if request.xhr?
294 render :layout => false if request.xhr?
292 end
295 end
293
296
294 def destroy
297 def destroy
295 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
298 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
296 if @hours > 0
299 if @hours > 0
297 case params[:todo]
300 case params[:todo]
298 when 'destroy'
301 when 'destroy'
299 # nothing to do
302 # nothing to do
300 when 'nullify'
303 when 'nullify'
301 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
304 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
302 when 'reassign'
305 when 'reassign'
303 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
306 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
304 if reassign_to.nil?
307 if reassign_to.nil?
305 flash.now[:error] = l(:error_issue_not_found_in_project)
308 flash.now[:error] = l(:error_issue_not_found_in_project)
306 return
309 return
307 else
310 else
308 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
311 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
309 end
312 end
310 else
313 else
311 # display the destroy form
314 # display the destroy form
312 return
315 return
313 end
316 end
314 end
317 end
315 @issues.each(&:destroy)
318 @issues.each(&:destroy)
316 redirect_to :action => 'index', :project_id => @project
319 redirect_to :action => 'index', :project_id => @project
317 end
320 end
318
321
319 def destroy_attachment
322 def destroy_attachment
320 a = @issue.attachments.find(params[:attachment_id])
323 a = @issue.attachments.find(params[:attachment_id])
321 a.destroy
324 a.destroy
322 journal = @issue.init_journal(User.current)
325 journal = @issue.init_journal(User.current)
323 journal.details << JournalDetail.new(:property => 'attachment',
326 journal.details << JournalDetail.new(:property => 'attachment',
324 :prop_key => a.id,
327 :prop_key => a.id,
325 :old_value => a.filename)
328 :old_value => a.filename)
326 journal.save
329 journal.save
327 redirect_to :action => 'show', :id => @issue
330 redirect_to :action => 'show', :id => @issue
328 end
331 end
329
332
330 def gantt
333 def gantt
331 @gantt = Redmine::Helpers::Gantt.new(params)
334 @gantt = Redmine::Helpers::Gantt.new(params)
332 retrieve_query
335 retrieve_query
333 if @query.valid?
336 if @query.valid?
334 events = []
337 events = []
335 # Issues that have start and due dates
338 # Issues that have start and due dates
336 events += Issue.find(:all,
339 events += Issue.find(:all,
337 :order => "start_date, due_date",
340 :order => "start_date, due_date",
338 :include => [:tracker, :status, :assigned_to, :priority, :project],
341 :include => [:tracker, :status, :assigned_to, :priority, :project],
339 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
342 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
340 )
343 )
341 # Issues that don't have a due date but that are assigned to a version with a date
344 # Issues that don't have a due date but that are assigned to a version with a date
342 events += Issue.find(:all,
345 events += Issue.find(:all,
343 :order => "start_date, effective_date",
346 :order => "start_date, effective_date",
344 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
347 :include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
345 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
348 :conditions => ["(#{@query.statement}) AND (((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
346 )
349 )
347 # Versions
350 # Versions
348 events += Version.find(:all, :include => :project,
351 events += Version.find(:all, :include => :project,
349 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
352 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
350
353
351 @gantt.events = events
354 @gantt.events = events
352 end
355 end
353
356
354 respond_to do |format|
357 respond_to do |format|
355 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
358 format.html { render :template => "issues/gantt.rhtml", :layout => !request.xhr? }
356 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
359 format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png") } if @gantt.respond_to?('to_image')
357 format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
360 format.pdf { send_data(render(:template => "issues/gantt.rfpdf", :layout => false), :type => 'application/pdf', :filename => "#{@project.nil? ? '' : "#{@project.identifier}-" }gantt.pdf") }
358 end
361 end
359 end
362 end
360
363
361 def calendar
364 def calendar
362 if params[:year] and params[:year].to_i > 1900
365 if params[:year] and params[:year].to_i > 1900
363 @year = params[:year].to_i
366 @year = params[:year].to_i
364 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
367 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
365 @month = params[:month].to_i
368 @month = params[:month].to_i
366 end
369 end
367 end
370 end
368 @year ||= Date.today.year
371 @year ||= Date.today.year
369 @month ||= Date.today.month
372 @month ||= Date.today.month
370
373
371 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
374 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
372 retrieve_query
375 retrieve_query
373 if @query.valid?
376 if @query.valid?
374 events = []
377 events = []
375 events += Issue.find(:all,
378 events += Issue.find(:all,
376 :include => [:tracker, :status, :assigned_to, :priority, :project],
379 :include => [:tracker, :status, :assigned_to, :priority, :project],
377 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
380 :conditions => ["(#{@query.statement}) AND ((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?))", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
378 )
381 )
379 events += Version.find(:all, :include => :project,
382 events += Version.find(:all, :include => :project,
380 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
383 :conditions => ["(#{@query.project_statement}) AND effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
381
384
382 @calendar.events = events
385 @calendar.events = events
383 end
386 end
384
387
385 render :layout => false if request.xhr?
388 render :layout => false if request.xhr?
386 end
389 end
387
390
388 def context_menu
391 def context_menu
389 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
392 @issues = Issue.find_all_by_id(params[:ids], :include => :project)
390 if (@issues.size == 1)
393 if (@issues.size == 1)
391 @issue = @issues.first
394 @issue = @issues.first
392 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
395 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
393 end
396 end
394 projects = @issues.collect(&:project).compact.uniq
397 projects = @issues.collect(&:project).compact.uniq
395 @project = projects.first if projects.size == 1
398 @project = projects.first if projects.size == 1
396
399
397 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
400 @can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
398 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
401 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
399 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
402 :update => (@project && (User.current.allowed_to?(:edit_issues, @project) || (User.current.allowed_to?(:change_status, @project) && @allowed_statuses && !@allowed_statuses.empty?))),
400 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
403 :move => (@project && User.current.allowed_to?(:move_issues, @project)),
401 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
404 :copy => (@issue && @project.trackers.include?(@issue.tracker) && User.current.allowed_to?(:add_issues, @project)),
402 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
405 :delete => (@project && User.current.allowed_to?(:delete_issues, @project))
403 }
406 }
404 if @project
407 if @project
405 @assignables = @project.assignable_users
408 @assignables = @project.assignable_users
406 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
409 @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
407 end
410 end
408
411
409 @priorities = Enumeration.get_values('IPRI').reverse
412 @priorities = Enumeration.get_values('IPRI').reverse
410 @statuses = IssueStatus.find(:all, :order => 'position')
413 @statuses = IssueStatus.find(:all, :order => 'position')
411 @back = request.env['HTTP_REFERER']
414 @back = request.env['HTTP_REFERER']
412
415
413 render :layout => false
416 render :layout => false
414 end
417 end
415
418
416 def update_form
419 def update_form
417 @issue = Issue.new(params[:issue])
420 @issue = Issue.new(params[:issue])
418 render :action => :new, :layout => false
421 render :action => :new, :layout => false
419 end
422 end
420
423
421 def preview
424 def preview
422 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
425 @issue = @project.issues.find_by_id(params[:id]) unless params[:id].blank?
423 @attachements = @issue.attachments if @issue
426 @attachements = @issue.attachments if @issue
424 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
427 @text = params[:notes] || (params[:issue] ? params[:issue][:description] : nil)
425 render :partial => 'common/preview'
428 render :partial => 'common/preview'
426 end
429 end
427
430
428 private
431 private
429 def find_issue
432 def find_issue
430 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
433 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
431 @project = @issue.project
434 @project = @issue.project
432 rescue ActiveRecord::RecordNotFound
435 rescue ActiveRecord::RecordNotFound
433 render_404
436 render_404
434 end
437 end
435
438
436 # Filter for bulk operations
439 # Filter for bulk operations
437 def find_issues
440 def find_issues
438 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
441 @issues = Issue.find_all_by_id(params[:id] || params[:ids])
439 raise ActiveRecord::RecordNotFound if @issues.empty?
442 raise ActiveRecord::RecordNotFound if @issues.empty?
440 projects = @issues.collect(&:project).compact.uniq
443 projects = @issues.collect(&:project).compact.uniq
441 if projects.size == 1
444 if projects.size == 1
442 @project = projects.first
445 @project = projects.first
443 else
446 else
444 # TODO: let users bulk edit/move/destroy issues from different projects
447 # TODO: let users bulk edit/move/destroy issues from different projects
445 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
448 render_error 'Can not bulk edit/move/destroy issues from different projects' and return false
446 end
449 end
447 rescue ActiveRecord::RecordNotFound
450 rescue ActiveRecord::RecordNotFound
448 render_404
451 render_404
449 end
452 end
450
453
451 def find_project
454 def find_project
452 @project = Project.find(params[:project_id])
455 @project = Project.find(params[:project_id])
453 rescue ActiveRecord::RecordNotFound
456 rescue ActiveRecord::RecordNotFound
454 render_404
457 render_404
455 end
458 end
456
459
457 def find_optional_project
460 def find_optional_project
458 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
461 @project = Project.find(params[:project_id]) unless params[:project_id].blank?
459 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
462 allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
460 allowed ? true : deny_access
463 allowed ? true : deny_access
461 rescue ActiveRecord::RecordNotFound
464 rescue ActiveRecord::RecordNotFound
462 render_404
465 render_404
463 end
466 end
464
467
465 # Retrieve query from session or build a new query
468 # Retrieve query from session or build a new query
466 def retrieve_query
469 def retrieve_query
467 if !params[:query_id].blank?
470 if !params[:query_id].blank?
468 cond = "project_id IS NULL"
471 cond = "project_id IS NULL"
469 cond << " OR project_id = #{@project.id}" if @project
472 cond << " OR project_id = #{@project.id}" if @project
470 @query = Query.find(params[:query_id], :conditions => cond)
473 @query = Query.find(params[:query_id], :conditions => cond)
471 @query.project = @project
474 @query.project = @project
472 session[:query] = {:id => @query.id, :project_id => @query.project_id}
475 session[:query] = {:id => @query.id, :project_id => @query.project_id}
473 else
476 else
474 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
477 if params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
475 # Give it a name, required to be valid
478 # Give it a name, required to be valid
476 @query = Query.new(:name => "_")
479 @query = Query.new(:name => "_")
477 @query.project = @project
480 @query.project = @project
478 if params[:fields] and params[:fields].is_a? Array
481 if params[:fields] and params[:fields].is_a? Array
479 params[:fields].each do |field|
482 params[:fields].each do |field|
480 @query.add_filter(field, params[:operators][field], params[:values][field])
483 @query.add_filter(field, params[:operators][field], params[:values][field])
481 end
484 end
482 else
485 else
483 @query.available_filters.keys.each do |field|
486 @query.available_filters.keys.each do |field|
484 @query.add_short_filter(field, params[field]) if params[field]
487 @query.add_short_filter(field, params[field]) if params[field]
485 end
488 end
486 end
489 end
487 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
490 session[:query] = {:project_id => @query.project_id, :filters => @query.filters}
488 else
491 else
489 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
492 @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
490 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
493 @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters])
491 @query.project = @project
494 @query.project = @project
492 end
495 end
493 end
496 end
494 end
497 end
495 end
498 end
@@ -1,325 +1,328
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'SVG/Graph/Bar'
18 require 'SVG/Graph/Bar'
19 require 'SVG/Graph/BarHorizontal'
19 require 'SVG/Graph/BarHorizontal'
20 require 'digest/sha1'
20 require 'digest/sha1'
21
21
22 class ChangesetNotFound < Exception; end
22 class ChangesetNotFound < Exception; end
23 class InvalidRevisionParam < Exception; end
23 class InvalidRevisionParam < Exception; end
24
24
25 class RepositoriesController < ApplicationController
25 class RepositoriesController < ApplicationController
26 menu_item :repository
26 menu_item :repository
27 before_filter :find_repository, :except => :edit
27 before_filter :find_repository, :except => :edit
28 before_filter :find_project, :only => :edit
28 before_filter :find_project, :only => :edit
29 before_filter :authorize
29 before_filter :authorize
30 accept_key_auth :revisions
30 accept_key_auth :revisions
31
31
32 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
32 rescue_from Redmine::Scm::Adapters::CommandFailed, :with => :show_error_command_failed
33
33
34 def edit
34 def edit
35 @repository = @project.repository
35 @repository = @project.repository
36 if !@repository
36 if !@repository
37 @repository = Repository.factory(params[:repository_scm])
37 @repository = Repository.factory(params[:repository_scm])
38 @repository.project = @project if @repository
38 @repository.project = @project if @repository
39 end
39 end
40 if request.post? && @repository
40 if request.post? && @repository
41 @repository.attributes = params[:repository]
41 @repository.attributes = params[:repository]
42 @repository.save
42 @repository.save
43 end
43 end
44 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
44 render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
45 end
45 end
46
46
47 def committers
47 def committers
48 @committers = @repository.committers
48 @committers = @repository.committers
49 @users = @project.users
49 @users = @project.users
50 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
50 additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
51 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
51 @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
52 @users.compact!
52 @users.compact!
53 @users.sort!
53 @users.sort!
54 if request.post? && params[:committers].is_a?(Hash)
54 if request.post? && params[:committers].is_a?(Hash)
55 # Build a hash with repository usernames as keys and corresponding user ids as values
55 # Build a hash with repository usernames as keys and corresponding user ids as values
56 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
56 @repository.committer_ids = params[:committers].values.inject({}) {|h, c| h[c.first] = c.last; h}
57 flash[:notice] = l(:notice_successful_update)
57 flash[:notice] = l(:notice_successful_update)
58 redirect_to :action => 'committers', :id => @project
58 redirect_to :action => 'committers', :id => @project
59 end
59 end
60 end
60 end
61
61
62 def destroy
62 def destroy
63 @repository.destroy
63 @repository.destroy
64 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
64 redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
65 end
65 end
66
66
67 def show
67 def show
68 # check if new revisions have been committed in the repository
68 # check if new revisions have been committed in the repository
69 @repository.fetch_changesets if Setting.autofetch_changesets?
69 @repository.fetch_changesets if Setting.autofetch_changesets?
70 # root entries
70 # root entries
71 @entries = @repository.entries('', @rev)
71 @entries = @repository.entries('', @rev)
72 # latest changesets
72 # latest changesets
73 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
73 @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC")
74 show_error_not_found unless @entries || @changesets.any?
74 show_error_not_found unless @entries || @changesets.any?
75 end
75 end
76
76
77 def browse
77 def browse
78 @entries = @repository.entries(@path, @rev)
78 @entries = @repository.entries(@path, @rev)
79 if request.xhr?
79 if request.xhr?
80 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
80 @entries ? render(:partial => 'dir_list_content') : render(:nothing => true)
81 else
81 else
82 show_error_not_found and return unless @entries
82 show_error_not_found and return unless @entries
83 @properties = @repository.properties(@path, @rev)
83 @properties = @repository.properties(@path, @rev)
84 render :action => 'browse'
84 render :action => 'browse'
85 end
85 end
86 end
86 end
87
87
88 def changes
88 def changes
89 @entry = @repository.entry(@path, @rev)
89 @entry = @repository.entry(@path, @rev)
90 show_error_not_found and return unless @entry
90 show_error_not_found and return unless @entry
91 @changesets = @repository.changesets_for_path(@path)
91 @changesets = @repository.changesets_for_path(@path)
92 @properties = @repository.properties(@path, @rev)
92 @properties = @repository.properties(@path, @rev)
93 end
93 end
94
94
95 def revisions
95 def revisions
96 @changeset_count = @repository.changesets.count
96 @changeset_count = @repository.changesets.count
97 @changeset_pages = Paginator.new self, @changeset_count,
97 @changeset_pages = Paginator.new self, @changeset_count,
98 per_page_option,
98 per_page_option,
99 params['page']
99 params['page']
100 @changesets = @repository.changesets.find(:all,
100 @changesets = @repository.changesets.find(:all,
101 :limit => @changeset_pages.items_per_page,
101 :limit => @changeset_pages.items_per_page,
102 :offset => @changeset_pages.current.offset,
102 :offset => @changeset_pages.current.offset,
103 :include => :user)
103 :include => :user)
104
104
105 respond_to do |format|
105 respond_to do |format|
106 format.html { render :layout => false if request.xhr? }
106 format.html { render :layout => false if request.xhr? }
107 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
107 format.atom { render_feed(@changesets, :title => "#{@project.name}: #{l(:label_revision_plural)}") }
108 end
108 end
109 end
109 end
110
110
111 def entry
111 def entry
112 @entry = @repository.entry(@path, @rev)
112 @entry = @repository.entry(@path, @rev)
113 show_error_not_found and return unless @entry
113 show_error_not_found and return unless @entry
114
114
115 # If the entry is a dir, show the browser
115 # If the entry is a dir, show the browser
116 browse and return if @entry.is_dir?
116 browse and return if @entry.is_dir?
117
117
118 @content = @repository.cat(@path, @rev)
118 @content = @repository.cat(@path, @rev)
119 show_error_not_found and return unless @content
119 show_error_not_found and return unless @content
120 if 'raw' == params[:format] || @content.is_binary_data?
120 if 'raw' == params[:format] || @content.is_binary_data?
121 # Force the download if it's a binary file
121 # Force the download if it's a binary file
122 send_data @content, :filename => @path.split('/').last
122 send_data @content, :filename => @path.split('/').last
123 else
123 else
124 # Prevent empty lines when displaying a file with Windows style eol
124 # Prevent empty lines when displaying a file with Windows style eol
125 @content.gsub!("\r\n", "\n")
125 @content.gsub!("\r\n", "\n")
126 end
126 end
127 end
127 end
128
128
129 def annotate
129 def annotate
130 @entry = @repository.entry(@path, @rev)
131 show_error_not_found and return unless @entry
132
130 @annotate = @repository.scm.annotate(@path, @rev)
133 @annotate = @repository.scm.annotate(@path, @rev)
131 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
134 render_error l(:error_scm_annotate) and return if @annotate.nil? || @annotate.empty?
132 end
135 end
133
136
134 def revision
137 def revision
135 @changeset = @repository.changesets.find_by_revision(@rev)
138 @changeset = @repository.changesets.find_by_revision(@rev)
136 raise ChangesetNotFound unless @changeset
139 raise ChangesetNotFound unless @changeset
137
140
138 respond_to do |format|
141 respond_to do |format|
139 format.html
142 format.html
140 format.js {render :layout => false}
143 format.js {render :layout => false}
141 end
144 end
142 rescue ChangesetNotFound
145 rescue ChangesetNotFound
143 show_error_not_found
146 show_error_not_found
144 end
147 end
145
148
146 def diff
149 def diff
147 if params[:format] == 'diff'
150 if params[:format] == 'diff'
148 @diff = @repository.diff(@path, @rev, @rev_to)
151 @diff = @repository.diff(@path, @rev, @rev_to)
149 show_error_not_found and return unless @diff
152 show_error_not_found and return unless @diff
150 filename = "changeset_r#{@rev}"
153 filename = "changeset_r#{@rev}"
151 filename << "_r#{@rev_to}" if @rev_to
154 filename << "_r#{@rev_to}" if @rev_to
152 send_data @diff.join, :filename => "#{filename}.diff",
155 send_data @diff.join, :filename => "#{filename}.diff",
153 :type => 'text/x-patch',
156 :type => 'text/x-patch',
154 :disposition => 'attachment'
157 :disposition => 'attachment'
155 else
158 else
156 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
159 @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
157 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
160 @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
158
161
159 # Save diff type as user preference
162 # Save diff type as user preference
160 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
163 if User.current.logged? && @diff_type != User.current.pref[:diff_type]
161 User.current.pref[:diff_type] = @diff_type
164 User.current.pref[:diff_type] = @diff_type
162 User.current.preference.save
165 User.current.preference.save
163 end
166 end
164
167
165 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
168 @cache_key = "repositories/diff/#{@repository.id}/" + Digest::MD5.hexdigest("#{@path}-#{@rev}-#{@rev_to}-#{@diff_type}")
166 unless read_fragment(@cache_key)
169 unless read_fragment(@cache_key)
167 @diff = @repository.diff(@path, @rev, @rev_to)
170 @diff = @repository.diff(@path, @rev, @rev_to)
168 show_error_not_found unless @diff
171 show_error_not_found unless @diff
169 end
172 end
170 end
173 end
171 end
174 end
172
175
173 def stats
176 def stats
174 end
177 end
175
178
176 def graph
179 def graph
177 data = nil
180 data = nil
178 case params[:graph]
181 case params[:graph]
179 when "commits_per_month"
182 when "commits_per_month"
180 data = graph_commits_per_month(@repository)
183 data = graph_commits_per_month(@repository)
181 when "commits_per_author"
184 when "commits_per_author"
182 data = graph_commits_per_author(@repository)
185 data = graph_commits_per_author(@repository)
183 end
186 end
184 if data
187 if data
185 headers["Content-Type"] = "image/svg+xml"
188 headers["Content-Type"] = "image/svg+xml"
186 send_data(data, :type => "image/svg+xml", :disposition => "inline")
189 send_data(data, :type => "image/svg+xml", :disposition => "inline")
187 else
190 else
188 render_404
191 render_404
189 end
192 end
190 end
193 end
191
194
192 private
195 private
193 def find_project
196 def find_project
194 @project = Project.find(params[:id])
197 @project = Project.find(params[:id])
195 rescue ActiveRecord::RecordNotFound
198 rescue ActiveRecord::RecordNotFound
196 render_404
199 render_404
197 end
200 end
198
201
199 REV_PARAM_RE = %r{^[a-f0-9]*$}
202 REV_PARAM_RE = %r{^[a-f0-9]*$}
200
203
201 def find_repository
204 def find_repository
202 @project = Project.find(params[:id])
205 @project = Project.find(params[:id])
203 @repository = @project.repository
206 @repository = @project.repository
204 render_404 and return false unless @repository
207 render_404 and return false unless @repository
205 @path = params[:path].join('/') unless params[:path].nil?
208 @path = params[:path].join('/') unless params[:path].nil?
206 @path ||= ''
209 @path ||= ''
207 @rev = params[:rev]
210 @rev = params[:rev]
208 @rev_to = params[:rev_to]
211 @rev_to = params[:rev_to]
209 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
212 raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE)
210 rescue ActiveRecord::RecordNotFound
213 rescue ActiveRecord::RecordNotFound
211 render_404
214 render_404
212 rescue InvalidRevisionParam
215 rescue InvalidRevisionParam
213 show_error_not_found
216 show_error_not_found
214 end
217 end
215
218
216 def show_error_not_found
219 def show_error_not_found
217 render_error l(:error_scm_not_found)
220 render_error l(:error_scm_not_found)
218 end
221 end
219
222
220 # Handler for Redmine::Scm::Adapters::CommandFailed exception
223 # Handler for Redmine::Scm::Adapters::CommandFailed exception
221 def show_error_command_failed(exception)
224 def show_error_command_failed(exception)
222 render_error l(:error_scm_command_failed, exception.message)
225 render_error l(:error_scm_command_failed, exception.message)
223 end
226 end
224
227
225 def graph_commits_per_month(repository)
228 def graph_commits_per_month(repository)
226 @date_to = Date.today
229 @date_to = Date.today
227 @date_from = @date_to << 11
230 @date_from = @date_to << 11
228 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
231 @date_from = Date.civil(@date_from.year, @date_from.month, 1)
229 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
232 commits_by_day = repository.changesets.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
230 commits_by_month = [0] * 12
233 commits_by_month = [0] * 12
231 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
234 commits_by_day.each {|c| commits_by_month[c.first.to_date.months_ago] += c.last }
232
235
233 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
236 changes_by_day = repository.changes.count(:all, :group => :commit_date, :conditions => ["commit_date BETWEEN ? AND ?", @date_from, @date_to])
234 changes_by_month = [0] * 12
237 changes_by_month = [0] * 12
235 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
238 changes_by_day.each {|c| changes_by_month[c.first.to_date.months_ago] += c.last }
236
239
237 fields = []
240 fields = []
238 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
241 month_names = l(:actionview_datehelper_select_month_names_abbr).split(',')
239 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
242 12.times {|m| fields << month_names[((Date.today.month - 1 - m) % 12)]}
240
243
241 graph = SVG::Graph::Bar.new(
244 graph = SVG::Graph::Bar.new(
242 :height => 300,
245 :height => 300,
243 :width => 800,
246 :width => 800,
244 :fields => fields.reverse,
247 :fields => fields.reverse,
245 :stack => :side,
248 :stack => :side,
246 :scale_integers => true,
249 :scale_integers => true,
247 :step_x_labels => 2,
250 :step_x_labels => 2,
248 :show_data_values => false,
251 :show_data_values => false,
249 :graph_title => l(:label_commits_per_month),
252 :graph_title => l(:label_commits_per_month),
250 :show_graph_title => true
253 :show_graph_title => true
251 )
254 )
252
255
253 graph.add_data(
256 graph.add_data(
254 :data => commits_by_month[0..11].reverse,
257 :data => commits_by_month[0..11].reverse,
255 :title => l(:label_revision_plural)
258 :title => l(:label_revision_plural)
256 )
259 )
257
260
258 graph.add_data(
261 graph.add_data(
259 :data => changes_by_month[0..11].reverse,
262 :data => changes_by_month[0..11].reverse,
260 :title => l(:label_change_plural)
263 :title => l(:label_change_plural)
261 )
264 )
262
265
263 graph.burn
266 graph.burn
264 end
267 end
265
268
266 def graph_commits_per_author(repository)
269 def graph_commits_per_author(repository)
267 commits_by_author = repository.changesets.count(:all, :group => :committer)
270 commits_by_author = repository.changesets.count(:all, :group => :committer)
268 commits_by_author.sort! {|x, y| x.last <=> y.last}
271 commits_by_author.sort! {|x, y| x.last <=> y.last}
269
272
270 changes_by_author = repository.changes.count(:all, :group => :committer)
273 changes_by_author = repository.changes.count(:all, :group => :committer)
271 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
274 h = changes_by_author.inject({}) {|o, i| o[i.first] = i.last; o}
272
275
273 fields = commits_by_author.collect {|r| r.first}
276 fields = commits_by_author.collect {|r| r.first}
274 commits_data = commits_by_author.collect {|r| r.last}
277 commits_data = commits_by_author.collect {|r| r.last}
275 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
278 changes_data = commits_by_author.collect {|r| h[r.first] || 0}
276
279
277 fields = fields + [""]*(10 - fields.length) if fields.length<10
280 fields = fields + [""]*(10 - fields.length) if fields.length<10
278 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
281 commits_data = commits_data + [0]*(10 - commits_data.length) if commits_data.length<10
279 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
282 changes_data = changes_data + [0]*(10 - changes_data.length) if changes_data.length<10
280
283
281 # Remove email adress in usernames
284 # Remove email adress in usernames
282 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
285 fields = fields.collect {|c| c.gsub(%r{<.+@.+>}, '') }
283
286
284 graph = SVG::Graph::BarHorizontal.new(
287 graph = SVG::Graph::BarHorizontal.new(
285 :height => 400,
288 :height => 400,
286 :width => 800,
289 :width => 800,
287 :fields => fields,
290 :fields => fields,
288 :stack => :side,
291 :stack => :side,
289 :scale_integers => true,
292 :scale_integers => true,
290 :show_data_values => false,
293 :show_data_values => false,
291 :rotate_y_labels => false,
294 :rotate_y_labels => false,
292 :graph_title => l(:label_commits_per_author),
295 :graph_title => l(:label_commits_per_author),
293 :show_graph_title => true
296 :show_graph_title => true
294 )
297 )
295
298
296 graph.add_data(
299 graph.add_data(
297 :data => commits_data,
300 :data => commits_data,
298 :title => l(:label_revision_plural)
301 :title => l(:label_revision_plural)
299 )
302 )
300
303
301 graph.add_data(
304 graph.add_data(
302 :data => changes_data,
305 :data => changes_data,
303 :title => l(:label_change_plural)
306 :title => l(:label_change_plural)
304 )
307 )
305
308
306 graph.burn
309 graph.burn
307 end
310 end
308
311
309 end
312 end
310
313
311 class Date
314 class Date
312 def months_ago(date = Date.today)
315 def months_ago(date = Date.today)
313 (date.year - self.year)*12 + (date.month - self.month)
316 (date.year - self.year)*12 + (date.month - self.month)
314 end
317 end
315
318
316 def weeks_ago(date = Date.today)
319 def weeks_ago(date = Date.today)
317 (date.year - self.year)*52 + (date.cweek - self.cweek)
320 (date.year - self.year)*52 + (date.cweek - self.cweek)
318 end
321 end
319 end
322 end
320
323
321 class String
324 class String
322 def with_leading_slash
325 def with_leading_slash
323 starts_with?('/') ? self : "/#{self}"
326 starts_with?('/') ? self : "/#{self}"
324 end
327 end
325 end
328 end
@@ -1,247 +1,248
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Mailer < ActionMailer::Base
18 class Mailer < ActionMailer::Base
19 helper :application
19 helper :application
20 helper :issues
20 helper :issues
21 helper :custom_fields
21 helper :custom_fields
22
22
23 include ActionController::UrlWriter
23 include ActionController::UrlWriter
24
24
25 def issue_add(issue)
25 def issue_add(issue)
26 redmine_headers 'Project' => issue.project.identifier,
26 redmine_headers 'Project' => issue.project.identifier,
27 'Issue-Id' => issue.id,
27 'Issue-Id' => issue.id,
28 'Issue-Author' => issue.author.login
28 'Issue-Author' => issue.author.login
29 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
29 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
30 recipients issue.recipients
30 recipients issue.recipients
31 cc(issue.watcher_recipients - @recipients)
31 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
32 subject "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] (#{issue.status.name}) #{issue.subject}"
32 body :issue => issue,
33 body :issue => issue,
33 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
34 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
34 end
35 end
35
36
36 def issue_edit(journal)
37 def issue_edit(journal)
37 issue = journal.journalized
38 issue = journal.journalized
38 redmine_headers 'Project' => issue.project.identifier,
39 redmine_headers 'Project' => issue.project.identifier,
39 'Issue-Id' => issue.id,
40 'Issue-Id' => issue.id,
40 'Issue-Author' => issue.author.login
41 'Issue-Author' => issue.author.login
41 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
42 redmine_headers 'Issue-Assignee' => issue.assigned_to.login if issue.assigned_to
42 recipients issue.recipients
43 recipients issue.recipients
43 # Watchers in cc
44 # Watchers in cc
44 cc(issue.watcher_recipients - @recipients)
45 cc(issue.watcher_recipients - @recipients)
45 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
46 s = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] "
46 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
47 s << "(#{issue.status.name}) " if journal.new_value_for('status_id')
47 s << issue.subject
48 s << issue.subject
48 subject s
49 subject s
49 body :issue => issue,
50 body :issue => issue,
50 :journal => journal,
51 :journal => journal,
51 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
52 :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue)
52 end
53 end
53
54
54 def reminder(user, issues, days)
55 def reminder(user, issues, days)
55 set_language_if_valid user.language
56 set_language_if_valid user.language
56 recipients user.mail
57 recipients user.mail
57 subject l(:mail_subject_reminder, issues.size)
58 subject l(:mail_subject_reminder, issues.size)
58 body :issues => issues,
59 body :issues => issues,
59 :days => days,
60 :days => days,
60 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
61 :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'due_date', :sort_order => 'asc')
61 end
62 end
62
63
63 def document_added(document)
64 def document_added(document)
64 redmine_headers 'Project' => document.project.identifier
65 redmine_headers 'Project' => document.project.identifier
65 recipients document.project.recipients
66 recipients document.project.recipients
66 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
67 subject "[#{document.project.name}] #{l(:label_document_new)}: #{document.title}"
67 body :document => document,
68 body :document => document,
68 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
69 :document_url => url_for(:controller => 'documents', :action => 'show', :id => document)
69 end
70 end
70
71
71 def attachments_added(attachments)
72 def attachments_added(attachments)
72 container = attachments.first.container
73 container = attachments.first.container
73 added_to = ''
74 added_to = ''
74 added_to_url = ''
75 added_to_url = ''
75 case container.class.name
76 case container.class.name
76 when 'Version'
77 when 'Version'
77 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
78 added_to_url = url_for(:controller => 'projects', :action => 'list_files', :id => container.project_id)
78 added_to = "#{l(:label_version)}: #{container.name}"
79 added_to = "#{l(:label_version)}: #{container.name}"
79 when 'Document'
80 when 'Document'
80 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
81 added_to_url = url_for(:controller => 'documents', :action => 'show', :id => container.id)
81 added_to = "#{l(:label_document)}: #{container.title}"
82 added_to = "#{l(:label_document)}: #{container.title}"
82 end
83 end
83 redmine_headers 'Project' => container.project.identifier
84 redmine_headers 'Project' => container.project.identifier
84 recipients container.project.recipients
85 recipients container.project.recipients
85 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
86 subject "[#{container.project.name}] #{l(:label_attachment_new)}"
86 body :attachments => attachments,
87 body :attachments => attachments,
87 :added_to => added_to,
88 :added_to => added_to,
88 :added_to_url => added_to_url
89 :added_to_url => added_to_url
89 end
90 end
90
91
91 def news_added(news)
92 def news_added(news)
92 redmine_headers 'Project' => news.project.identifier
93 redmine_headers 'Project' => news.project.identifier
93 recipients news.project.recipients
94 recipients news.project.recipients
94 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
95 subject "[#{news.project.name}] #{l(:label_news)}: #{news.title}"
95 body :news => news,
96 body :news => news,
96 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
97 :news_url => url_for(:controller => 'news', :action => 'show', :id => news)
97 end
98 end
98
99
99 def message_posted(message, recipients)
100 def message_posted(message, recipients)
100 redmine_headers 'Project' => message.project.identifier,
101 redmine_headers 'Project' => message.project.identifier,
101 'Topic-Id' => (message.parent_id || message.id)
102 'Topic-Id' => (message.parent_id || message.id)
102 recipients(recipients)
103 recipients(recipients)
103 subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
104 subject "[#{message.board.project.name} - #{message.board.name}] #{message.subject}"
104 body :message => message,
105 body :message => message,
105 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
106 :message_url => url_for(:controller => 'messages', :action => 'show', :board_id => message.board_id, :id => message.root)
106 end
107 end
107
108
108 def account_information(user, password)
109 def account_information(user, password)
109 set_language_if_valid user.language
110 set_language_if_valid user.language
110 recipients user.mail
111 recipients user.mail
111 subject l(:mail_subject_register, Setting.app_title)
112 subject l(:mail_subject_register, Setting.app_title)
112 body :user => user,
113 body :user => user,
113 :password => password,
114 :password => password,
114 :login_url => url_for(:controller => 'account', :action => 'login')
115 :login_url => url_for(:controller => 'account', :action => 'login')
115 end
116 end
116
117
117 def account_activation_request(user)
118 def account_activation_request(user)
118 # Send the email to all active administrators
119 # Send the email to all active administrators
119 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
120 recipients User.active.find(:all, :conditions => {:admin => true}).collect { |u| u.mail }.compact
120 subject l(:mail_subject_account_activation_request, Setting.app_title)
121 subject l(:mail_subject_account_activation_request, Setting.app_title)
121 body :user => user,
122 body :user => user,
122 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
123 :url => url_for(:controller => 'users', :action => 'index', :status => User::STATUS_REGISTERED, :sort_key => 'created_on', :sort_order => 'desc')
123 end
124 end
124
125
125 def lost_password(token)
126 def lost_password(token)
126 set_language_if_valid(token.user.language)
127 set_language_if_valid(token.user.language)
127 recipients token.user.mail
128 recipients token.user.mail
128 subject l(:mail_subject_lost_password, Setting.app_title)
129 subject l(:mail_subject_lost_password, Setting.app_title)
129 body :token => token,
130 body :token => token,
130 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
131 :url => url_for(:controller => 'account', :action => 'lost_password', :token => token.value)
131 end
132 end
132
133
133 def register(token)
134 def register(token)
134 set_language_if_valid(token.user.language)
135 set_language_if_valid(token.user.language)
135 recipients token.user.mail
136 recipients token.user.mail
136 subject l(:mail_subject_register, Setting.app_title)
137 subject l(:mail_subject_register, Setting.app_title)
137 body :token => token,
138 body :token => token,
138 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
139 :url => url_for(:controller => 'account', :action => 'activate', :token => token.value)
139 end
140 end
140
141
141 def test(user)
142 def test(user)
142 set_language_if_valid(user.language)
143 set_language_if_valid(user.language)
143 recipients user.mail
144 recipients user.mail
144 subject 'Redmine test'
145 subject 'Redmine test'
145 body :url => url_for(:controller => 'welcome')
146 body :url => url_for(:controller => 'welcome')
146 end
147 end
147
148
148 # Overrides default deliver! method to prevent from sending an email
149 # Overrides default deliver! method to prevent from sending an email
149 # with no recipient, cc or bcc
150 # with no recipient, cc or bcc
150 def deliver!(mail = @mail)
151 def deliver!(mail = @mail)
151 return false if (recipients.nil? || recipients.empty?) &&
152 return false if (recipients.nil? || recipients.empty?) &&
152 (cc.nil? || cc.empty?) &&
153 (cc.nil? || cc.empty?) &&
153 (bcc.nil? || bcc.empty?)
154 (bcc.nil? || bcc.empty?)
154 super
155 super
155 end
156 end
156
157
157 # Sends reminders to issue assignees
158 # Sends reminders to issue assignees
158 # Available options:
159 # Available options:
159 # * :days => how many days in the future to remind about (defaults to 7)
160 # * :days => how many days in the future to remind about (defaults to 7)
160 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
161 # * :tracker => id of tracker for filtering issues (defaults to all trackers)
161 # * :project => id or identifier of project to process (defaults to all projects)
162 # * :project => id or identifier of project to process (defaults to all projects)
162 def self.reminders(options={})
163 def self.reminders(options={})
163 days = options[:days] || 7
164 days = options[:days] || 7
164 project = options[:project] ? Project.find(options[:project]) : nil
165 project = options[:project] ? Project.find(options[:project]) : nil
165 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
166 tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil
166
167
167 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
168 s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ?", false, days.day.from_now.to_date]
168 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
169 s << "#{Issue.table_name}.assigned_to_id IS NOT NULL"
169 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
170 s << "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}"
170 s << "#{Issue.table_name}.project_id = #{project.id}" if project
171 s << "#{Issue.table_name}.project_id = #{project.id}" if project
171 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
172 s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker
172
173
173 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
174 issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker],
174 :conditions => s.conditions
175 :conditions => s.conditions
175 ).group_by(&:assigned_to)
176 ).group_by(&:assigned_to)
176 issues_by_assignee.each do |assignee, issues|
177 issues_by_assignee.each do |assignee, issues|
177 deliver_reminder(assignee, issues, days) unless assignee.nil?
178 deliver_reminder(assignee, issues, days) unless assignee.nil?
178 end
179 end
179 end
180 end
180
181
181 private
182 private
182 def initialize_defaults(method_name)
183 def initialize_defaults(method_name)
183 super
184 super
184 set_language_if_valid Setting.default_language
185 set_language_if_valid Setting.default_language
185 from Setting.mail_from
186 from Setting.mail_from
186
187
187 # URL options
188 # URL options
188 h = Setting.host_name
189 h = Setting.host_name
189 h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank?
190 h = h.to_s.gsub(%r{\/.*$}, '') unless ActionController::AbstractRequest.relative_url_root.blank?
190 default_url_options[:host] = h
191 default_url_options[:host] = h
191 default_url_options[:protocol] = Setting.protocol
192 default_url_options[:protocol] = Setting.protocol
192
193
193 # Common headers
194 # Common headers
194 headers 'X-Mailer' => 'Redmine',
195 headers 'X-Mailer' => 'Redmine',
195 'X-Redmine-Host' => Setting.host_name,
196 'X-Redmine-Host' => Setting.host_name,
196 'X-Redmine-Site' => Setting.app_title
197 'X-Redmine-Site' => Setting.app_title
197 end
198 end
198
199
199 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
200 # Appends a Redmine header field (name is prepended with 'X-Redmine-')
200 def redmine_headers(h)
201 def redmine_headers(h)
201 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
202 h.each { |k,v| headers["X-Redmine-#{k}"] = v }
202 end
203 end
203
204
204 # Overrides the create_mail method
205 # Overrides the create_mail method
205 def create_mail
206 def create_mail
206 # Removes the current user from the recipients and cc
207 # Removes the current user from the recipients and cc
207 # if he doesn't want to receive notifications about what he does
208 # if he doesn't want to receive notifications about what he does
208 if User.current.pref[:no_self_notified]
209 if User.current.pref[:no_self_notified]
209 recipients.delete(User.current.mail) if recipients
210 recipients.delete(User.current.mail) if recipients
210 cc.delete(User.current.mail) if cc
211 cc.delete(User.current.mail) if cc
211 end
212 end
212 # Blind carbon copy recipients
213 # Blind carbon copy recipients
213 if Setting.bcc_recipients?
214 if Setting.bcc_recipients?
214 bcc([recipients, cc].flatten.compact.uniq)
215 bcc([recipients, cc].flatten.compact.uniq)
215 recipients []
216 recipients []
216 cc []
217 cc []
217 end
218 end
218 super
219 super
219 end
220 end
220
221
221 # Renders a message with the corresponding layout
222 # Renders a message with the corresponding layout
222 def render_message(method_name, body)
223 def render_message(method_name, body)
223 layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
224 layout = method_name.match(%r{text\.html\.(rhtml|rxml)}) ? 'layout.text.html.rhtml' : 'layout.text.plain.rhtml'
224 body[:content_for_layout] = render(:file => method_name, :body => body)
225 body[:content_for_layout] = render(:file => method_name, :body => body)
225 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
226 ActionView::Base.new(template_root, body, self).render(:file => "mailer/#{layout}", :use_full_path => true)
226 end
227 end
227
228
228 # for the case of plain text only
229 # for the case of plain text only
229 def body(*params)
230 def body(*params)
230 value = super(*params)
231 value = super(*params)
231 if Setting.plain_text_mail?
232 if Setting.plain_text_mail?
232 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
233 templates = Dir.glob("#{template_path}/#{@template}.text.plain.{rhtml,erb}")
233 unless String === @body or templates.empty?
234 unless String === @body or templates.empty?
234 template = File.basename(templates.first)
235 template = File.basename(templates.first)
235 @body[:content_for_layout] = render(:file => template, :body => @body)
236 @body[:content_for_layout] = render(:file => template, :body => @body)
236 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
237 @body = ActionView::Base.new(template_root, @body, self).render(:file => "mailer/layout.text.plain.rhtml", :use_full_path => true)
237 return @body
238 return @body
238 end
239 end
239 end
240 end
240 return value
241 return value
241 end
242 end
242
243
243 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
244 # Makes partial rendering work with Rails 1.2 (retro-compatibility)
244 def self.controller_path
245 def self.controller_path
245 ''
246 ''
246 end unless respond_to?('controller_path')
247 end unless respond_to?('controller_path')
247 end
248 end
@@ -1,53 +1,61
1 <% if @issue.new_record? %>
1 <% if @issue.new_record? %>
2 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
2 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
3 <%= observe_field :issue_tracker_id, :url => { :action => :new },
3 <%= observe_field :issue_tracker_id, :url => { :action => :new },
4 :update => :content,
4 :update => :content,
5 :with => "Form.serialize('issue-form')" %>
5 :with => "Form.serialize('issue-form')" %>
6 <hr />
6 <hr />
7 <% end %>
7 <% end %>
8
8
9 <div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>>
9 <div id="issue_descr_fields" <%= 'style="display:none"' unless @issue.new_record? || @issue.errors.any? %>>
10 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
10 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
11 <p><%= f.text_area :description, :required => true,
11 <p><%= f.text_area :description, :required => true,
12 :cols => 60,
12 :cols => 60,
13 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
13 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
14 :accesskey => accesskey(:edit),
14 :accesskey => accesskey(:edit),
15 :class => 'wiki-edit' %></p>
15 :class => 'wiki-edit' %></p>
16 </div>
16 </div>
17
17
18 <div class="splitcontentleft">
18 <div class="splitcontentleft">
19 <% if @issue.new_record? || @allowed_statuses.any? %>
19 <% if @issue.new_record? || @allowed_statuses.any? %>
20 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
20 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
21 <% else %>
21 <% else %>
22 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
22 <p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
23 <% end %>
23 <% end %>
24
24
25 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
25 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
26 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
26 <p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
27 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
27 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
28 <%= prompt_to_remote(l(:label_issue_category_new),
28 <%= prompt_to_remote(l(:label_issue_category_new),
29 l(:label_issue_category_new), 'category[name]',
29 l(:label_issue_category_new), 'category[name]',
30 {:controller => 'projects', :action => 'add_issue_category', :id => @project},
30 {:controller => 'projects', :action => 'add_issue_category', :id => @project},
31 :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
31 :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
32 <%= content_tag('p', f.select(:fixed_version_id,
32 <%= content_tag('p', f.select(:fixed_version_id,
33 (@project.versions.sort.collect {|v| [v.name, v.id]}),
33 (@project.versions.sort.collect {|v| [v.name, v.id]}),
34 { :include_blank => true })) unless @project.versions.empty? %>
34 { :include_blank => true })) unless @project.versions.empty? %>
35 </div>
35 </div>
36
36
37 <div class="splitcontentright">
37 <div class="splitcontentright">
38 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
38 <p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
39 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
39 <p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
40 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
40 <p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
41 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
41 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
42 </div>
42 </div>
43
43
44 <div style="clear:both;"> </div>
44 <div style="clear:both;"> </div>
45 <%= render :partial => 'form_custom_fields' %>
45 <%= render :partial => 'form_custom_fields' %>
46
46
47 <% if @issue.new_record? %>
47 <% if @issue.new_record? %>
48 <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
48 <p><label><%=l(:label_attachment_plural)%></label><%= render :partial => 'attachments/form' %></p>
49 <% end %>
49 <% end %>
50
50
51 <% if @issue.new_record? && User.current.allowed_to?(:add_issue_watchers, @project) -%>
52 <p><label><%= l(:label_issue_watchers) %></label>
53 <% @issue.project.users.sort.each do |user| -%>
54 <label class="floating"><%= check_box_tag 'issue[watcher_user_ids][]', user.id, @issue.watcher_user_ids.include?(user.id) %> <%=h user %></label>
55 <% end -%>
56 </p>
57 <% end %>
58
51 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
59 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
52
60
53 <%= wikitoolbar_for 'issue_description' %>
61 <%= wikitoolbar_for 'issue_description' %>
@@ -1,28 +1,30
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
2
2
3 <p><%= render :partial => 'link_to_functions' %></p>
4
3 <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
5 <% colors = Hash.new {|k,v| k[v] = (k.size % 12) } %>
4
6
5 <div class="autoscroll">
7 <div class="autoscroll">
6 <table class="filecontent annotate CodeRay">
8 <table class="filecontent annotate CodeRay">
7 <tbody>
9 <tbody>
8 <% line_num = 1 %>
10 <% line_num = 1 %>
9 <% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %>
11 <% syntax_highlight(@path, to_utf8(@annotate.content)).each_line do |line| %>
10 <% revision = @annotate.revisions[line_num-1] %>
12 <% revision = @annotate.revisions[line_num-1] %>
11 <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
13 <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
12 <th class="line-num"><%= line_num %></th>
14 <th class="line-num"><%= line_num %></th>
13 <td class="revision">
15 <td class="revision">
14 <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
16 <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
15 <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
17 <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
16 <td class="line-code"><pre><%= line %></pre></td>
18 <td class="line-code"><pre><%= line %></pre></td>
17 </tr>
19 </tr>
18 <% line_num += 1 %>
20 <% line_num += 1 %>
19 <% end %>
21 <% end %>
20 </tbody>
22 </tbody>
21 </table>
23 </table>
22 </div>
24 </div>
23
25
24 <% html_title(l(:button_annotate)) -%>
26 <% html_title(l(:button_annotate)) -%>
25
27
26 <% content_for :header_tags do %>
28 <% content_for :header_tags do %>
27 <%= stylesheet_link_tag 'scm' %>
29 <%= stylesheet_link_tag 'scm' %>
28 <% end %>
30 <% end %>
@@ -1,19 +1,10
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
2
2
3 <p>
3 <p><%= render :partial => 'link_to_functions' %></p>
4 <% if @repository.supports_cat? %>
5 <%= link_to l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
6 <% end %>
7 <% if @repository.supports_annotate? %>
8 <%= link_to l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
9 <% end %>
10 <%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
11 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
12 </p>
13
4
14 <%= render_properties(@properties) %>
5 <%= render_properties(@properties) %>
15
6
16 <%= render(:partial => 'revisions',
7 <%= render(:partial => 'revisions',
17 :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
8 :locals => {:project => @project, :path => @path, :revisions => @changesets, :entry => @entry }) unless @changesets.empty? %>
18
9
19 <% html_title(l(:label_change_plural)) -%>
10 <% html_title(l(:label_change_plural)) -%>
@@ -1,7 +1,9
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
1 <h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
2
2
3 <p><%= render :partial => 'link_to_functions' %></p>
4
3 <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
5 <%= render :partial => 'common/file', :locals => {:filename => @path, :content => @content} %>
4
6
5 <% content_for :header_tags do %>
7 <% content_for :header_tags do %>
6 <%= stylesheet_link_tag "scm" %>
8 <%= stylesheet_link_tag "scm" %>
7 <% end %>
9 <% end %>
@@ -1,832 +1,839
1 == Redmine changelog
1 == Redmine changelog
2
2
3 Redmine - project management software
3 Redmine - project management software
4 Copyright (C) 2006-2008 Jean-Philippe Lang
4 Copyright (C) 2006-2008 Jean-Philippe Lang
5 http://www.redmine.org/
5 http://www.redmine.org/
6
6
7
7
8 == v0.8.1
9
10 * Select watchers on new issue form
11 * Show view/annotate/download links on entry and annotate views
12 * Fixed: Deleted files are shown when using Darcs
13
14
8 == 2008-12-30 v0.8.0
15 == 2008-12-30 v0.8.0
9
16
10 * Setting added in order to limit the number of diff lines that should be displayed
17 * Setting added in order to limit the number of diff lines that should be displayed
11 * Makes logged-in username in topbar linking to
18 * Makes logged-in username in topbar linking to
12 * Mail handler: strip tags when receiving a html-only email
19 * Mail handler: strip tags when receiving a html-only email
13 * Mail handler: add watchers before sending notification
20 * Mail handler: add watchers before sending notification
14 * Adds a css class (overdue) to overdue issues on issue lists and detail views
21 * Adds a css class (overdue) to overdue issues on issue lists and detail views
15 * Fixed: project activity truncated after viewing user's activity
22 * Fixed: project activity truncated after viewing user's activity
16 * Fixed: email address entered for password recovery shouldn't be case-sensitive
23 * Fixed: email address entered for password recovery shouldn't be case-sensitive
17 * Fixed: default flag removed when editing a default enumeration
24 * Fixed: default flag removed when editing a default enumeration
18 * Fixed: default category ignored when adding a document
25 * Fixed: default category ignored when adding a document
19 * Fixed: error on repository user mapping when a repository username is blank
26 * Fixed: error on repository user mapping when a repository username is blank
20 * Fixed: Firefox cuts off large diffs
27 * Fixed: Firefox cuts off large diffs
21 * Fixed: CVS browser should not show dead revisions (deleted files)
28 * Fixed: CVS browser should not show dead revisions (deleted files)
22 * Fixed: escape double-quotes in image titles
29 * Fixed: escape double-quotes in image titles
23 * Fixed: escape textarea content when editing a issue note
30 * Fixed: escape textarea content when editing a issue note
24 * Fixed: JS error on context menu with IE
31 * Fixed: JS error on context menu with IE
25 * Fixed: bold syntax around single character in series doesn't work
32 * Fixed: bold syntax around single character in series doesn't work
26 * Fixed several XSS vulnerabilities
33 * Fixed several XSS vulnerabilities
27 * Fixed a SQL injection vulnerability
34 * Fixed a SQL injection vulnerability
28
35
29
36
30 == 2008-12-07 v0.8.0-rc1
37 == 2008-12-07 v0.8.0-rc1
31
38
32 * Wiki page protection
39 * Wiki page protection
33 * Wiki page hierarchy. Parent page can be assigned on the Rename screen
40 * Wiki page hierarchy. Parent page can be assigned on the Rename screen
34 * Adds support for issue creation via email
41 * Adds support for issue creation via email
35 * Adds support for free ticket filtering and custom queries on Gantt chart and calendar
42 * Adds support for free ticket filtering and custom queries on Gantt chart and calendar
36 * Cross-project search
43 * Cross-project search
37 * Ability to search a project and its subprojects
44 * Ability to search a project and its subprojects
38 * Ability to search the projects the user belongs to
45 * Ability to search the projects the user belongs to
39 * Adds custom fields on time entries
46 * Adds custom fields on time entries
40 * Adds boolean and list custom fields for time entries as criteria on time report
47 * Adds boolean and list custom fields for time entries as criteria on time report
41 * Cross-project time reports
48 * Cross-project time reports
42 * Display latest user's activity on account/show view
49 * Display latest user's activity on account/show view
43 * Show last connexion time on user's page
50 * Show last connexion time on user's page
44 * Obfuscates email address on user's account page using javascript
51 * Obfuscates email address on user's account page using javascript
45 * wiki TOC rendered as an unordered list
52 * wiki TOC rendered as an unordered list
46 * Adds the ability to search for a user on the administration users list
53 * Adds the ability to search for a user on the administration users list
47 * Adds the ability to search for a project name or identifier on the administration projects list
54 * Adds the ability to search for a project name or identifier on the administration projects list
48 * Redirect user to the previous page after logging in
55 * Redirect user to the previous page after logging in
49 * Adds a permission 'view wiki edits' so that wiki history can be hidden to certain users
56 * Adds a permission 'view wiki edits' so that wiki history can be hidden to certain users
50 * Adds permissions for viewing the watcher list and adding new watchers on the issue detail view
57 * Adds permissions for viewing the watcher list and adding new watchers on the issue detail view
51 * Adds permissions to let users edit and/or delete their messages
58 * Adds permissions to let users edit and/or delete their messages
52 * Link to activity view when displaying dates
59 * Link to activity view when displaying dates
53 * Hide Redmine version in atom feeds and pdf properties
60 * Hide Redmine version in atom feeds and pdf properties
54 * Maps repository users to Redmine users. Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user.
61 * Maps repository users to Redmine users. Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user.
55 * Sort users by their display names so that user dropdown lists are sorted alphabetically
62 * Sort users by their display names so that user dropdown lists are sorted alphabetically
56 * Adds estimated hours to issue filters
63 * Adds estimated hours to issue filters
57 * Switch order of current and previous revisions in side-by-side diff
64 * Switch order of current and previous revisions in side-by-side diff
58 * Render the commit changes list as a tree
65 * Render the commit changes list as a tree
59 * Adds watch/unwatch functionality at forum topic level
66 * Adds watch/unwatch functionality at forum topic level
60 * When moving an issue to another project, reassign it to the category with same name if any
67 * When moving an issue to another project, reassign it to the category with same name if any
61 * Adds child_pages macro for wiki pages
68 * Adds child_pages macro for wiki pages
62 * Use GET instead of POST on roadmap (#718), gantt and calendar forms
69 * Use GET instead of POST on roadmap (#718), gantt and calendar forms
63 * Search engine: display total results count and count by result type
70 * Search engine: display total results count and count by result type
64 * Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file)
71 * Email delivery configuration moved to an unversioned YAML file (config/email.yml, see the sample file)
65 * Adds icons on search results
72 * Adds icons on search results
66 * Adds 'Edit' link on account/show for admin users
73 * Adds 'Edit' link on account/show for admin users
67 * Adds Lock/Unlock/Activate link on user edit screen
74 * Adds Lock/Unlock/Activate link on user edit screen
68 * Adds user count in status drop down on admin user list
75 * Adds user count in status drop down on admin user list
69 * Adds multi-levels blockquotes support by using > at the beginning of lines
76 * Adds multi-levels blockquotes support by using > at the beginning of lines
70 * Adds a Reply link to each issue note
77 * Adds a Reply link to each issue note
71 * Adds plain text only option for mail notifications
78 * Adds plain text only option for mail notifications
72 * Gravatar support for issue detail, user grid, and activity stream (disabled by default)
79 * Gravatar support for issue detail, user grid, and activity stream (disabled by default)
73 * Adds 'Delete wiki pages attachments' permission
80 * Adds 'Delete wiki pages attachments' permission
74 * Show the most recent file when displaying an inline image
81 * Show the most recent file when displaying an inline image
75 * Makes permission screens localized
82 * Makes permission screens localized
76 * AuthSource list: display associated users count and disable 'Delete' buton if any
83 * AuthSource list: display associated users count and disable 'Delete' buton if any
77 * Make the 'duplicates of' relation asymmetric
84 * Make the 'duplicates of' relation asymmetric
78 * Adds username to the password reminder email
85 * Adds username to the password reminder email
79 * Adds links to forum messages using message#id syntax
86 * Adds links to forum messages using message#id syntax
80 * Allow same name for custom fields on different object types
87 * Allow same name for custom fields on different object types
81 * One-click bulk edition using the issue list context menu within the same project
88 * One-click bulk edition using the issue list context menu within the same project
82 * Adds support for commit logs reencoding to UTF-8 before insertion in the database. Source encoding of commit logs can be selected in Application settings -> Repositories.
89 * Adds support for commit logs reencoding to UTF-8 before insertion in the database. Source encoding of commit logs can be selected in Application settings -> Repositories.
83 * Adds checkboxes toggle links on permissions report
90 * Adds checkboxes toggle links on permissions report
84 * Adds Trac-Like anchors on wiki headings
91 * Adds Trac-Like anchors on wiki headings
85 * Adds support for wiki links with anchor
92 * Adds support for wiki links with anchor
86 * Adds category to the issue context menu
93 * Adds category to the issue context menu
87 * Adds a workflow overview screen
94 * Adds a workflow overview screen
88 * Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename
95 * Appends the filename to the attachment url so that clients that ignore content-disposition http header get the real filename
89 * Dots allowed in custom field name
96 * Dots allowed in custom field name
90 * Adds posts quoting functionality
97 * Adds posts quoting functionality
91 * Adds an option to generate sequential project identifiers
98 * Adds an option to generate sequential project identifiers
92 * Adds mailto link on the user administration list
99 * Adds mailto link on the user administration list
93 * Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value
100 * Ability to remove enumerations (activities, priorities, document categories) that are in use. Associated objects can be reassigned to another value
94 * Gantt chart: display issues that don't have a due date if they are assigned to a version with a date
101 * Gantt chart: display issues that don't have a due date if they are assigned to a version with a date
95 * Change projects homepage limit to 255 chars
102 * Change projects homepage limit to 255 chars
96 * Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes
103 * Improved on-the-fly account creation. If some attributes are missing (eg. not present in the LDAP) or are invalid, the registration form is displayed so that the user is able to fill or fix these attributes
97 * Adds "please select" to activity select box if no activity is set as default
104 * Adds "please select" to activity select box if no activity is set as default
98 * Do not silently ignore timelog validation failure on issue edit
105 * Do not silently ignore timelog validation failure on issue edit
99 * Adds a rake task to send reminder emails
106 * Adds a rake task to send reminder emails
100 * Allow empty cells in wiki tables
107 * Allow empty cells in wiki tables
101 * Makes wiki text formatter pluggable
108 * Makes wiki text formatter pluggable
102 * Adds back textile acronyms support
109 * Adds back textile acronyms support
103 * Remove pre tag attributes
110 * Remove pre tag attributes
104 * Plugin hooks
111 * Plugin hooks
105 * Pluggable admin menu
112 * Pluggable admin menu
106 * Plugins can provide activity content
113 * Plugins can provide activity content
107 * Moves plugin list to its own administration menu item
114 * Moves plugin list to its own administration menu item
108 * Adds url and author_url plugin attributes
115 * Adds url and author_url plugin attributes
109 * Adds Plugin#requires_redmine method so that plugin compatibility can be checked against current Redmine version
116 * Adds Plugin#requires_redmine method so that plugin compatibility can be checked against current Redmine version
110 * Adds atom feed on time entries details
117 * Adds atom feed on time entries details
111 * Adds project name to issues feed title
118 * Adds project name to issues feed title
112 * Adds a css class on menu items in order to apply item specific styles (eg. icons)
119 * Adds a css class on menu items in order to apply item specific styles (eg. icons)
113 * Adds a Redmine plugin generators
120 * Adds a Redmine plugin generators
114 * Adds timelog link to the issue context menu
121 * Adds timelog link to the issue context menu
115 * Adds links to the user page on various views
122 * Adds links to the user page on various views
116 * Turkish translation by Ismail Sezen
123 * Turkish translation by Ismail Sezen
117 * Catalan translation
124 * Catalan translation
118 * Vietnamese translation
125 * Vietnamese translation
119 * Slovak translation
126 * Slovak translation
120 * Better naming of activity feed if only one kind of event is displayed
127 * Better naming of activity feed if only one kind of event is displayed
121 * Enable syntax highlight on issues, messages and news
128 * Enable syntax highlight on issues, messages and news
122 * Add target version to the issue list context menu
129 * Add target version to the issue list context menu
123 * Hide 'Target version' filter if no version is defined
130 * Hide 'Target version' filter if no version is defined
124 * Add filters on cross-project issue list for custom fields marked as 'For all projects'
131 * Add filters on cross-project issue list for custom fields marked as 'For all projects'
125 * Turn ftp urls into links
132 * Turn ftp urls into links
126 * Hiding the View Differences button when a wiki page's history only has one version
133 * Hiding the View Differences button when a wiki page's history only has one version
127 * Messages on a Board can now be sorted by the number of replies
134 * Messages on a Board can now be sorted by the number of replies
128 * Adds a class ('me') to events of the activity view created by current user
135 * Adds a class ('me') to events of the activity view created by current user
129 * Strip pre/code tags content from activity view events
136 * Strip pre/code tags content from activity view events
130 * Display issue notes in the activity view
137 * Display issue notes in the activity view
131 * Adds links to changesets atom feed on repository browser
138 * Adds links to changesets atom feed on repository browser
132 * Track project and tracker changes in issue history
139 * Track project and tracker changes in issue history
133 * Adds anchor to atom feed messages links
140 * Adds anchor to atom feed messages links
134 * Adds a key in lang files to set the decimal separator (point or comma) in csv exports
141 * Adds a key in lang files to set the decimal separator (point or comma) in csv exports
135 * Makes importer work with Trac 0.8.x
142 * Makes importer work with Trac 0.8.x
136 * Upgraded to Prototype 1.6.0.1
143 * Upgraded to Prototype 1.6.0.1
137 * File viewer for attached text files
144 * File viewer for attached text files
138 * Menu mapper: add support for :before, :after and :last options to #push method and add #delete method
145 * Menu mapper: add support for :before, :after and :last options to #push method and add #delete method
139 * Removed inconsistent revision numbers on diff view
146 * Removed inconsistent revision numbers on diff view
140 * CVS: add support for modules names with spaces
147 * CVS: add support for modules names with spaces
141 * Log the user in after registration if account activation is not needed
148 * Log the user in after registration if account activation is not needed
142 * Mercurial adapter improvements
149 * Mercurial adapter improvements
143 * Trac importer: read session_attribute table to find user's email and real name
150 * Trac importer: read session_attribute table to find user's email and real name
144 * Ability to disable unused SCM adapters in application settings
151 * Ability to disable unused SCM adapters in application settings
145 * Adds Filesystem adapter
152 * Adds Filesystem adapter
146 * Clear changesets and changes with raw sql when deleting a repository for performance
153 * Clear changesets and changes with raw sql when deleting a repository for performance
147 * Redmine.pm now uses the 'commit access' permission defined in Redmine
154 * Redmine.pm now uses the 'commit access' permission defined in Redmine
148 * Reposman can create any type of scm (--scm option)
155 * Reposman can create any type of scm (--scm option)
149 * Reposman creates a repository if the 'repository' module is enabled at project level only
156 * Reposman creates a repository if the 'repository' module is enabled at project level only
150 * Display svn properties in the browser, svn >= 1.5.0 only
157 * Display svn properties in the browser, svn >= 1.5.0 only
151 * Reduces memory usage when importing large git repositories
158 * Reduces memory usage when importing large git repositories
152 * Wider SVG graphs in repository stats
159 * Wider SVG graphs in repository stats
153 * SubversionAdapter#entries performance improvement
160 * SubversionAdapter#entries performance improvement
154 * SCM browser: ability to download raw unified diffs
161 * SCM browser: ability to download raw unified diffs
155 * More detailed error message in log when scm command fails
162 * More detailed error message in log when scm command fails
156 * Adds support for file viewing with Darcs 2.0+
163 * Adds support for file viewing with Darcs 2.0+
157 * Check that git changeset is not in the database before creating it
164 * Check that git changeset is not in the database before creating it
158 * Unified diff viewer for attached files with .patch or .diff extension
165 * Unified diff viewer for attached files with .patch or .diff extension
159 * File size display with Bazaar repositories
166 * File size display with Bazaar repositories
160 * Git adapter: use commit time instead of author time
167 * Git adapter: use commit time instead of author time
161 * Prettier url for changesets
168 * Prettier url for changesets
162 * Makes changes link to entries on the revision view
169 * Makes changes link to entries on the revision view
163 * Adds a field on the repository view to browse at specific revision
170 * Adds a field on the repository view to browse at specific revision
164 * Adds new projects atom feed
171 * Adds new projects atom feed
165 * Added rake tasks to generate rcov code coverage reports
172 * Added rake tasks to generate rcov code coverage reports
166 * Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki
173 * Add Redcloth's :block_markdown_rule to allow horizontal rules in wiki
167 * Show the project hierarchy in the drop down list for new membership on user administration screen
174 * Show the project hierarchy in the drop down list for new membership on user administration screen
168 * Split user edit screen into tabs
175 * Split user edit screen into tabs
169 * Renames bundled RedCloth to RedCloth3 to avoid RedCloth 4 to be loaded instead
176 * Renames bundled RedCloth to RedCloth3 to avoid RedCloth 4 to be loaded instead
170 * Fixed: Roadmap crashes when a version has a due date > 2037
177 * Fixed: Roadmap crashes when a version has a due date > 2037
171 * Fixed: invalid effective date (eg. 99999-01-01) causes an error on version edition screen
178 * Fixed: invalid effective date (eg. 99999-01-01) causes an error on version edition screen
172 * Fixed: login filter providing incorrect back_url for Redmine installed in sub-directory
179 * Fixed: login filter providing incorrect back_url for Redmine installed in sub-directory
173 * Fixed: logtime entry duplicated when edited from parent project
180 * Fixed: logtime entry duplicated when edited from parent project
174 * Fixed: wrong digest for text files under Windows
181 * Fixed: wrong digest for text files under Windows
175 * Fixed: associated revisions are displayed in wrong order on issue view
182 * Fixed: associated revisions are displayed in wrong order on issue view
176 * Fixed: Git Adapter date parsing ignores timezone
183 * Fixed: Git Adapter date parsing ignores timezone
177 * Fixed: Printing long roadmap doesn't split across pages
184 * Fixed: Printing long roadmap doesn't split across pages
178 * Fixes custom fields display order at several places
185 * Fixes custom fields display order at several places
179 * Fixed: urls containing @ are parsed as email adress by the wiki formatter
186 * Fixed: urls containing @ are parsed as email adress by the wiki formatter
180 * Fixed date filters accuracy with SQLite
187 * Fixed date filters accuracy with SQLite
181 * Fixed: tokens not escaped in highlight_tokens regexp
188 * Fixed: tokens not escaped in highlight_tokens regexp
182 * Fixed Bazaar shared repository browsing
189 * Fixed Bazaar shared repository browsing
183 * Fixes platform determination under JRuby
190 * Fixes platform determination under JRuby
184 * Fixed: Estimated time in issue's journal should be rounded to two decimals
191 * Fixed: Estimated time in issue's journal should be rounded to two decimals
185 * Fixed: 'search titles only' box ignored after one search is done on titles only
192 * Fixed: 'search titles only' box ignored after one search is done on titles only
186 * Fixed: non-ASCII subversion path can't be displayed
193 * Fixed: non-ASCII subversion path can't be displayed
187 * Fixed: Inline images don't work if file name has upper case letters or if image is in BMP format
194 * Fixed: Inline images don't work if file name has upper case letters or if image is in BMP format
188 * Fixed: document listing shows on "my page" when viewing documents is disabled for the role
195 * Fixed: document listing shows on "my page" when viewing documents is disabled for the role
189 * Fixed: Latest news appear on the homepage for projects with the News module disabled
196 * Fixed: Latest news appear on the homepage for projects with the News module disabled
190 * Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled
197 * Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled
191 * Fixed: the default status is lost when reordering issue statuses
198 * Fixed: the default status is lost when reordering issue statuses
192 * Fixes error with Postgresql and non-UTF8 commit logs
199 * Fixes error with Postgresql and non-UTF8 commit logs
193 * Fixed: textile footnotes no longer work
200 * Fixed: textile footnotes no longer work
194 * Fixed: http links containing parentheses fail to reder correctly
201 * Fixed: http links containing parentheses fail to reder correctly
195 * Fixed: GitAdapter#get_rev should use current branch instead of hardwiring master
202 * Fixed: GitAdapter#get_rev should use current branch instead of hardwiring master
196
203
197
204
198 == 2008-07-06 v0.7.3
205 == 2008-07-06 v0.7.3
199
206
200 * Allow dot in firstnames and lastnames
207 * Allow dot in firstnames and lastnames
201 * Add project name to cross-project Atom feeds
208 * Add project name to cross-project Atom feeds
202 * Encoding set to utf8 in example database.yml
209 * Encoding set to utf8 in example database.yml
203 * HTML titles on forums related views
210 * HTML titles on forums related views
204 * Fixed: various XSS vulnerabilities
211 * Fixed: various XSS vulnerabilities
205 * Fixed: Entourage (and some old client) fails to correctly render notification styles
212 * Fixed: Entourage (and some old client) fails to correctly render notification styles
206 * Fixed: Fixed: timelog redirects inappropriately when :back_url is blank
213 * Fixed: Fixed: timelog redirects inappropriately when :back_url is blank
207 * Fixed: wrong relative paths to images in wiki_syntax.html
214 * Fixed: wrong relative paths to images in wiki_syntax.html
208
215
209
216
210 == 2008-06-15 v0.7.2
217 == 2008-06-15 v0.7.2
211
218
212 * "New Project" link on Projects page
219 * "New Project" link on Projects page
213 * Links to repository directories on the repo browser
220 * Links to repository directories on the repo browser
214 * Move status to front in Activity View
221 * Move status to front in Activity View
215 * Remove edit step from Status context menu
222 * Remove edit step from Status context menu
216 * Fixed: No way to do textile horizontal rule
223 * Fixed: No way to do textile horizontal rule
217 * Fixed: Repository: View differences doesn't work
224 * Fixed: Repository: View differences doesn't work
218 * Fixed: attachement's name maybe invalid.
225 * Fixed: attachement's name maybe invalid.
219 * Fixed: Error when creating a new issue
226 * Fixed: Error when creating a new issue
220 * Fixed: NoMethodError on @available_filters.has_key?
227 * Fixed: NoMethodError on @available_filters.has_key?
221 * Fixed: Check All / Uncheck All in Email Settings
228 * Fixed: Check All / Uncheck All in Email Settings
222 * Fixed: "View differences" of one file at /repositories/revision/ fails
229 * Fixed: "View differences" of one file at /repositories/revision/ fails
223 * Fixed: Column width in "my page"
230 * Fixed: Column width in "my page"
224 * Fixed: private subprojects are listed on Issues view
231 * Fixed: private subprojects are listed on Issues view
225 * Fixed: Textile: bold, italics, underline, etc... not working after parentheses
232 * Fixed: Textile: bold, italics, underline, etc... not working after parentheses
226 * Fixed: Update issue form: comment field from log time end out of screen
233 * Fixed: Update issue form: comment field from log time end out of screen
227 * Fixed: Editing role: "issue can be assigned to this role" out of box
234 * Fixed: Editing role: "issue can be assigned to this role" out of box
228 * Fixed: Unable use angular braces after include word
235 * Fixed: Unable use angular braces after include word
229 * Fixed: Using '*' as keyword for repository referencing keywords doesn't work
236 * Fixed: Using '*' as keyword for repository referencing keywords doesn't work
230 * Fixed: Subversion repository "View differences" on each file rise ERROR
237 * Fixed: Subversion repository "View differences" on each file rise ERROR
231 * Fixed: View differences for individual file of a changeset fails if the repository URL doesn't point to the repository root
238 * Fixed: View differences for individual file of a changeset fails if the repository URL doesn't point to the repository root
232 * Fixed: It is possible to lock out the last admin account
239 * Fixed: It is possible to lock out the last admin account
233 * Fixed: Wikis are viewable for anonymous users on public projects, despite not granting access
240 * Fixed: Wikis are viewable for anonymous users on public projects, despite not granting access
234 * Fixed: Issue number display clipped on 'my issues'
241 * Fixed: Issue number display clipped on 'my issues'
235 * Fixed: Roadmap version list links not carrying state
242 * Fixed: Roadmap version list links not carrying state
236 * Fixed: Log Time fieldset in IssueController#edit doesn't set default Activity as default
243 * Fixed: Log Time fieldset in IssueController#edit doesn't set default Activity as default
237 * Fixed: git's "get_rev" API should use repo's current branch instead of hardwiring "master"
244 * Fixed: git's "get_rev" API should use repo's current branch instead of hardwiring "master"
238 * Fixed: browser's language subcodes ignored
245 * Fixed: browser's language subcodes ignored
239 * Fixed: Error on project selection with numeric (only) identifier.
246 * Fixed: Error on project selection with numeric (only) identifier.
240 * Fixed: Link to PDF doesn't work after creating new issue
247 * Fixed: Link to PDF doesn't work after creating new issue
241 * Fixed: "Replies" should not be shown on forum threads that are locked
248 * Fixed: "Replies" should not be shown on forum threads that are locked
242 * Fixed: SVN errors lead to svn username/password being displayed to end users (security issue)
249 * Fixed: SVN errors lead to svn username/password being displayed to end users (security issue)
243 * Fixed: http links containing hashes don't display correct
250 * Fixed: http links containing hashes don't display correct
244 * Fixed: Allow ampersands in Enumeration names
251 * Fixed: Allow ampersands in Enumeration names
245 * Fixed: Atom link on saved query does not include query_id
252 * Fixed: Atom link on saved query does not include query_id
246 * Fixed: Logtime info lost when there's an error updating an issue
253 * Fixed: Logtime info lost when there's an error updating an issue
247 * Fixed: TOC does not parse colorization markups
254 * Fixed: TOC does not parse colorization markups
248 * Fixed: CVS: add support for modules names with spaces
255 * Fixed: CVS: add support for modules names with spaces
249 * Fixed: Bad rendering on projects/add
256 * Fixed: Bad rendering on projects/add
250 * Fixed: exception when viewing differences on cvs
257 * Fixed: exception when viewing differences on cvs
251 * Fixed: export issue to pdf will messup when use Chinese language
258 * Fixed: export issue to pdf will messup when use Chinese language
252 * Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant
259 * Fixed: Redmine::Scm::Adapters::GitAdapter#get_rev ignored GIT_BIN constant
253 * Fixed: Adding non-ASCII new issue type in the New Issue page have encoding error using IE
260 * Fixed: Adding non-ASCII new issue type in the New Issue page have encoding error using IE
254 * Fixed: Importing from trac : some wiki links are messed
261 * Fixed: Importing from trac : some wiki links are messed
255 * Fixed: Incorrect weekend definition in Hebrew calendar locale
262 * Fixed: Incorrect weekend definition in Hebrew calendar locale
256 * Fixed: Atom feeds don't provide author section for repository revisions
263 * Fixed: Atom feeds don't provide author section for repository revisions
257 * Fixed: In Activity views, changesets titles can be multiline while they should not
264 * Fixed: In Activity views, changesets titles can be multiline while they should not
258 * Fixed: Ignore unreadable subversion directories (read disabled using authz)
265 * Fixed: Ignore unreadable subversion directories (read disabled using authz)
259 * Fixed: lib/SVG/Graph/Graph.rb can't externalize stylesheets
266 * Fixed: lib/SVG/Graph/Graph.rb can't externalize stylesheets
260 * Fixed: Close statement handler in Redmine.pm
267 * Fixed: Close statement handler in Redmine.pm
261
268
262
269
263 == 2008-05-04 v0.7.1
270 == 2008-05-04 v0.7.1
264
271
265 * Thai translation added (Gampol Thitinilnithi)
272 * Thai translation added (Gampol Thitinilnithi)
266 * Translations updates
273 * Translations updates
267 * Escape HTML comment tags
274 * Escape HTML comment tags
268 * Prevent "can't convert nil into String" error when :sort_order param is not present
275 * Prevent "can't convert nil into String" error when :sort_order param is not present
269 * Fixed: Updating tickets add a time log with zero hours
276 * Fixed: Updating tickets add a time log with zero hours
270 * Fixed: private subprojects names are revealed on the project overview
277 * Fixed: private subprojects names are revealed on the project overview
271 * Fixed: Search for target version of "none" fails with postgres 8.3
278 * Fixed: Search for target version of "none" fails with postgres 8.3
272 * Fixed: Home, Logout, Login links shouldn't be absolute links
279 * Fixed: Home, Logout, Login links shouldn't be absolute links
273 * Fixed: 'Latest projects' box on the welcome screen should be hidden if there are no projects
280 * Fixed: 'Latest projects' box on the welcome screen should be hidden if there are no projects
274 * Fixed: error when using upcase language name in coderay
281 * Fixed: error when using upcase language name in coderay
275 * Fixed: error on Trac import when :due attribute is nil
282 * Fixed: error on Trac import when :due attribute is nil
276
283
277
284
278 == 2008-04-28 v0.7.0
285 == 2008-04-28 v0.7.0
279
286
280 * Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present
287 * Forces Redmine to use rails 2.0.2 gem when vendor/rails is not present
281 * Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list.
288 * Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list.
282 * Add predefined date ranges to the time report
289 * Add predefined date ranges to the time report
283 * Time report can be done at issue level
290 * Time report can be done at issue level
284 * Various timelog report enhancements
291 * Various timelog report enhancements
285 * Accept the following formats for "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30
292 * Accept the following formats for "hours" field: 1h, 1 h, 1 hour, 2 hours, 30m, 30min, 1h30, 1h30m, 1:30
286 * Display the context menu above and/or to the left of the click if needed
293 * Display the context menu above and/or to the left of the click if needed
287 * Make the admin project files list sortable
294 * Make the admin project files list sortable
288 * Mercurial: display working directory files sizes unless browsing a specific revision
295 * Mercurial: display working directory files sizes unless browsing a specific revision
289 * Preserve status filter and page number when using lock/unlock/activate links on the users list
296 * Preserve status filter and page number when using lock/unlock/activate links on the users list
290 * Redmine.pm support for LDAP authentication
297 * Redmine.pm support for LDAP authentication
291 * Better error message and AR errors in log for failed LDAP on-the-fly user creation
298 * Better error message and AR errors in log for failed LDAP on-the-fly user creation
292 * Redirected user to where he is coming from after logging hours
299 * Redirected user to where he is coming from after logging hours
293 * Warn user that subprojects are also deleted when deleting a project
300 * Warn user that subprojects are also deleted when deleting a project
294 * Include subprojects versions on calendar and gantt
301 * Include subprojects versions on calendar and gantt
295 * Notify project members when a message is posted if they want to receive notifications
302 * Notify project members when a message is posted if they want to receive notifications
296 * Fixed: Feed content limit setting has no effect
303 * Fixed: Feed content limit setting has no effect
297 * Fixed: Priorities not ordered when displayed as a filter in issue list
304 * Fixed: Priorities not ordered when displayed as a filter in issue list
298 * Fixed: can not display attached images inline in message replies
305 * Fixed: can not display attached images inline in message replies
299 * Fixed: Boards are not deleted when project is deleted
306 * Fixed: Boards are not deleted when project is deleted
300 * Fixed: trying to preview a new issue raises an exception with postgresql
307 * Fixed: trying to preview a new issue raises an exception with postgresql
301 * Fixed: single file 'View difference' links do not work because of duplicate slashes in url
308 * Fixed: single file 'View difference' links do not work because of duplicate slashes in url
302 * Fixed: inline image not displayed when including a wiki page
309 * Fixed: inline image not displayed when including a wiki page
303 * Fixed: CVS duplicate key violation
310 * Fixed: CVS duplicate key violation
304 * Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues
311 * Fixed: ActiveRecord::StaleObjectError exception on closing a set of circular duplicate issues
305 * Fixed: custom field filters behaviour
312 * Fixed: custom field filters behaviour
306 * Fixed: Postgresql 8.3 compatibility
313 * Fixed: Postgresql 8.3 compatibility
307 * Fixed: Links to repository directories don't work
314 * Fixed: Links to repository directories don't work
308
315
309
316
310 == 2008-03-29 v0.7.0-rc1
317 == 2008-03-29 v0.7.0-rc1
311
318
312 * Overall activity view and feed added, link is available on the project list
319 * Overall activity view and feed added, link is available on the project list
313 * Git VCS support
320 * Git VCS support
314 * Rails 2.0 sessions cookie store compatibility
321 * Rails 2.0 sessions cookie store compatibility
315 * Use project identifiers in urls instead of ids
322 * Use project identifiers in urls instead of ids
316 * Default configuration data can now be loaded from the administration screen
323 * Default configuration data can now be loaded from the administration screen
317 * Administration settings screen split to tabs (email notifications options moved to 'Settings')
324 * Administration settings screen split to tabs (email notifications options moved to 'Settings')
318 * Project description is now unlimited and optional
325 * Project description is now unlimited and optional
319 * Wiki annotate view
326 * Wiki annotate view
320 * Escape HTML tag in textile content
327 * Escape HTML tag in textile content
321 * Add Redmine links to documents, versions, attachments and repository files
328 * Add Redmine links to documents, versions, attachments and repository files
322 * New setting to specify how many objects should be displayed on paginated lists. There are 2 ways to select a set of issues on the issue list:
329 * New setting to specify how many objects should be displayed on paginated lists. There are 2 ways to select a set of issues on the issue list:
323 * by using checkbox and/or the little pencil that will select/unselect all issues
330 * by using checkbox and/or the little pencil that will select/unselect all issues
324 * by clicking on the rows (but not on the links), Ctrl and Shift keys can be used to select multiple issues
331 * by clicking on the rows (but not on the links), Ctrl and Shift keys can be used to select multiple issues
325 * Context menu disabled on links so that the default context menu of the browser is displayed when right-clicking on a link (click anywhere else on the row to display the context menu)
332 * Context menu disabled on links so that the default context menu of the browser is displayed when right-clicking on a link (click anywhere else on the row to display the context menu)
326 * User display format is now configurable in administration settings
333 * User display format is now configurable in administration settings
327 * Issue list now supports bulk edit/move/delete (for a set of issues that belong to the same project)
334 * Issue list now supports bulk edit/move/delete (for a set of issues that belong to the same project)
328 * Merged 'change status', 'edit issue' and 'add note' actions:
335 * Merged 'change status', 'edit issue' and 'add note' actions:
329 * Users with 'edit issues' permission can now update any property including custom fields when adding a note or changing the status
336 * Users with 'edit issues' permission can now update any property including custom fields when adding a note or changing the status
330 * 'Change issue status' permission removed. To change an issue status, a user just needs to have either 'Edit' or 'Add note' permissions and some workflow transitions allowed
337 * 'Change issue status' permission removed. To change an issue status, a user just needs to have either 'Edit' or 'Add note' permissions and some workflow transitions allowed
331 * Details by assignees on issue summary view
338 * Details by assignees on issue summary view
332 * 'New issue' link in the main menu (accesskey 7). The drop-down lists to add an issue on the project overview and the issue list are removed
339 * 'New issue' link in the main menu (accesskey 7). The drop-down lists to add an issue on the project overview and the issue list are removed
333 * Change status select box default to current status
340 * Change status select box default to current status
334 * Preview for issue notes, news and messages
341 * Preview for issue notes, news and messages
335 * Optional description for attachments
342 * Optional description for attachments
336 * 'Fixed version' label changed to 'Target version'
343 * 'Fixed version' label changed to 'Target version'
337 * Let the user choose when deleting issues with reported hours to:
344 * Let the user choose when deleting issues with reported hours to:
338 * delete the hours
345 * delete the hours
339 * assign the hours to the project
346 * assign the hours to the project
340 * reassign the hours to another issue
347 * reassign the hours to another issue
341 * Date range filter and pagination on time entries detail view
348 * Date range filter and pagination on time entries detail view
342 * Propagate time tracking to the parent project
349 * Propagate time tracking to the parent project
343 * Switch added on the project activity view to include subprojects
350 * Switch added on the project activity view to include subprojects
344 * Display total estimated and spent hours on the version detail view
351 * Display total estimated and spent hours on the version detail view
345 * Weekly time tracking block for 'My page'
352 * Weekly time tracking block for 'My page'
346 * Permissions to edit time entries
353 * Permissions to edit time entries
347 * Include subprojects on the issue list, calendar, gantt and timelog by default (can be turned off is administration settings)
354 * Include subprojects on the issue list, calendar, gantt and timelog by default (can be turned off is administration settings)
348 * Roadmap enhancements (separate related issues from wiki contents, leading h1 in version wiki pages is hidden, smaller wiki headings)
355 * Roadmap enhancements (separate related issues from wiki contents, leading h1 in version wiki pages is hidden, smaller wiki headings)
349 * Make versions with same date sorted by name
356 * Make versions with same date sorted by name
350 * Allow issue list to be sorted by target version
357 * Allow issue list to be sorted by target version
351 * Related changesets messages displayed on the issue details view
358 * Related changesets messages displayed on the issue details view
352 * Create a journal and send an email when an issue is closed by commit
359 * Create a journal and send an email when an issue is closed by commit
353 * Add 'Author' to the available columns for the issue list
360 * Add 'Author' to the available columns for the issue list
354 * More appropriate default sort order on sortable columns
361 * More appropriate default sort order on sortable columns
355 * Add issue subject to the time entries view and issue subject, description and tracker to the csv export
362 * Add issue subject to the time entries view and issue subject, description and tracker to the csv export
356 * Permissions to edit issue notes
363 * Permissions to edit issue notes
357 * Display date/time instead of date on files list
364 * Display date/time instead of date on files list
358 * Do not show Roadmap menu item if the project doesn't define any versions
365 * Do not show Roadmap menu item if the project doesn't define any versions
359 * Allow longer version names (60 chars)
366 * Allow longer version names (60 chars)
360 * Ability to copy an existing workflow when creating a new role
367 * Ability to copy an existing workflow when creating a new role
361 * Display custom fields in two columns on the issue form
368 * Display custom fields in two columns on the issue form
362 * Added 'estimated time' in the csv export of the issue list
369 * Added 'estimated time' in the csv export of the issue list
363 * Display the last 30 days on the activity view rather than the current month (number of days can be configured in the application settings)
370 * Display the last 30 days on the activity view rather than the current month (number of days can be configured in the application settings)
364 * Setting for whether new projects should be public by default
371 * Setting for whether new projects should be public by default
365 * User preference to choose how comments/replies are displayed: in chronological or reverse chronological order
372 * User preference to choose how comments/replies are displayed: in chronological or reverse chronological order
366 * Added default value for custom fields
373 * Added default value for custom fields
367 * Added tabindex property on wiki toolbar buttons (to easily move from field to field using the tab key)
374 * Added tabindex property on wiki toolbar buttons (to easily move from field to field using the tab key)
368 * Redirect to issue page after creating a new issue
375 * Redirect to issue page after creating a new issue
369 * Wiki toolbar improvements (mainly for Firefox)
376 * Wiki toolbar improvements (mainly for Firefox)
370 * Display wiki syntax quick ref link on all wiki textareas
377 * Display wiki syntax quick ref link on all wiki textareas
371 * Display links to Atom feeds
378 * Display links to Atom feeds
372 * Breadcrumb nav for the forums
379 * Breadcrumb nav for the forums
373 * Show replies when choosing to display messages in the activity
380 * Show replies when choosing to display messages in the activity
374 * Added 'include' macro to include another wiki page
381 * Added 'include' macro to include another wiki page
375 * RedmineWikiFormatting page available as a static HTML file locally
382 * RedmineWikiFormatting page available as a static HTML file locally
376 * Wrap diff content
383 * Wrap diff content
377 * Strip out email address from authors in repository screens
384 * Strip out email address from authors in repository screens
378 * Highlight the current item of the main menu
385 * Highlight the current item of the main menu
379 * Added simple syntax highlighters for php and java languages
386 * Added simple syntax highlighters for php and java languages
380 * Do not show empty diffs
387 * Do not show empty diffs
381 * Show explicit error message when the scm command failed (eg. when svn binary is not available)
388 * Show explicit error message when the scm command failed (eg. when svn binary is not available)
382 * Lithuanian translation added (Sergej Jegorov)
389 * Lithuanian translation added (Sergej Jegorov)
383 * Ukrainan translation added (Natalia Konovka & Mykhaylo Sorochan)
390 * Ukrainan translation added (Natalia Konovka & Mykhaylo Sorochan)
384 * Danish translation added (Mads Vestergaard)
391 * Danish translation added (Mads Vestergaard)
385 * Added i18n support to the jstoolbar and various settings screen
392 * Added i18n support to the jstoolbar and various settings screen
386 * RedCloth's glyphs no longer user
393 * RedCloth's glyphs no longer user
387 * New icons for the wiki toolbar (from http://www.famfamfam.com/lab/icons/silk/)
394 * New icons for the wiki toolbar (from http://www.famfamfam.com/lab/icons/silk/)
388 * The following menus can now be extended by plugins: top_menu, account_menu, application_menu
395 * The following menus can now be extended by plugins: top_menu, account_menu, application_menu
389 * Added a simple rake task to fetch changesets from the repositories: rake redmine:fetch_changesets
396 * Added a simple rake task to fetch changesets from the repositories: rake redmine:fetch_changesets
390 * Remove hardcoded "Redmine" strings in account related emails and use application title instead
397 * Remove hardcoded "Redmine" strings in account related emails and use application title instead
391 * Mantis importer preserve bug ids
398 * Mantis importer preserve bug ids
392 * Trac importer: Trac guide wiki pages skipped
399 * Trac importer: Trac guide wiki pages skipped
393 * Trac importer: wiki attachments migration added
400 * Trac importer: wiki attachments migration added
394 * Trac importer: support database schema for Trac migration
401 * Trac importer: support database schema for Trac migration
395 * Trac importer: support CamelCase links
402 * Trac importer: support CamelCase links
396 * Removes the Redmine version from the footer (can be viewed on admin -> info)
403 * Removes the Redmine version from the footer (can be viewed on admin -> info)
397 * Rescue and display an error message when trying to delete a role that is in use
404 * Rescue and display an error message when trying to delete a role that is in use
398 * Add various 'X-Redmine' headers to email notifications: X-Redmine-Host, X-Redmine-Site, X-Redmine-Project, X-Redmine-Issue-Id, -Author, -Assignee, X-Redmine-Topic-Id
405 * Add various 'X-Redmine' headers to email notifications: X-Redmine-Host, X-Redmine-Site, X-Redmine-Project, X-Redmine-Issue-Id, -Author, -Assignee, X-Redmine-Topic-Id
399 * Add "--encoding utf8" option to the Mercurial "hg log" command in order to get utf8 encoded commit logs
406 * Add "--encoding utf8" option to the Mercurial "hg log" command in order to get utf8 encoded commit logs
400 * Fixed: Gantt and calendar not properly refreshed (fragment caching removed)
407 * Fixed: Gantt and calendar not properly refreshed (fragment caching removed)
401 * Fixed: Textile image with style attribute cause internal server error
408 * Fixed: Textile image with style attribute cause internal server error
402 * Fixed: wiki TOC not rendered properly when used in an issue or document description
409 * Fixed: wiki TOC not rendered properly when used in an issue or document description
403 * Fixed: 'has already been taken' error message on username and email fields if left empty
410 * Fixed: 'has already been taken' error message on username and email fields if left empty
404 * Fixed: non-ascii attachement filename with IE
411 * Fixed: non-ascii attachement filename with IE
405 * Fixed: wrong url for wiki syntax pop-up when Redmine urls are prefixed
412 * Fixed: wrong url for wiki syntax pop-up when Redmine urls are prefixed
406 * Fixed: search for all words doesn't work
413 * Fixed: search for all words doesn't work
407 * Fixed: Do not show sticky and locked checkboxes when replying to a message
414 * Fixed: Do not show sticky and locked checkboxes when replying to a message
408 * Fixed: Mantis importer: do not duplicate Mantis username in firstname and lastname if realname is blank
415 * Fixed: Mantis importer: do not duplicate Mantis username in firstname and lastname if realname is blank
409 * Fixed: Date custom fields not displayed as specified in application settings
416 * Fixed: Date custom fields not displayed as specified in application settings
410 * Fixed: titles not escaped in the activity view
417 * Fixed: titles not escaped in the activity view
411 * Fixed: issue queries can not use custom fields marked as 'for all projects' in a project context
418 * Fixed: issue queries can not use custom fields marked as 'for all projects' in a project context
412 * Fixed: on calendar, gantt and in the tracker filter on the issue list, only active trackers of the project (and its sub projects) should be available
419 * Fixed: on calendar, gantt and in the tracker filter on the issue list, only active trackers of the project (and its sub projects) should be available
413 * Fixed: locked users should not receive email notifications
420 * Fixed: locked users should not receive email notifications
414 * Fixed: custom field selection is not saved when unchecking them all on project settings
421 * Fixed: custom field selection is not saved when unchecking them all on project settings
415 * Fixed: can not lock a topic when creating it
422 * Fixed: can not lock a topic when creating it
416 * Fixed: Incorrect filtering for unset values when using 'is not' filter
423 * Fixed: Incorrect filtering for unset values when using 'is not' filter
417 * Fixed: PostgreSQL issues_seq_id not updated when using Trac importer
424 * Fixed: PostgreSQL issues_seq_id not updated when using Trac importer
418 * Fixed: ajax pagination does not scroll up
425 * Fixed: ajax pagination does not scroll up
419 * Fixed: error when uploading a file with no content-type specified by the browser
426 * Fixed: error when uploading a file with no content-type specified by the browser
420 * Fixed: wiki and changeset links not displayed when previewing issue description or notes
427 * Fixed: wiki and changeset links not displayed when previewing issue description or notes
421 * Fixed: 'LdapError: no bind result' error when authenticating
428 * Fixed: 'LdapError: no bind result' error when authenticating
422 * Fixed: 'LdapError: invalid binding information' when no username/password are set on the LDAP account
429 * Fixed: 'LdapError: invalid binding information' when no username/password are set on the LDAP account
423 * Fixed: CVS repository doesn't work if port is used in the url
430 * Fixed: CVS repository doesn't work if port is used in the url
424 * Fixed: Email notifications: host name is missing in generated links
431 * Fixed: Email notifications: host name is missing in generated links
425 * Fixed: Email notifications: referenced changesets, wiki pages, attachments... are not turned into links
432 * Fixed: Email notifications: referenced changesets, wiki pages, attachments... are not turned into links
426 * Fixed: Do not clear issue relations when moving an issue to another project if cross-project issue relations are allowed
433 * Fixed: Do not clear issue relations when moving an issue to another project if cross-project issue relations are allowed
427 * Fixed: "undefined method 'textilizable'" error on email notification when running Repository#fetch_changesets from the console
434 * Fixed: "undefined method 'textilizable'" error on email notification when running Repository#fetch_changesets from the console
428 * Fixed: Do not send an email with no recipient, cc or bcc
435 * Fixed: Do not send an email with no recipient, cc or bcc
429 * Fixed: fetch_changesets fails on commit comments that close 2 duplicates issues.
436 * Fixed: fetch_changesets fails on commit comments that close 2 duplicates issues.
430 * Fixed: Mercurial browsing under unix-like os and for directory depth > 2
437 * Fixed: Mercurial browsing under unix-like os and for directory depth > 2
431 * Fixed: Wiki links with pipe can not be used in wiki tables
438 * Fixed: Wiki links with pipe can not be used in wiki tables
432 * Fixed: migrate_from_trac doesn't import timestamps of wiki and tickets
439 * Fixed: migrate_from_trac doesn't import timestamps of wiki and tickets
433 * Fixed: when bulk editing, setting "Assigned to" to "nobody" causes an sql error with Postgresql
440 * Fixed: when bulk editing, setting "Assigned to" to "nobody" causes an sql error with Postgresql
434
441
435
442
436 == 2008-03-12 v0.6.4
443 == 2008-03-12 v0.6.4
437
444
438 * Fixed: private projects name are displayed on account/show even if the current user doesn't have access to these private projects
445 * Fixed: private projects name are displayed on account/show even if the current user doesn't have access to these private projects
439 * Fixed: potential LDAP authentication security flaw
446 * Fixed: potential LDAP authentication security flaw
440 * Fixed: context submenus on the issue list don't show up with IE6.
447 * Fixed: context submenus on the issue list don't show up with IE6.
441 * Fixed: Themes are not applied with Rails 2.0
448 * Fixed: Themes are not applied with Rails 2.0
442 * Fixed: crash when fetching Mercurial changesets if changeset[:files] is nil
449 * Fixed: crash when fetching Mercurial changesets if changeset[:files] is nil
443 * Fixed: Mercurial repository browsing
450 * Fixed: Mercurial repository browsing
444 * Fixed: undefined local variable or method 'log' in CvsAdapter when a cvs command fails
451 * Fixed: undefined local variable or method 'log' in CvsAdapter when a cvs command fails
445 * Fixed: not null constraints not removed with Postgresql
452 * Fixed: not null constraints not removed with Postgresql
446 * Doctype set to transitional
453 * Doctype set to transitional
447
454
448
455
449 == 2007-12-18 v0.6.3
456 == 2007-12-18 v0.6.3
450
457
451 * Fixed: upload doesn't work in 'Files' section
458 * Fixed: upload doesn't work in 'Files' section
452
459
453
460
454 == 2007-12-16 v0.6.2
461 == 2007-12-16 v0.6.2
455
462
456 * Search engine: issue custom fields can now be searched
463 * Search engine: issue custom fields can now be searched
457 * News comments are now textilized
464 * News comments are now textilized
458 * Updated Japanese translation (Satoru Kurashiki)
465 * Updated Japanese translation (Satoru Kurashiki)
459 * Updated Chinese translation (Shortie Lo)
466 * Updated Chinese translation (Shortie Lo)
460 * Fixed Rails 2.0 compatibility bugs:
467 * Fixed Rails 2.0 compatibility bugs:
461 * Unable to create a wiki
468 * Unable to create a wiki
462 * Gantt and calendar error
469 * Gantt and calendar error
463 * Trac importer error (readonly? is defined by ActiveRecord)
470 * Trac importer error (readonly? is defined by ActiveRecord)
464 * Fixed: 'assigned to me' filter broken
471 * Fixed: 'assigned to me' filter broken
465 * Fixed: crash when validation fails on issue edition with no custom fields
472 * Fixed: crash when validation fails on issue edition with no custom fields
466 * Fixed: reposman "can't find group" error
473 * Fixed: reposman "can't find group" error
467 * Fixed: 'LDAP account password is too long' error when leaving the field empty on creation
474 * Fixed: 'LDAP account password is too long' error when leaving the field empty on creation
468 * Fixed: empty lines when displaying repository files with Windows style eol
475 * Fixed: empty lines when displaying repository files with Windows style eol
469 * Fixed: missing body closing tag in repository annotate and entry views
476 * Fixed: missing body closing tag in repository annotate and entry views
470
477
471
478
472 == 2007-12-10 v0.6.1
479 == 2007-12-10 v0.6.1
473
480
474 * Rails 2.0 compatibility
481 * Rails 2.0 compatibility
475 * Custom fields can now be displayed as columns on the issue list
482 * Custom fields can now be displayed as columns on the issue list
476 * Added version details view (accessible from the roadmap)
483 * Added version details view (accessible from the roadmap)
477 * Roadmap: more accurate completion percentage calculation (done ratio of open issues is now taken into account)
484 * Roadmap: more accurate completion percentage calculation (done ratio of open issues is now taken into account)
478 * Added per-project tracker selection. Trackers can be selected on project settings
485 * Added per-project tracker selection. Trackers can be selected on project settings
479 * Anonymous users can now be allowed to create, edit, comment issues, comment news and post messages in the forums
486 * Anonymous users can now be allowed to create, edit, comment issues, comment news and post messages in the forums
480 * Forums: messages can now be edited/deleted (explicit permissions need to be given)
487 * Forums: messages can now be edited/deleted (explicit permissions need to be given)
481 * Forums: topics can be locked so that no reply can be added
488 * Forums: topics can be locked so that no reply can be added
482 * Forums: topics can be marked as sticky so that they always appear at the top of the list
489 * Forums: topics can be marked as sticky so that they always appear at the top of the list
483 * Forums: attachments can now be added to replies
490 * Forums: attachments can now be added to replies
484 * Added time zone support
491 * Added time zone support
485 * Added a setting to choose the account activation strategy (available in application settings)
492 * Added a setting to choose the account activation strategy (available in application settings)
486 * Added 'Classic' theme (inspired from the v0.51 design)
493 * Added 'Classic' theme (inspired from the v0.51 design)
487 * Added an alternate theme which provides issue list colorization based on issues priority
494 * Added an alternate theme which provides issue list colorization based on issues priority
488 * Added Bazaar SCM adapter
495 * Added Bazaar SCM adapter
489 * Added Annotate/Blame view in the repository browser (except for Darcs SCM)
496 * Added Annotate/Blame view in the repository browser (except for Darcs SCM)
490 * Diff style (inline or side by side) automatically saved as a user preference
497 * Diff style (inline or side by side) automatically saved as a user preference
491 * Added issues status changes on the activity view (by Cyril Mougel)
498 * Added issues status changes on the activity view (by Cyril Mougel)
492 * Added forums topics on the activity view (disabled by default)
499 * Added forums topics on the activity view (disabled by default)
493 * Added an option on 'My account' for users who don't want to be notified of changes that they make
500 * Added an option on 'My account' for users who don't want to be notified of changes that they make
494 * Trac importer now supports mysql and postgresql databases
501 * Trac importer now supports mysql and postgresql databases
495 * Trac importer improvements (by Mat Trudel)
502 * Trac importer improvements (by Mat Trudel)
496 * 'fixed version' field can now be displayed on the issue list
503 * 'fixed version' field can now be displayed on the issue list
497 * Added a couple of new formats for the 'date format' setting
504 * Added a couple of new formats for the 'date format' setting
498 * Added Traditional Chinese translation (by Shortie Lo)
505 * Added Traditional Chinese translation (by Shortie Lo)
499 * Added Russian translation (iGor kMeta)
506 * Added Russian translation (iGor kMeta)
500 * Project name format limitation removed (name can now contain any character)
507 * Project name format limitation removed (name can now contain any character)
501 * Project identifier maximum length changed from 12 to 20
508 * Project identifier maximum length changed from 12 to 20
502 * Changed the maximum length of LDAP account to 255 characters
509 * Changed the maximum length of LDAP account to 255 characters
503 * Removed the 12 characters limit on passwords
510 * Removed the 12 characters limit on passwords
504 * Added wiki macros support
511 * Added wiki macros support
505 * Performance improvement on workflow setup screen
512 * Performance improvement on workflow setup screen
506 * More detailed html title on several views
513 * More detailed html title on several views
507 * Custom fields can now be reordered
514 * Custom fields can now be reordered
508 * Search engine: search can be restricted to an exact phrase by using quotation marks
515 * Search engine: search can be restricted to an exact phrase by using quotation marks
509 * Added custom fields marked as 'For all projects' to the csv export of the cross project issue list
516 * Added custom fields marked as 'For all projects' to the csv export of the cross project issue list
510 * Email notifications are now sent as Blind carbon copy by default
517 * Email notifications are now sent as Blind carbon copy by default
511 * Fixed: all members (including non active) should be deleted when deleting a project
518 * Fixed: all members (including non active) should be deleted when deleting a project
512 * Fixed: Error on wiki syntax link (accessible from wiki/edit)
519 * Fixed: Error on wiki syntax link (accessible from wiki/edit)
513 * Fixed: 'quick jump to a revision' form on the revisions list
520 * Fixed: 'quick jump to a revision' form on the revisions list
514 * Fixed: error on admin/info if there's more than 1 plugin installed
521 * Fixed: error on admin/info if there's more than 1 plugin installed
515 * Fixed: svn or ldap password can be found in clear text in the html source in editing mode
522 * Fixed: svn or ldap password can be found in clear text in the html source in editing mode
516 * Fixed: 'Assigned to' drop down list is not sorted
523 * Fixed: 'Assigned to' drop down list is not sorted
517 * Fixed: 'View all issues' link doesn't work on issues/show
524 * Fixed: 'View all issues' link doesn't work on issues/show
518 * Fixed: error on account/register when validation fails
525 * Fixed: error on account/register when validation fails
519 * Fixed: Error when displaying the issue list if a float custom field is marked as 'used as filter'
526 * Fixed: Error when displaying the issue list if a float custom field is marked as 'used as filter'
520 * Fixed: Mercurial adapter breaks on missing :files entry in changeset hash (James Britt)
527 * Fixed: Mercurial adapter breaks on missing :files entry in changeset hash (James Britt)
521 * Fixed: Wrong feed URLs on the home page
528 * Fixed: Wrong feed URLs on the home page
522 * Fixed: Update of time entry fails when the issue has been moved to an other project
529 * Fixed: Update of time entry fails when the issue has been moved to an other project
523 * Fixed: Error when moving an issue without changing its tracker (Postgresql)
530 * Fixed: Error when moving an issue without changing its tracker (Postgresql)
524 * Fixed: Changes not recorded when using :pserver string (CVS adapter)
531 * Fixed: Changes not recorded when using :pserver string (CVS adapter)
525 * Fixed: admin should be able to move issues to any project
532 * Fixed: admin should be able to move issues to any project
526 * Fixed: adding an attachment is not possible when changing the status of an issue
533 * Fixed: adding an attachment is not possible when changing the status of an issue
527 * Fixed: No mime-types in documents/files downloading
534 * Fixed: No mime-types in documents/files downloading
528 * Fixed: error when sorting the messages if there's only one board for the project
535 * Fixed: error when sorting the messages if there's only one board for the project
529 * Fixed: 'me' doesn't appear in the drop down filters on a project issue list.
536 * Fixed: 'me' doesn't appear in the drop down filters on a project issue list.
530
537
531 == 2007-11-04 v0.6.0
538 == 2007-11-04 v0.6.0
532
539
533 * Permission model refactoring.
540 * Permission model refactoring.
534 * Permissions: there are now 2 builtin roles that can be used to specify permissions given to other users than members of projects
541 * Permissions: there are now 2 builtin roles that can be used to specify permissions given to other users than members of projects
535 * Permissions: some permissions (eg. browse the repository) can be removed for certain roles
542 * Permissions: some permissions (eg. browse the repository) can be removed for certain roles
536 * Permissions: modules (eg. issue tracking, news, documents...) can be enabled/disabled at project level
543 * Permissions: modules (eg. issue tracking, news, documents...) can be enabled/disabled at project level
537 * Added Mantis and Trac importers
544 * Added Mantis and Trac importers
538 * New application layout
545 * New application layout
539 * Added "Bulk edit" functionality on the issue list
546 * Added "Bulk edit" functionality on the issue list
540 * More flexible mail notifications settings at user level
547 * More flexible mail notifications settings at user level
541 * Added AJAX based context menu on the project issue list that provide shortcuts for editing, re-assigning, changing the status or the priority, moving or deleting an issue
548 * Added AJAX based context menu on the project issue list that provide shortcuts for editing, re-assigning, changing the status or the priority, moving or deleting an issue
542 * Added the hability to copy an issue. It can be done from the "issue/show" view or from the context menu on the issue list
549 * Added the hability to copy an issue. It can be done from the "issue/show" view or from the context menu on the issue list
543 * Added the ability to customize issue list columns (at application level or for each saved query)
550 * Added the ability to customize issue list columns (at application level or for each saved query)
544 * Overdue versions (date reached and open issues > 0) are now always displayed on the roadmap
551 * Overdue versions (date reached and open issues > 0) are now always displayed on the roadmap
545 * Added the ability to rename wiki pages (specific permission required)
552 * Added the ability to rename wiki pages (specific permission required)
546 * Search engines now supports pagination. Results are sorted in reverse chronological order
553 * Search engines now supports pagination. Results are sorted in reverse chronological order
547 * Added "Estimated hours" attribute on issues
554 * Added "Estimated hours" attribute on issues
548 * A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category
555 * A category with assigned issue can now be deleted. 2 options are proposed: remove assignments or reassign issues to another category
549 * Forum notifications are now also sent to the authors of the thread, even if they donοΏ½t watch the board
556 * Forum notifications are now also sent to the authors of the thread, even if they donοΏ½t watch the board
550 * Added an application setting to specify the application protocol (http or https) used to generate urls in emails
557 * Added an application setting to specify the application protocol (http or https) used to generate urls in emails
551 * Gantt chart: now starts at the current month by default
558 * Gantt chart: now starts at the current month by default
552 * Gantt chart: month count and zoom factor are automatically saved as user preferences
559 * Gantt chart: month count and zoom factor are automatically saved as user preferences
553 * Wiki links can now refer to other project wikis
560 * Wiki links can now refer to other project wikis
554 * Added wiki index by date
561 * Added wiki index by date
555 * Added preview on add/edit issue form
562 * Added preview on add/edit issue form
556 * Emails footer can now be customized from the admin interface (Admin -> Email notifications)
563 * Emails footer can now be customized from the admin interface (Admin -> Email notifications)
557 * Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that theyοΏ½re properly displayed)
564 * Default encodings for repository files can now be set in application settings (used to convert files content and diff to UTF-8 so that theyοΏ½re properly displayed)
558 * Calendar: first day of week can now be set in lang files
565 * Calendar: first day of week can now be set in lang files
559 * Automatic closing of duplicate issues
566 * Automatic closing of duplicate issues
560 * Added a cross-project issue list
567 * Added a cross-project issue list
561 * AJAXified the SCM browser (tree view)
568 * AJAXified the SCM browser (tree view)
562 * Pretty URL for the repository browser (Cyril Mougel)
569 * Pretty URL for the repository browser (Cyril Mougel)
563 * Search engine: added a checkbox to search titles only
570 * Search engine: added a checkbox to search titles only
564 * Added "% done" in the filter list
571 * Added "% done" in the filter list
565 * Enumerations: values can now be reordered and a default value can be specified (eg. default issue priority)
572 * Enumerations: values can now be reordered and a default value can be specified (eg. default issue priority)
566 * Added some accesskeys
573 * Added some accesskeys
567 * Added "Float" as a custom field format
574 * Added "Float" as a custom field format
568 * Added basic Theme support
575 * Added basic Theme support
569 * Added the ability to set the οΏ½done ratioοΏ½ of issues fixed by commit (Nikolay Solakov)
576 * Added the ability to set the οΏ½done ratioοΏ½ of issues fixed by commit (Nikolay Solakov)
570 * Added custom fields in issue related mail notifications
577 * Added custom fields in issue related mail notifications
571 * Email notifications are now sent in plain text and html
578 * Email notifications are now sent in plain text and html
572 * Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed.
579 * Gantt chart can now be exported to a graphic file (png). This functionality is only available if RMagick is installed.
573 * Added syntax highlightment for repository files and wiki
580 * Added syntax highlightment for repository files and wiki
574 * Improved automatic Redmine links
581 * Improved automatic Redmine links
575 * Added automatic table of content support on wiki pages
582 * Added automatic table of content support on wiki pages
576 * Added radio buttons on the documents list to sort documents by category, date, title or author
583 * Added radio buttons on the documents list to sort documents by category, date, title or author
577 * Added basic plugin support, with a sample plugin
584 * Added basic plugin support, with a sample plugin
578 * Added a link to add a new category when creating or editing an issue
585 * Added a link to add a new category when creating or editing an issue
579 * Added a "Assignable" boolean on the Role model. If unchecked, issues can not be assigned to users having this role.
586 * Added a "Assignable" boolean on the Role model. If unchecked, issues can not be assigned to users having this role.
580 * Added an option to be able to relate issues in different projects
587 * Added an option to be able to relate issues in different projects
581 * Added the ability to move issues (to another project) without changing their trackers.
588 * Added the ability to move issues (to another project) without changing their trackers.
582 * Atom feeds added on project activity, news and changesets
589 * Atom feeds added on project activity, news and changesets
583 * Added the ability to reset its own RSS access key
590 * Added the ability to reset its own RSS access key
584 * Main project list now displays root projects with their subprojects
591 * Main project list now displays root projects with their subprojects
585 * Added anchor links to issue notes
592 * Added anchor links to issue notes
586 * Added reposman Ruby version. This script can now register created repositories in Redmine (Nicolas Chuche)
593 * Added reposman Ruby version. This script can now register created repositories in Redmine (Nicolas Chuche)
587 * Issue notes are now included in search
594 * Issue notes are now included in search
588 * Added email sending test functionality
595 * Added email sending test functionality
589 * Added LDAPS support for LDAP authentication
596 * Added LDAPS support for LDAP authentication
590 * Removed hard-coded URLs in mail templates
597 * Removed hard-coded URLs in mail templates
591 * Subprojects are now grouped by projects in the navigation drop-down menu
598 * Subprojects are now grouped by projects in the navigation drop-down menu
592 * Added a new value for date filters: this week
599 * Added a new value for date filters: this week
593 * Added cache for application settings
600 * Added cache for application settings
594 * Added Polish translation (Tomasz Gawryl)
601 * Added Polish translation (Tomasz Gawryl)
595 * Added Czech translation (Jan Kadlecek)
602 * Added Czech translation (Jan Kadlecek)
596 * Added Romanian translation (Csongor Bartus)
603 * Added Romanian translation (Csongor Bartus)
597 * Added Hebrew translation (Bob Builder)
604 * Added Hebrew translation (Bob Builder)
598 * Added Serbian translation (Dragan Matic)
605 * Added Serbian translation (Dragan Matic)
599 * Added Korean translation (Choi Jong Yoon)
606 * Added Korean translation (Choi Jong Yoon)
600 * Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations
607 * Fixed: the link to delete issue relations is displayed even if the user is not authorized to delete relations
601 * Performance improvement on calendar and gantt
608 * Performance improvement on calendar and gantt
602 * Fixed: wiki preview doesnοΏ½t work on long entries
609 * Fixed: wiki preview doesnοΏ½t work on long entries
603 * Fixed: queries with multiple custom fields return no result
610 * Fixed: queries with multiple custom fields return no result
604 * Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters
611 * Fixed: Can not authenticate user against LDAP if its DN contains non-ascii characters
605 * Fixed: URL with ~ broken in wiki formatting
612 * Fixed: URL with ~ broken in wiki formatting
606 * Fixed: some quotation marks are rendered as strange characters in pdf
613 * Fixed: some quotation marks are rendered as strange characters in pdf
607
614
608
615
609 == 2007-07-15 v0.5.1
616 == 2007-07-15 v0.5.1
610
617
611 * per project forums added
618 * per project forums added
612 * added the ability to archive projects
619 * added the ability to archive projects
613 * added οΏ½WatchοΏ½ functionality on issues. It allows users to receive notifications about issue changes
620 * added οΏ½WatchοΏ½ functionality on issues. It allows users to receive notifications about issue changes
614 * custom fields for issues can now be used as filters on issue list
621 * custom fields for issues can now be used as filters on issue list
615 * added per user custom queries
622 * added per user custom queries
616 * commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings)
623 * commit messages are now scanned for referenced or fixed issue IDs (keywords defined in Admin -> Settings)
617 * projects list now shows the list of public projects and private projects for which the user is a member
624 * projects list now shows the list of public projects and private projects for which the user is a member
618 * versions can now be created with no date
625 * versions can now be created with no date
619 * added issue count details for versions on Reports view
626 * added issue count details for versions on Reports view
620 * added time report, by member/activity/tracker/version and year/month/week for the selected period
627 * added time report, by member/activity/tracker/version and year/month/week for the selected period
621 * each category can now be associated to a user, so that new issues in that category are automatically assigned to that user
628 * each category can now be associated to a user, so that new issues in that category are automatically assigned to that user
622 * added autologin feature (disabled by default)
629 * added autologin feature (disabled by default)
623 * optimistic locking added for wiki edits
630 * optimistic locking added for wiki edits
624 * added wiki diff
631 * added wiki diff
625 * added the ability to destroy wiki pages (requires permission)
632 * added the ability to destroy wiki pages (requires permission)
626 * a wiki page can now be attached to each version, and displayed on the roadmap
633 * a wiki page can now be attached to each version, and displayed on the roadmap
627 * attachments can now be added to wiki pages (original patch by Pavol Murin) and displayed online
634 * attachments can now be added to wiki pages (original patch by Pavol Murin) and displayed online
628 * added an option to see all versions in the roadmap view (including completed ones)
635 * added an option to see all versions in the roadmap view (including completed ones)
629 * added basic issue relations
636 * added basic issue relations
630 * added the ability to log time when changing an issue status
637 * added the ability to log time when changing an issue status
631 * account information can now be sent to the user when creating an account
638 * account information can now be sent to the user when creating an account
632 * author and assignee of an issue always receive notifications (even if they turned of mail notifications)
639 * author and assignee of an issue always receive notifications (even if they turned of mail notifications)
633 * added a quick search form in page header
640 * added a quick search form in page header
634 * added 'me' value for 'assigned to' and 'author' query filters
641 * added 'me' value for 'assigned to' and 'author' query filters
635 * added a link on revision screen to see the entire diff for the revision
642 * added a link on revision screen to see the entire diff for the revision
636 * added last commit message for each entry in repository browser
643 * added last commit message for each entry in repository browser
637 * added the ability to view a file diff with free to/from revision selection.
644 * added the ability to view a file diff with free to/from revision selection.
638 * text files can now be viewed online when browsing the repository
645 * text files can now be viewed online when browsing the repository
639 * added basic support for other SCM: CVS (Ralph Vater), Mercurial and Darcs
646 * added basic support for other SCM: CVS (Ralph Vater), Mercurial and Darcs
640 * added fragment caching for svn diffs
647 * added fragment caching for svn diffs
641 * added fragment caching for calendar and gantt views
648 * added fragment caching for calendar and gantt views
642 * login field automatically focused on login form
649 * login field automatically focused on login form
643 * subproject name displayed on issue list, calendar and gantt
650 * subproject name displayed on issue list, calendar and gantt
644 * added an option to choose the date format: language based or ISO 8601
651 * added an option to choose the date format: language based or ISO 8601
645 * added a simple mail handler. It lets users add notes to an existing issue by replying to the initial notification email.
652 * added a simple mail handler. It lets users add notes to an existing issue by replying to the initial notification email.
646 * a 403 error page is now displayed (instead of a blank page) when trying to access a protected page
653 * a 403 error page is now displayed (instead of a blank page) when trying to access a protected page
647 * added portuguese translation (Joao Carlos Clementoni)
654 * added portuguese translation (Joao Carlos Clementoni)
648 * added partial online help japanese translation (Ken Date)
655 * added partial online help japanese translation (Ken Date)
649 * added bulgarian translation (Nikolay Solakov)
656 * added bulgarian translation (Nikolay Solakov)
650 * added dutch translation (Linda van den Brink)
657 * added dutch translation (Linda van den Brink)
651 * added swedish translation (Thomas Habets)
658 * added swedish translation (Thomas Habets)
652 * italian translation update (Alessio Spadaro)
659 * italian translation update (Alessio Spadaro)
653 * japanese translation update (Satoru Kurashiki)
660 * japanese translation update (Satoru Kurashiki)
654 * fixed: error on history atom feed when thereοΏ½s no notes on an issue change
661 * fixed: error on history atom feed when thereοΏ½s no notes on an issue change
655 * fixed: error in journalizing an issue with longtext custom fields (Postgresql)
662 * fixed: error in journalizing an issue with longtext custom fields (Postgresql)
656 * fixed: creation of Oracle schema
663 * fixed: creation of Oracle schema
657 * fixed: last day of the month not included in project activity
664 * fixed: last day of the month not included in project activity
658 * fixed: files with an apostrophe in their names can't be accessed in SVN repository
665 * fixed: files with an apostrophe in their names can't be accessed in SVN repository
659 * fixed: performance issue on RepositoriesController#revisions when a changeset has a great number of changes (eg. 100,000)
666 * fixed: performance issue on RepositoriesController#revisions when a changeset has a great number of changes (eg. 100,000)
660 * fixed: open/closed issue counts are always 0 on reports view (postgresql)
667 * fixed: open/closed issue counts are always 0 on reports view (postgresql)
661 * fixed: date query filters (wrong results and sql error with postgresql)
668 * fixed: date query filters (wrong results and sql error with postgresql)
662 * fixed: confidentiality issue on account/show (private project names displayed to anyone)
669 * fixed: confidentiality issue on account/show (private project names displayed to anyone)
663 * fixed: Long text custom fields displayed without line breaks
670 * fixed: Long text custom fields displayed without line breaks
664 * fixed: Error when editing the wokflow after deleting a status
671 * fixed: Error when editing the wokflow after deleting a status
665 * fixed: SVN commit dates are now stored as local time
672 * fixed: SVN commit dates are now stored as local time
666
673
667
674
668 == 2007-04-11 v0.5.0
675 == 2007-04-11 v0.5.0
669
676
670 * added per project Wiki
677 * added per project Wiki
671 * added rss/atom feeds at project level (custom queries can be used as feeds)
678 * added rss/atom feeds at project level (custom queries can be used as feeds)
672 * added search engine (search in issues, news, commits, wiki pages, documents)
679 * added search engine (search in issues, news, commits, wiki pages, documents)
673 * simple time tracking functionality added
680 * simple time tracking functionality added
674 * added version due dates on calendar and gantt
681 * added version due dates on calendar and gantt
675 * added subprojects issue count on project Reports page
682 * added subprojects issue count on project Reports page
676 * added the ability to copy an existing workflow when creating a new tracker
683 * added the ability to copy an existing workflow when creating a new tracker
677 * added the ability to include subprojects on calendar and gantt
684 * added the ability to include subprojects on calendar and gantt
678 * added the ability to select trackers to display on calendar and gantt (Jeffrey Jones)
685 * added the ability to select trackers to display on calendar and gantt (Jeffrey Jones)
679 * added side by side svn diff view (Cyril Mougel)
686 * added side by side svn diff view (Cyril Mougel)
680 * added back subproject filter on issue list
687 * added back subproject filter on issue list
681 * added permissions report in admin area
688 * added permissions report in admin area
682 * added a status filter on users list
689 * added a status filter on users list
683 * support for password-protected SVN repositories
690 * support for password-protected SVN repositories
684 * SVN commits are now stored in the database
691 * SVN commits are now stored in the database
685 * added simple svn statistics SVG graphs
692 * added simple svn statistics SVG graphs
686 * progress bars for roadmap versions (Nick Read)
693 * progress bars for roadmap versions (Nick Read)
687 * issue history now shows file uploads and deletions
694 * issue history now shows file uploads and deletions
688 * #id patterns are turned into links to issues in descriptions and commit messages
695 * #id patterns are turned into links to issues in descriptions and commit messages
689 * japanese translation added (Satoru Kurashiki)
696 * japanese translation added (Satoru Kurashiki)
690 * chinese simplified translation added (Andy Wu)
697 * chinese simplified translation added (Andy Wu)
691 * italian translation added (Alessio Spadaro)
698 * italian translation added (Alessio Spadaro)
692 * added scripts to manage SVN repositories creation and user access control using ssh+svn (Nicolas Chuche)
699 * added scripts to manage SVN repositories creation and user access control using ssh+svn (Nicolas Chuche)
693 * better calendar rendering time
700 * better calendar rendering time
694 * fixed migration scripts to work with mysql 5 running in strict mode
701 * fixed migration scripts to work with mysql 5 running in strict mode
695 * fixed: error when clicking "add" with no block selected on my/page_layout
702 * fixed: error when clicking "add" with no block selected on my/page_layout
696 * fixed: hard coded links in navigation bar
703 * fixed: hard coded links in navigation bar
697 * fixed: table_name pre/suffix support
704 * fixed: table_name pre/suffix support
698
705
699
706
700 == 2007-02-18 v0.4.2
707 == 2007-02-18 v0.4.2
701
708
702 * Rails 1.2 is now required
709 * Rails 1.2 is now required
703 * settings are now stored in the database and editable through the application in: Admin -> Settings (config_custom.rb is no longer used)
710 * settings are now stored in the database and editable through the application in: Admin -> Settings (config_custom.rb is no longer used)
704 * added project roadmap view
711 * added project roadmap view
705 * mail notifications added when a document, a file or an attachment is added
712 * mail notifications added when a document, a file or an attachment is added
706 * tooltips added on Gantt chart and calender to view the details of the issues
713 * tooltips added on Gantt chart and calender to view the details of the issues
707 * ability to set the sort order for roles, trackers, issue statuses
714 * ability to set the sort order for roles, trackers, issue statuses
708 * added missing fields to csv export: priority, start date, due date, done ratio
715 * added missing fields to csv export: priority, start date, due date, done ratio
709 * added total number of issues per tracker on project overview
716 * added total number of issues per tracker on project overview
710 * all icons replaced (new icons are based on GPL icon set: "KDE Crystal Diamond 2.5" -by paolino- and "kNeu! Alpha v0.1" -by Pablo Fabregat-)
717 * all icons replaced (new icons are based on GPL icon set: "KDE Crystal Diamond 2.5" -by paolino- and "kNeu! Alpha v0.1" -by Pablo Fabregat-)
711 * added back "fixed version" field on issue screen and in filters
718 * added back "fixed version" field on issue screen and in filters
712 * project settings screen split in 4 tabs
719 * project settings screen split in 4 tabs
713 * custom fields screen split in 3 tabs (one for each kind of custom field)
720 * custom fields screen split in 3 tabs (one for each kind of custom field)
714 * multiple issues pdf export now rendered as a table
721 * multiple issues pdf export now rendered as a table
715 * added a button on users/list to manually activate an account
722 * added a button on users/list to manually activate an account
716 * added a setting option to disable "password lost" functionality
723 * added a setting option to disable "password lost" functionality
717 * added a setting option to set max number of issues in csv/pdf exports
724 * added a setting option to set max number of issues in csv/pdf exports
718 * fixed: subprojects count is always 0 on projects list
725 * fixed: subprojects count is always 0 on projects list
719 * fixed: locked users are proposed when adding a member to a project
726 * fixed: locked users are proposed when adding a member to a project
720 * fixed: setting an issue status as default status leads to an sql error with SQLite
727 * fixed: setting an issue status as default status leads to an sql error with SQLite
721 * fixed: unable to delete an issue status even if it's not used yet
728 * fixed: unable to delete an issue status even if it's not used yet
722 * fixed: filters ignored when exporting a predefined query to csv/pdf
729 * fixed: filters ignored when exporting a predefined query to csv/pdf
723 * fixed: crash when french "issue_edit" email notification is sent
730 * fixed: crash when french "issue_edit" email notification is sent
724 * fixed: hide mail preference not saved (my/account)
731 * fixed: hide mail preference not saved (my/account)
725 * fixed: crash when a new user try to edit its "my page" layout
732 * fixed: crash when a new user try to edit its "my page" layout
726
733
727
734
728 == 2007-01-03 v0.4.1
735 == 2007-01-03 v0.4.1
729
736
730 * fixed: emails have no recipient when one of the project members has notifications disabled
737 * fixed: emails have no recipient when one of the project members has notifications disabled
731
738
732
739
733 == 2007-01-02 v0.4.0
740 == 2007-01-02 v0.4.0
734
741
735 * simple SVN browser added (just needs svn binaries in PATH)
742 * simple SVN browser added (just needs svn binaries in PATH)
736 * comments can now be added on news
743 * comments can now be added on news
737 * "my page" is now customizable
744 * "my page" is now customizable
738 * more powerfull and savable filters for issues lists
745 * more powerfull and savable filters for issues lists
739 * improved issues change history
746 * improved issues change history
740 * new functionality: move an issue to another project or tracker
747 * new functionality: move an issue to another project or tracker
741 * new functionality: add a note to an issue
748 * new functionality: add a note to an issue
742 * new report: project activity
749 * new report: project activity
743 * "start date" and "% done" fields added on issues
750 * "start date" and "% done" fields added on issues
744 * project calendar added
751 * project calendar added
745 * gantt chart added (exportable to pdf)
752 * gantt chart added (exportable to pdf)
746 * single/multiple issues pdf export added
753 * single/multiple issues pdf export added
747 * issues reports improvements
754 * issues reports improvements
748 * multiple file upload for issues, documents and files
755 * multiple file upload for issues, documents and files
749 * option to set maximum size of uploaded files
756 * option to set maximum size of uploaded files
750 * textile formating of issue and news descritions (RedCloth required)
757 * textile formating of issue and news descritions (RedCloth required)
751 * integration of DotClear jstoolbar for textile formatting
758 * integration of DotClear jstoolbar for textile formatting
752 * calendar date picker for date fields (LGPL DHTML Calendar http://sourceforge.net/projects/jscalendar)
759 * calendar date picker for date fields (LGPL DHTML Calendar http://sourceforge.net/projects/jscalendar)
753 * new filter in issues list: Author
760 * new filter in issues list: Author
754 * ajaxified paginators
761 * ajaxified paginators
755 * news rss feed added
762 * news rss feed added
756 * option to set number of results per page on issues list
763 * option to set number of results per page on issues list
757 * localized csv separator (comma/semicolon)
764 * localized csv separator (comma/semicolon)
758 * csv output encoded to ISO-8859-1
765 * csv output encoded to ISO-8859-1
759 * user custom field displayed on account/show
766 * user custom field displayed on account/show
760 * default configuration improved (default roles, trackers, status, permissions and workflows)
767 * default configuration improved (default roles, trackers, status, permissions and workflows)
761 * language for default configuration data can now be chosen when running 'load_default_data' task
768 * language for default configuration data can now be chosen when running 'load_default_data' task
762 * javascript added on custom field form to show/hide fields according to the format of custom field
769 * javascript added on custom field form to show/hide fields according to the format of custom field
763 * fixed: custom fields not in csv exports
770 * fixed: custom fields not in csv exports
764 * fixed: project settings now displayed according to user's permissions
771 * fixed: project settings now displayed according to user's permissions
765 * fixed: application error when no version is selected on projects/add_file
772 * fixed: application error when no version is selected on projects/add_file
766 * fixed: public actions not authorized for members of non public projects
773 * fixed: public actions not authorized for members of non public projects
767 * fixed: non public projects were shown on welcome screen even if current user is not a member
774 * fixed: non public projects were shown on welcome screen even if current user is not a member
768
775
769
776
770 == 2006-10-08 v0.3.0
777 == 2006-10-08 v0.3.0
771
778
772 * user authentication against multiple LDAP (optional)
779 * user authentication against multiple LDAP (optional)
773 * token based "lost password" functionality
780 * token based "lost password" functionality
774 * user self-registration functionality (optional)
781 * user self-registration functionality (optional)
775 * custom fields now available for issues, users and projects
782 * custom fields now available for issues, users and projects
776 * new custom field format "text" (displayed as a textarea field)
783 * new custom field format "text" (displayed as a textarea field)
777 * project & administration drop down menus in navigation bar for quicker access
784 * project & administration drop down menus in navigation bar for quicker access
778 * text formatting is preserved for long text fields (issues, projects and news descriptions)
785 * text formatting is preserved for long text fields (issues, projects and news descriptions)
779 * urls and emails are turned into clickable links in long text fields
786 * urls and emails are turned into clickable links in long text fields
780 * "due date" field added on issues
787 * "due date" field added on issues
781 * tracker selection filter added on change log
788 * tracker selection filter added on change log
782 * Localization plugin replaced with GLoc 1.1.0 (iconv required)
789 * Localization plugin replaced with GLoc 1.1.0 (iconv required)
783 * error messages internationalization
790 * error messages internationalization
784 * german translation added (thanks to Karim Trott)
791 * german translation added (thanks to Karim Trott)
785 * data locking for issues to prevent update conflicts (using ActiveRecord builtin optimistic locking)
792 * data locking for issues to prevent update conflicts (using ActiveRecord builtin optimistic locking)
786 * new filter in issues list: "Fixed version"
793 * new filter in issues list: "Fixed version"
787 * active filters are displayed with colored background on issues list
794 * active filters are displayed with colored background on issues list
788 * custom configuration is now defined in config/config_custom.rb
795 * custom configuration is now defined in config/config_custom.rb
789 * user object no more stored in session (only user_id)
796 * user object no more stored in session (only user_id)
790 * news summary field is no longer required
797 * news summary field is no longer required
791 * tables and forms redesign
798 * tables and forms redesign
792 * Fixed: boolean custom field not working
799 * Fixed: boolean custom field not working
793 * Fixed: error messages for custom fields are not displayed
800 * Fixed: error messages for custom fields are not displayed
794 * Fixed: invalid custom fields should have a red border
801 * Fixed: invalid custom fields should have a red border
795 * Fixed: custom fields values are not validated on issue update
802 * Fixed: custom fields values are not validated on issue update
796 * Fixed: unable to choose an empty value for 'List' custom fields
803 * Fixed: unable to choose an empty value for 'List' custom fields
797 * Fixed: no issue categories sorting
804 * Fixed: no issue categories sorting
798 * Fixed: incorrect versions sorting
805 * Fixed: incorrect versions sorting
799
806
800
807
801 == 2006-07-12 - v0.2.2
808 == 2006-07-12 - v0.2.2
802
809
803 * Fixed: bug in "issues list"
810 * Fixed: bug in "issues list"
804
811
805
812
806 == 2006-07-09 - v0.2.1
813 == 2006-07-09 - v0.2.1
807
814
808 * new databases supported: Oracle, PostgreSQL, SQL Server
815 * new databases supported: Oracle, PostgreSQL, SQL Server
809 * projects/subprojects hierarchy (1 level of subprojects only)
816 * projects/subprojects hierarchy (1 level of subprojects only)
810 * environment information display in admin/info
817 * environment information display in admin/info
811 * more filter options in issues list (rev6)
818 * more filter options in issues list (rev6)
812 * default language based on browser settings (Accept-Language HTTP header)
819 * default language based on browser settings (Accept-Language HTTP header)
813 * issues list exportable to CSV (rev6)
820 * issues list exportable to CSV (rev6)
814 * simple_format and auto_link on long text fields
821 * simple_format and auto_link on long text fields
815 * more data validations
822 * more data validations
816 * Fixed: error when all mail notifications are unchecked in admin/mail_options
823 * Fixed: error when all mail notifications are unchecked in admin/mail_options
817 * Fixed: all project news are displayed on project summary
824 * Fixed: all project news are displayed on project summary
818 * Fixed: Can't change user password in users/edit
825 * Fixed: Can't change user password in users/edit
819 * Fixed: Error on tables creation with PostgreSQL (rev5)
826 * Fixed: Error on tables creation with PostgreSQL (rev5)
820 * Fixed: SQL error in "issue reports" view with PostgreSQL (rev5)
827 * Fixed: SQL error in "issue reports" view with PostgreSQL (rev5)
821
828
822
829
823 == 2006-06-25 - v0.1.0
830 == 2006-06-25 - v0.1.0
824
831
825 * multiple users/multiple projects
832 * multiple users/multiple projects
826 * role based access control
833 * role based access control
827 * issue tracking system
834 * issue tracking system
828 * fully customizable workflow
835 * fully customizable workflow
829 * documents/files repository
836 * documents/files repository
830 * email notifications on issue creation and update
837 * email notifications on issue creation and update
831 * multilanguage support (except for error messages):english, french, spanish
838 * multilanguage support (except for error messages):english, french, spanish
832 * online manual in french (unfinished)
839 * online manual in french (unfinished)
@@ -1,700 +1,700
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
2
2
3 actionview_datehelper_select_day_prefix:
3 actionview_datehelper_select_day_prefix:
4 actionview_datehelper_select_month_names: Janvier,FΓ©vrier,Mars,Avril,Mai,Juin,Juillet,AoΓ»t,Septembre,Octobre,Novembre,DΓ©cembre
4 actionview_datehelper_select_month_names: Janvier,FΓ©vrier,Mars,Avril,Mai,Juin,Juillet,AoΓ»t,Septembre,Octobre,Novembre,DΓ©cembre
5 actionview_datehelper_select_month_names_abbr: Jan,FΓ©v,Mars,Avril,Mai,Juin,Juil,AoΓ»t,Sept,Oct,Nov,DΓ©c
5 actionview_datehelper_select_month_names_abbr: Jan,FΓ©v,Mars,Avril,Mai,Juin,Juil,AoΓ»t,Sept,Oct,Nov,DΓ©c
6 actionview_datehelper_select_month_prefix:
6 actionview_datehelper_select_month_prefix:
7 actionview_datehelper_select_year_prefix:
7 actionview_datehelper_select_year_prefix:
8 actionview_datehelper_time_in_words_day: 1 jour
8 actionview_datehelper_time_in_words_day: 1 jour
9 actionview_datehelper_time_in_words_day_plural: %d jours
9 actionview_datehelper_time_in_words_day_plural: %d jours
10 actionview_datehelper_time_in_words_hour_about: environ une heure
10 actionview_datehelper_time_in_words_hour_about: environ une heure
11 actionview_datehelper_time_in_words_hour_about_plural: environ %d heures
11 actionview_datehelper_time_in_words_hour_about_plural: environ %d heures
12 actionview_datehelper_time_in_words_hour_about_single: environ une heure
12 actionview_datehelper_time_in_words_hour_about_single: environ une heure
13 actionview_datehelper_time_in_words_minute: 1 minute
13 actionview_datehelper_time_in_words_minute: 1 minute
14 actionview_datehelper_time_in_words_minute_half: 30 secondes
14 actionview_datehelper_time_in_words_minute_half: 30 secondes
15 actionview_datehelper_time_in_words_minute_less_than: moins d'une minute
15 actionview_datehelper_time_in_words_minute_less_than: moins d'une minute
16 actionview_datehelper_time_in_words_minute_plural: %d minutes
16 actionview_datehelper_time_in_words_minute_plural: %d minutes
17 actionview_datehelper_time_in_words_minute_single: 1 minute
17 actionview_datehelper_time_in_words_minute_single: 1 minute
18 actionview_datehelper_time_in_words_second_less_than: moins d'une seconde
18 actionview_datehelper_time_in_words_second_less_than: moins d'une seconde
19 actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes
19 actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes
20 actionview_instancetag_blank_option: Choisir
20 actionview_instancetag_blank_option: Choisir
21
21
22 activerecord_error_inclusion: n'est pas inclus dans la liste
22 activerecord_error_inclusion: n'est pas inclus dans la liste
23 activerecord_error_exclusion: est reservΓ©
23 activerecord_error_exclusion: est reservΓ©
24 activerecord_error_invalid: est invalide
24 activerecord_error_invalid: est invalide
25 activerecord_error_confirmation: ne correspond pas Γ  la confirmation
25 activerecord_error_confirmation: ne correspond pas Γ  la confirmation
26 activerecord_error_accepted: doit Γͺtre acceptΓ©
26 activerecord_error_accepted: doit Γͺtre acceptΓ©
27 activerecord_error_empty: doit Γͺtre renseignΓ©
27 activerecord_error_empty: doit Γͺtre renseignΓ©
28 activerecord_error_blank: doit Γͺtre renseignΓ©
28 activerecord_error_blank: doit Γͺtre renseignΓ©
29 activerecord_error_too_long: est trop long
29 activerecord_error_too_long: est trop long
30 activerecord_error_too_short: est trop court
30 activerecord_error_too_short: est trop court
31 activerecord_error_wrong_length: n'est pas de la bonne longueur
31 activerecord_error_wrong_length: n'est pas de la bonne longueur
32 activerecord_error_taken: est dΓ©jΓ  utilisΓ©
32 activerecord_error_taken: est dΓ©jΓ  utilisΓ©
33 activerecord_error_not_a_number: n'est pas un nombre
33 activerecord_error_not_a_number: n'est pas un nombre
34 activerecord_error_not_a_date: n'est pas une date valide
34 activerecord_error_not_a_date: n'est pas une date valide
35 activerecord_error_greater_than_start_date: doit Γͺtre postΓ©rieur Γ  la date de dΓ©but
35 activerecord_error_greater_than_start_date: doit Γͺtre postΓ©rieur Γ  la date de dΓ©but
36 activerecord_error_not_same_project: n'appartient pas au mΓͺme projet
36 activerecord_error_not_same_project: n'appartient pas au mΓͺme projet
37 activerecord_error_circular_dependency: Cette relation crΓ©erait une dΓ©pendance circulaire
37 activerecord_error_circular_dependency: Cette relation crΓ©erait une dΓ©pendance circulaire
38
38
39 general_fmt_age: %d an
39 general_fmt_age: %d an
40 general_fmt_age_plural: %d ans
40 general_fmt_age_plural: %d ans
41 general_fmt_date: %%d/%%m/%%Y
41 general_fmt_date: %%d/%%m/%%Y
42 general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
42 general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
43 general_fmt_datetime_short: %%d/%%m %%H:%%M
43 general_fmt_datetime_short: %%d/%%m %%H:%%M
44 general_fmt_time: %%H:%%M
44 general_fmt_time: %%H:%%M
45 general_text_No: 'Non'
45 general_text_No: 'Non'
46 general_text_Yes: 'Oui'
46 general_text_Yes: 'Oui'
47 general_text_no: 'non'
47 general_text_no: 'non'
48 general_text_yes: 'oui'
48 general_text_yes: 'oui'
49 general_lang_name: 'FranΓ§ais'
49 general_lang_name: 'FranΓ§ais'
50 general_csv_separator: ';'
50 general_csv_separator: ';'
51 general_csv_decimal_separator: ','
51 general_csv_decimal_separator: ','
52 general_csv_encoding: ISO-8859-1
52 general_csv_encoding: ISO-8859-1
53 general_pdf_encoding: ISO-8859-1
53 general_pdf_encoding: ISO-8859-1
54 general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche
54 general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche
55 general_first_day_of_week: '1'
55 general_first_day_of_week: '1'
56
56
57 notice_account_updated: Le compte a été mis à jour avec succès.
57 notice_account_updated: Le compte a été mis à jour avec succès.
58 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
58 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
59 notice_account_password_updated: Mot de passe mis à jour avec succès.
59 notice_account_password_updated: Mot de passe mis à jour avec succès.
60 notice_account_wrong_password: Mot de passe incorrect
60 notice_account_wrong_password: Mot de passe incorrect
61 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
61 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ©.
62 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
62 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
63 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
63 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
64 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
64 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
65 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
65 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
66 notice_successful_create: Création effectuée avec succès.
66 notice_successful_create: Création effectuée avec succès.
67 notice_successful_update: Mise à jour effectuée avec succès.
67 notice_successful_update: Mise à jour effectuée avec succès.
68 notice_successful_delete: Suppression effectuée avec succès.
68 notice_successful_delete: Suppression effectuée avec succès.
69 notice_successful_connection: Connection rΓ©ussie.
69 notice_successful_connection: Connection rΓ©ussie.
70 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
70 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
71 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
71 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
72 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ©s Γ  accΓ©der Γ  cette page."
72 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ©s Γ  accΓ©der Γ  cette page."
73 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %s"
73 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %s"
74 notice_email_error: "Erreur lors de l'envoi de l'email (%s)"
74 notice_email_error: "Erreur lors de l'envoi de l'email (%s)"
75 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
75 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux RSS a été réinitialisée."
76 notice_failed_to_save_issues: "%d demande(s) sur les %d sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour: %s."
76 notice_failed_to_save_issues: "%d demande(s) sur les %d sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour: %s."
77 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
77 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
78 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
78 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
79 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
79 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
80 notice_unable_delete_version: Impossible de supprimer cette version.
80 notice_unable_delete_version: Impossible de supprimer cette version.
81
81
82 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage: %s"
82 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage: %s"
83 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
83 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
84 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt: %s"
84 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt: %s"
85 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
85 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
86 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
86 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
87
87
88 mail_subject_lost_password: Votre mot de passe %s
88 mail_subject_lost_password: Votre mot de passe %s
89 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:'
89 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant:'
90 mail_subject_register: Activation de votre compte %s
90 mail_subject_register: Activation de votre compte %s
91 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant:'
91 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant:'
92 mail_body_account_information_external: Vous pouvez utiliser votre compte "%s" pour vous connecter.
92 mail_body_account_information_external: Vous pouvez utiliser votre compte "%s" pour vous connecter.
93 mail_body_account_information: Paramètres de connexion de votre compte
93 mail_body_account_information: Paramètres de connexion de votre compte
94 mail_subject_account_activation_request: "Demande d'activation d'un compte %s"
94 mail_subject_account_activation_request: "Demande d'activation d'un compte %s"
95 mail_body_account_activation_request: "Un nouvel utilisateur (%s) s'est inscrit. Son compte nΓ©cessite votre approbation:"
95 mail_body_account_activation_request: "Un nouvel utilisateur (%s) s'est inscrit. Son compte nΓ©cessite votre approbation:"
96 mail_subject_reminder: "%d demande(s) arrivent Γ  Γ©chΓ©ance"
96 mail_subject_reminder: "%d demande(s) arrivent Γ  Γ©chΓ©ance"
97 mail_body_reminder: "%d demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %d prochains jours:"
97 mail_body_reminder: "%d demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %d prochains jours:"
98
98
99 gui_validation_error: 1 erreur
99 gui_validation_error: 1 erreur
100 gui_validation_error_plural: %d erreurs
100 gui_validation_error_plural: %d erreurs
101
101
102 field_name: Nom
102 field_name: Nom
103 field_description: Description
103 field_description: Description
104 field_summary: RΓ©sumΓ©
104 field_summary: RΓ©sumΓ©
105 field_is_required: Obligatoire
105 field_is_required: Obligatoire
106 field_firstname: PrΓ©nom
106 field_firstname: PrΓ©nom
107 field_lastname: Nom
107 field_lastname: Nom
108 field_mail: Email
108 field_mail: Email
109 field_filename: Fichier
109 field_filename: Fichier
110 field_filesize: Taille
110 field_filesize: Taille
111 field_downloads: TΓ©lΓ©chargements
111 field_downloads: TΓ©lΓ©chargements
112 field_author: Auteur
112 field_author: Auteur
113 field_created_on: Créé
113 field_created_on: Créé
114 field_updated_on: Mis Γ  jour
114 field_updated_on: Mis Γ  jour
115 field_field_format: Format
115 field_field_format: Format
116 field_is_for_all: Pour tous les projets
116 field_is_for_all: Pour tous les projets
117 field_possible_values: Valeurs possibles
117 field_possible_values: Valeurs possibles
118 field_regexp: Expression régulière
118 field_regexp: Expression régulière
119 field_min_length: Longueur minimum
119 field_min_length: Longueur minimum
120 field_max_length: Longueur maximum
120 field_max_length: Longueur maximum
121 field_value: Valeur
121 field_value: Valeur
122 field_category: CatΓ©gorie
122 field_category: CatΓ©gorie
123 field_title: Titre
123 field_title: Titre
124 field_project: Projet
124 field_project: Projet
125 field_issue: Demande
125 field_issue: Demande
126 field_status: Statut
126 field_status: Statut
127 field_notes: Notes
127 field_notes: Notes
128 field_is_closed: Demande fermΓ©e
128 field_is_closed: Demande fermΓ©e
129 field_is_default: Valeur par dΓ©faut
129 field_is_default: Valeur par dΓ©faut
130 field_tracker: Tracker
130 field_tracker: Tracker
131 field_subject: Sujet
131 field_subject: Sujet
132 field_due_date: Date d'Γ©chΓ©ance
132 field_due_date: Date d'Γ©chΓ©ance
133 field_assigned_to: AssignΓ© Γ 
133 field_assigned_to: AssignΓ© Γ 
134 field_priority: PrioritΓ©
134 field_priority: PrioritΓ©
135 field_fixed_version: Version cible
135 field_fixed_version: Version cible
136 field_user: Utilisateur
136 field_user: Utilisateur
137 field_role: RΓ΄le
137 field_role: RΓ΄le
138 field_homepage: Site web
138 field_homepage: Site web
139 field_is_public: Public
139 field_is_public: Public
140 field_parent: Sous-projet de
140 field_parent: Sous-projet de
141 field_is_in_chlog: Demandes affichΓ©es dans l'historique
141 field_is_in_chlog: Demandes affichΓ©es dans l'historique
142 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
142 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
143 field_login: Identifiant
143 field_login: Identifiant
144 field_mail_notification: Notifications par mail
144 field_mail_notification: Notifications par mail
145 field_admin: Administrateur
145 field_admin: Administrateur
146 field_last_login_on: Dernière connexion
146 field_last_login_on: Dernière connexion
147 field_language: Langue
147 field_language: Langue
148 field_effective_date: Date
148 field_effective_date: Date
149 field_password: Mot de passe
149 field_password: Mot de passe
150 field_new_password: Nouveau mot de passe
150 field_new_password: Nouveau mot de passe
151 field_password_confirmation: Confirmation
151 field_password_confirmation: Confirmation
152 field_version: Version
152 field_version: Version
153 field_type: Type
153 field_type: Type
154 field_host: HΓ΄te
154 field_host: HΓ΄te
155 field_port: Port
155 field_port: Port
156 field_account: Compte
156 field_account: Compte
157 field_base_dn: Base DN
157 field_base_dn: Base DN
158 field_attr_login: Attribut Identifiant
158 field_attr_login: Attribut Identifiant
159 field_attr_firstname: Attribut PrΓ©nom
159 field_attr_firstname: Attribut PrΓ©nom
160 field_attr_lastname: Attribut Nom
160 field_attr_lastname: Attribut Nom
161 field_attr_mail: Attribut Email
161 field_attr_mail: Attribut Email
162 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
162 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
163 field_start_date: DΓ©but
163 field_start_date: DΓ©but
164 field_done_ratio: %% RΓ©alisΓ©
164 field_done_ratio: %% RΓ©alisΓ©
165 field_auth_source: Mode d'authentification
165 field_auth_source: Mode d'authentification
166 field_hide_mail: Cacher mon adresse mail
166 field_hide_mail: Cacher mon adresse mail
167 field_comments: Commentaire
167 field_comments: Commentaire
168 field_url: URL
168 field_url: URL
169 field_start_page: Page de dΓ©marrage
169 field_start_page: Page de dΓ©marrage
170 field_subproject: Sous-projet
170 field_subproject: Sous-projet
171 field_hours: Heures
171 field_hours: Heures
172 field_activity: ActivitΓ©
172 field_activity: ActivitΓ©
173 field_spent_on: Date
173 field_spent_on: Date
174 field_identifier: Identifiant
174 field_identifier: Identifiant
175 field_is_filter: UtilisΓ© comme filtre
175 field_is_filter: UtilisΓ© comme filtre
176 field_issue_to_id: Demande liΓ©e
176 field_issue_to_id: Demande liΓ©e
177 field_delay: Retard
177 field_delay: Retard
178 field_assignable: Demandes assignables Γ  ce rΓ΄le
178 field_assignable: Demandes assignables Γ  ce rΓ΄le
179 field_redirect_existing_links: Rediriger les liens existants
179 field_redirect_existing_links: Rediriger les liens existants
180 field_estimated_hours: Temps estimΓ©
180 field_estimated_hours: Temps estimΓ©
181 field_column_names: Colonnes
181 field_column_names: Colonnes
182 field_time_zone: Fuseau horaire
182 field_time_zone: Fuseau horaire
183 field_searchable: UtilisΓ© pour les recherches
183 field_searchable: UtilisΓ© pour les recherches
184 field_default_value: Valeur par dΓ©faut
184 field_default_value: Valeur par dΓ©faut
185 field_comments_sorting: Afficher les commentaires
185 field_comments_sorting: Afficher les commentaires
186 field_parent_title: Page parent
186 field_parent_title: Page parent
187
187
188 setting_app_title: Titre de l'application
188 setting_app_title: Titre de l'application
189 setting_app_subtitle: Sous-titre de l'application
189 setting_app_subtitle: Sous-titre de l'application
190 setting_welcome_text: Texte d'accueil
190 setting_welcome_text: Texte d'accueil
191 setting_default_language: Langue par dΓ©faut
191 setting_default_language: Langue par dΓ©faut
192 setting_login_required: Authentification obligatoire
192 setting_login_required: Authentification obligatoire
193 setting_self_registration: Inscription des nouveaux utilisateurs
193 setting_self_registration: Inscription des nouveaux utilisateurs
194 setting_attachment_max_size: Taille max des fichiers
194 setting_attachment_max_size: Taille max des fichiers
195 setting_issues_export_limit: Limite export demandes
195 setting_issues_export_limit: Limite export demandes
196 setting_mail_from: Adresse d'Γ©mission
196 setting_mail_from: Adresse d'Γ©mission
197 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
197 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
198 setting_plain_text_mail: Mail texte brut (non HTML)
198 setting_plain_text_mail: Mail texte brut (non HTML)
199 setting_host_name: Nom d'hΓ΄te et chemin
199 setting_host_name: Nom d'hΓ΄te et chemin
200 setting_text_formatting: Formatage du texte
200 setting_text_formatting: Formatage du texte
201 setting_wiki_compression: Compression historique wiki
201 setting_wiki_compression: Compression historique wiki
202 setting_feeds_limit: Limite du contenu des flux RSS
202 setting_feeds_limit: Limite du contenu des flux RSS
203 setting_default_projects_public: DΓ©finir les nouveaux projects comme publics par dΓ©faut
203 setting_default_projects_public: DΓ©finir les nouveaux projects comme publics par dΓ©faut
204 setting_autofetch_changesets: RΓ©cupΓ©ration auto. des commits
204 setting_autofetch_changesets: RΓ©cupΓ©ration auto. des commits
205 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
205 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
206 setting_commit_ref_keywords: Mot-clΓ©s de rΓ©fΓ©rencement
206 setting_commit_ref_keywords: Mot-clΓ©s de rΓ©fΓ©rencement
207 setting_commit_fix_keywords: Mot-clΓ©s de rΓ©solution
207 setting_commit_fix_keywords: Mot-clΓ©s de rΓ©solution
208 setting_autologin: Autologin
208 setting_autologin: Autologin
209 setting_date_format: Format de date
209 setting_date_format: Format de date
210 setting_time_format: Format d'heure
210 setting_time_format: Format d'heure
211 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
211 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
212 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
212 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
213 setting_repositories_encodings: Encodages des dΓ©pΓ΄ts
213 setting_repositories_encodings: Encodages des dΓ©pΓ΄ts
214 setting_commit_logs_encoding: Encodage des messages de commit
214 setting_commit_logs_encoding: Encodage des messages de commit
215 setting_emails_footer: Pied-de-page des emails
215 setting_emails_footer: Pied-de-page des emails
216 setting_protocol: Protocole
216 setting_protocol: Protocole
217 setting_per_page_options: Options d'objets affichΓ©s par page
217 setting_per_page_options: Options d'objets affichΓ©s par page
218 setting_user_format: Format d'affichage des utilisateurs
218 setting_user_format: Format d'affichage des utilisateurs
219 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
219 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
220 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
220 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
221 setting_enabled_scm: SCM activΓ©s
221 setting_enabled_scm: SCM activΓ©s
222 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
222 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
223 setting_mail_handler_api_key: ClΓ© de protection de l'API
223 setting_mail_handler_api_key: ClΓ© de protection de l'API
224 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
224 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
225 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
225 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
226 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
226 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
227
227
228 permission_edit_project: Modifier le projet
228 permission_edit_project: Modifier le projet
229 permission_select_project_modules: Choisir les modules
229 permission_select_project_modules: Choisir les modules
230 permission_manage_members: GΓ©rer les members
230 permission_manage_members: GΓ©rer les members
231 permission_manage_versions: GΓ©rer les versions
231 permission_manage_versions: GΓ©rer les versions
232 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
232 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
233 permission_add_issues: CrΓ©er des demandes
233 permission_add_issues: CrΓ©er des demandes
234 permission_edit_issues: Modifier les demandes
234 permission_edit_issues: Modifier les demandes
235 permission_manage_issue_relations: GΓ©rer les relations
235 permission_manage_issue_relations: GΓ©rer les relations
236 permission_add_issue_notes: Ajouter des notes
236 permission_add_issue_notes: Ajouter des notes
237 permission_edit_issue_notes: Modifier les notes
237 permission_edit_issue_notes: Modifier les notes
238 permission_edit_own_issue_notes: Modifier ses propres notes
238 permission_edit_own_issue_notes: Modifier ses propres notes
239 permission_move_issues: DΓ©placer les demandes
239 permission_move_issues: DΓ©placer les demandes
240 permission_delete_issues: Supprimer les demandes
240 permission_delete_issues: Supprimer les demandes
241 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
241 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
242 permission_save_queries: Sauvegarder les requΓͺtes
242 permission_save_queries: Sauvegarder les requΓͺtes
243 permission_view_gantt: Voir le gantt
243 permission_view_gantt: Voir le gantt
244 permission_view_calendar: Voir le calendrier
244 permission_view_calendar: Voir le calendrier
245 permission_view_issue_watchers: Voir la liste des observateurs
245 permission_view_issue_watchers: Voir la liste des observateurs
246 permission_add_issue_watchers: Ajouter des observateurs
246 permission_add_issue_watchers: Ajouter des observateurs
247 permission_log_time: Saisir le temps passΓ©
247 permission_log_time: Saisir le temps passΓ©
248 permission_view_time_entries: Voir le temps passΓ©
248 permission_view_time_entries: Voir le temps passΓ©
249 permission_edit_time_entries: Modifier les temps passΓ©s
249 permission_edit_time_entries: Modifier les temps passΓ©s
250 permission_edit_own_time_entries: Modifier son propre temps passΓ©
250 permission_edit_own_time_entries: Modifier son propre temps passΓ©
251 permission_manage_news: GΓ©rer les annonces
251 permission_manage_news: GΓ©rer les annonces
252 permission_comment_news: Commenter les annonces
252 permission_comment_news: Commenter les annonces
253 permission_manage_documents: GΓ©rer les documents
253 permission_manage_documents: GΓ©rer les documents
254 permission_view_documents: Voir les documents
254 permission_view_documents: Voir les documents
255 permission_manage_files: GΓ©rer les fichiers
255 permission_manage_files: GΓ©rer les fichiers
256 permission_view_files: Voir les fichiers
256 permission_view_files: Voir les fichiers
257 permission_manage_wiki: GΓ©rer le wiki
257 permission_manage_wiki: GΓ©rer le wiki
258 permission_rename_wiki_pages: Renommer les pages
258 permission_rename_wiki_pages: Renommer les pages
259 permission_delete_wiki_pages: Supprimer les pages
259 permission_delete_wiki_pages: Supprimer les pages
260 permission_view_wiki_pages: Voir le wiki
260 permission_view_wiki_pages: Voir le wiki
261 permission_view_wiki_edits: "Voir l'historique des modifications"
261 permission_view_wiki_edits: "Voir l'historique des modifications"
262 permission_edit_wiki_pages: Modifier les pages
262 permission_edit_wiki_pages: Modifier les pages
263 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
263 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
264 permission_protect_wiki_pages: ProtΓ©ger les pages
264 permission_protect_wiki_pages: ProtΓ©ger les pages
265 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
265 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
266 permission_browse_repository: Parcourir les sources
266 permission_browse_repository: Parcourir les sources
267 permission_view_changesets: Voir les rΓ©visions
267 permission_view_changesets: Voir les rΓ©visions
268 permission_commit_access: Droit de commit
268 permission_commit_access: Droit de commit
269 permission_manage_boards: GΓ©rer les forums
269 permission_manage_boards: GΓ©rer les forums
270 permission_view_messages: Voir les messages
270 permission_view_messages: Voir les messages
271 permission_add_messages: Poster un message
271 permission_add_messages: Poster un message
272 permission_edit_messages: Modifier les messages
272 permission_edit_messages: Modifier les messages
273 permission_edit_own_messages: Modifier ses propres messages
273 permission_edit_own_messages: Modifier ses propres messages
274 permission_delete_messages: Supprimer les messages
274 permission_delete_messages: Supprimer les messages
275 permission_delete_own_messages: Supprimer ses propres messages
275 permission_delete_own_messages: Supprimer ses propres messages
276
276
277 project_module_issue_tracking: Suivi des demandes
277 project_module_issue_tracking: Suivi des demandes
278 project_module_time_tracking: Suivi du temps passΓ©
278 project_module_time_tracking: Suivi du temps passΓ©
279 project_module_news: Publication d'annonces
279 project_module_news: Publication d'annonces
280 project_module_documents: Publication de documents
280 project_module_documents: Publication de documents
281 project_module_files: Publication de fichiers
281 project_module_files: Publication de fichiers
282 project_module_wiki: Wiki
282 project_module_wiki: Wiki
283 project_module_repository: DΓ©pΓ΄t de sources
283 project_module_repository: DΓ©pΓ΄t de sources
284 project_module_boards: Forums de discussion
284 project_module_boards: Forums de discussion
285
285
286 label_user: Utilisateur
286 label_user: Utilisateur
287 label_user_plural: Utilisateurs
287 label_user_plural: Utilisateurs
288 label_user_new: Nouvel utilisateur
288 label_user_new: Nouvel utilisateur
289 label_project: Projet
289 label_project: Projet
290 label_project_new: Nouveau projet
290 label_project_new: Nouveau projet
291 label_project_plural: Projets
291 label_project_plural: Projets
292 label_project_all: Tous les projets
292 label_project_all: Tous les projets
293 label_project_latest: Derniers projets
293 label_project_latest: Derniers projets
294 label_issue: Demande
294 label_issue: Demande
295 label_issue_new: Nouvelle demande
295 label_issue_new: Nouvelle demande
296 label_issue_plural: Demandes
296 label_issue_plural: Demandes
297 label_issue_view_all: Voir toutes les demandes
297 label_issue_view_all: Voir toutes les demandes
298 label_issue_added: Demande ajoutΓ©e
298 label_issue_added: Demande ajoutΓ©e
299 label_issue_updated: Demande mise Γ  jour
299 label_issue_updated: Demande mise Γ  jour
300 label_issues_by: Demandes par %s
300 label_issues_by: Demandes par %s
301 label_document: Document
301 label_document: Document
302 label_document_new: Nouveau document
302 label_document_new: Nouveau document
303 label_document_plural: Documents
303 label_document_plural: Documents
304 label_document_added: Document ajoutΓ©
304 label_document_added: Document ajoutΓ©
305 label_role: RΓ΄le
305 label_role: RΓ΄le
306 label_role_plural: RΓ΄les
306 label_role_plural: RΓ΄les
307 label_role_new: Nouveau rΓ΄le
307 label_role_new: Nouveau rΓ΄le
308 label_role_and_permissions: RΓ΄les et permissions
308 label_role_and_permissions: RΓ΄les et permissions
309 label_member: Membre
309 label_member: Membre
310 label_member_new: Nouveau membre
310 label_member_new: Nouveau membre
311 label_member_plural: Membres
311 label_member_plural: Membres
312 label_tracker: Tracker
312 label_tracker: Tracker
313 label_tracker_plural: Trackers
313 label_tracker_plural: Trackers
314 label_tracker_new: Nouveau tracker
314 label_tracker_new: Nouveau tracker
315 label_workflow: Workflow
315 label_workflow: Workflow
316 label_issue_status: Statut de demandes
316 label_issue_status: Statut de demandes
317 label_issue_status_plural: Statuts de demandes
317 label_issue_status_plural: Statuts de demandes
318 label_issue_status_new: Nouveau statut
318 label_issue_status_new: Nouveau statut
319 label_issue_category: CatΓ©gorie de demandes
319 label_issue_category: CatΓ©gorie de demandes
320 label_issue_category_plural: CatΓ©gories de demandes
320 label_issue_category_plural: CatΓ©gories de demandes
321 label_issue_category_new: Nouvelle catΓ©gorie
321 label_issue_category_new: Nouvelle catΓ©gorie
322 label_custom_field: Champ personnalisΓ©
322 label_custom_field: Champ personnalisΓ©
323 label_custom_field_plural: Champs personnalisΓ©s
323 label_custom_field_plural: Champs personnalisΓ©s
324 label_custom_field_new: Nouveau champ personnalisΓ©
324 label_custom_field_new: Nouveau champ personnalisΓ©
325 label_enumerations: Listes de valeurs
325 label_enumerations: Listes de valeurs
326 label_enumeration_new: Nouvelle valeur
326 label_enumeration_new: Nouvelle valeur
327 label_information: Information
327 label_information: Information
328 label_information_plural: Informations
328 label_information_plural: Informations
329 label_please_login: Identification
329 label_please_login: Identification
330 label_register: S'enregistrer
330 label_register: S'enregistrer
331 label_password_lost: Mot de passe perdu
331 label_password_lost: Mot de passe perdu
332 label_home: Accueil
332 label_home: Accueil
333 label_my_page: Ma page
333 label_my_page: Ma page
334 label_my_account: Mon compte
334 label_my_account: Mon compte
335 label_my_projects: Mes projets
335 label_my_projects: Mes projets
336 label_administration: Administration
336 label_administration: Administration
337 label_login: Connexion
337 label_login: Connexion
338 label_logout: DΓ©connexion
338 label_logout: DΓ©connexion
339 label_help: Aide
339 label_help: Aide
340 label_reported_issues: Demandes soumises
340 label_reported_issues: Demandes soumises
341 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
341 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
342 label_last_login: Dernière connexion
342 label_last_login: Dernière connexion
343 label_last_updates: Dernière mise à jour
343 label_last_updates: Dernière mise à jour
344 label_last_updates_plural: %d dernières mises à jour
344 label_last_updates_plural: %d dernières mises à jour
345 label_registered_on: Inscrit le
345 label_registered_on: Inscrit le
346 label_activity: ActivitΓ©
346 label_activity: ActivitΓ©
347 label_overall_activity: ActivitΓ© globale
347 label_overall_activity: ActivitΓ© globale
348 label_user_activity: "ActivitΓ© de %s"
348 label_user_activity: "ActivitΓ© de %s"
349 label_new: Nouveau
349 label_new: Nouveau
350 label_logged_as: ConnectΓ© en tant que
350 label_logged_as: ConnectΓ© en tant que
351 label_environment: Environnement
351 label_environment: Environnement
352 label_authentication: Authentification
352 label_authentication: Authentification
353 label_auth_source: Mode d'authentification
353 label_auth_source: Mode d'authentification
354 label_auth_source_new: Nouveau mode d'authentification
354 label_auth_source_new: Nouveau mode d'authentification
355 label_auth_source_plural: Modes d'authentification
355 label_auth_source_plural: Modes d'authentification
356 label_subproject_plural: Sous-projets
356 label_subproject_plural: Sous-projets
357 label_and_its_subprojects: %s et ses sous-projets
357 label_and_its_subprojects: %s et ses sous-projets
358 label_min_max_length: Longueurs mini - maxi
358 label_min_max_length: Longueurs mini - maxi
359 label_list: Liste
359 label_list: Liste
360 label_date: Date
360 label_date: Date
361 label_integer: Entier
361 label_integer: Entier
362 label_float: Nombre dΓ©cimal
362 label_float: Nombre dΓ©cimal
363 label_boolean: BoolΓ©en
363 label_boolean: BoolΓ©en
364 label_string: Texte
364 label_string: Texte
365 label_text: Texte long
365 label_text: Texte long
366 label_attribute: Attribut
366 label_attribute: Attribut
367 label_attribute_plural: Attributs
367 label_attribute_plural: Attributs
368 label_download: %d TΓ©lΓ©chargement
368 label_download: %d TΓ©lΓ©chargement
369 label_download_plural: %d TΓ©lΓ©chargements
369 label_download_plural: %d TΓ©lΓ©chargements
370 label_no_data: Aucune donnΓ©e Γ  afficher
370 label_no_data: Aucune donnΓ©e Γ  afficher
371 label_change_status: Changer le statut
371 label_change_status: Changer le statut
372 label_history: Historique
372 label_history: Historique
373 label_attachment: Fichier
373 label_attachment: Fichier
374 label_attachment_new: Nouveau fichier
374 label_attachment_new: Nouveau fichier
375 label_attachment_delete: Supprimer le fichier
375 label_attachment_delete: Supprimer le fichier
376 label_attachment_plural: Fichiers
376 label_attachment_plural: Fichiers
377 label_file_added: Fichier ajoutΓ©
377 label_file_added: Fichier ajoutΓ©
378 label_report: Rapport
378 label_report: Rapport
379 label_report_plural: Rapports
379 label_report_plural: Rapports
380 label_news: Annonce
380 label_news: Annonce
381 label_news_new: Nouvelle annonce
381 label_news_new: Nouvelle annonce
382 label_news_plural: Annonces
382 label_news_plural: Annonces
383 label_news_latest: Dernières annonces
383 label_news_latest: Dernières annonces
384 label_news_view_all: Voir toutes les annonces
384 label_news_view_all: Voir toutes les annonces
385 label_news_added: Annonce ajoutΓ©e
385 label_news_added: Annonce ajoutΓ©e
386 label_change_log: Historique
386 label_change_log: Historique
387 label_settings: Configuration
387 label_settings: Configuration
388 label_overview: AperΓ§u
388 label_overview: AperΓ§u
389 label_version: Version
389 label_version: Version
390 label_version_new: Nouvelle version
390 label_version_new: Nouvelle version
391 label_version_plural: Versions
391 label_version_plural: Versions
392 label_confirmation: Confirmation
392 label_confirmation: Confirmation
393 label_export_to: 'Formats disponibles:'
393 label_export_to: 'Formats disponibles:'
394 label_read: Lire...
394 label_read: Lire...
395 label_public_projects: Projets publics
395 label_public_projects: Projets publics
396 label_open_issues: ouvert
396 label_open_issues: ouvert
397 label_open_issues_plural: ouverts
397 label_open_issues_plural: ouverts
398 label_closed_issues: fermΓ©
398 label_closed_issues: fermΓ©
399 label_closed_issues_plural: fermΓ©s
399 label_closed_issues_plural: fermΓ©s
400 label_total: Total
400 label_total: Total
401 label_permissions: Permissions
401 label_permissions: Permissions
402 label_current_status: Statut actuel
402 label_current_status: Statut actuel
403 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
403 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
404 label_all: tous
404 label_all: tous
405 label_none: aucun
405 label_none: aucun
406 label_nobody: personne
406 label_nobody: personne
407 label_next: Suivant
407 label_next: Suivant
408 label_previous: PrΓ©cΓ©dent
408 label_previous: PrΓ©cΓ©dent
409 label_used_by: UtilisΓ© par
409 label_used_by: UtilisΓ© par
410 label_details: DΓ©tails
410 label_details: DΓ©tails
411 label_add_note: Ajouter une note
411 label_add_note: Ajouter une note
412 label_per_page: Par page
412 label_per_page: Par page
413 label_calendar: Calendrier
413 label_calendar: Calendrier
414 label_months_from: mois depuis
414 label_months_from: mois depuis
415 label_gantt: Gantt
415 label_gantt: Gantt
416 label_internal: Interne
416 label_internal: Interne
417 label_last_changes: %d derniers changements
417 label_last_changes: %d derniers changements
418 label_change_view_all: Voir tous les changements
418 label_change_view_all: Voir tous les changements
419 label_personalize_page: Personnaliser cette page
419 label_personalize_page: Personnaliser cette page
420 label_comment: Commentaire
420 label_comment: Commentaire
421 label_comment_plural: Commentaires
421 label_comment_plural: Commentaires
422 label_comment_add: Ajouter un commentaire
422 label_comment_add: Ajouter un commentaire
423 label_comment_added: Commentaire ajoutΓ©
423 label_comment_added: Commentaire ajoutΓ©
424 label_comment_delete: Supprimer les commentaires
424 label_comment_delete: Supprimer les commentaires
425 label_query: Rapport personnalisΓ©
425 label_query: Rapport personnalisΓ©
426 label_query_plural: Rapports personnalisΓ©s
426 label_query_plural: Rapports personnalisΓ©s
427 label_query_new: Nouveau rapport
427 label_query_new: Nouveau rapport
428 label_filter_add: Ajouter le filtre
428 label_filter_add: Ajouter le filtre
429 label_filter_plural: Filtres
429 label_filter_plural: Filtres
430 label_equals: Γ©gal
430 label_equals: Γ©gal
431 label_not_equals: diffΓ©rent
431 label_not_equals: diffΓ©rent
432 label_in_less_than: dans moins de
432 label_in_less_than: dans moins de
433 label_in_more_than: dans plus de
433 label_in_more_than: dans plus de
434 label_in: dans
434 label_in: dans
435 label_today: aujourd'hui
435 label_today: aujourd'hui
436 label_all_time: toute la pΓ©riode
436 label_all_time: toute la pΓ©riode
437 label_yesterday: hier
437 label_yesterday: hier
438 label_this_week: cette semaine
438 label_this_week: cette semaine
439 label_last_week: la semaine dernière
439 label_last_week: la semaine dernière
440 label_last_n_days: les %d derniers jours
440 label_last_n_days: les %d derniers jours
441 label_this_month: ce mois-ci
441 label_this_month: ce mois-ci
442 label_last_month: le mois dernier
442 label_last_month: le mois dernier
443 label_this_year: cette annΓ©e
443 label_this_year: cette annΓ©e
444 label_date_range: PΓ©riode
444 label_date_range: PΓ©riode
445 label_less_than_ago: il y a moins de
445 label_less_than_ago: il y a moins de
446 label_more_than_ago: il y a plus de
446 label_more_than_ago: il y a plus de
447 label_ago: il y a
447 label_ago: il y a
448 label_contains: contient
448 label_contains: contient
449 label_not_contains: ne contient pas
449 label_not_contains: ne contient pas
450 label_day_plural: jours
450 label_day_plural: jours
451 label_repository: DΓ©pΓ΄t
451 label_repository: DΓ©pΓ΄t
452 label_repository_plural: DΓ©pΓ΄ts
452 label_repository_plural: DΓ©pΓ΄ts
453 label_browse: Parcourir
453 label_browse: Parcourir
454 label_modification: %d modification
454 label_modification: %d modification
455 label_modification_plural: %d modifications
455 label_modification_plural: %d modifications
456 label_revision: RΓ©vision
456 label_revision: RΓ©vision
457 label_revision_plural: RΓ©visions
457 label_revision_plural: RΓ©visions
458 label_associated_revisions: RΓ©visions associΓ©es
458 label_associated_revisions: RΓ©visions associΓ©es
459 label_added: ajoutΓ©
459 label_added: ajoutΓ©
460 label_modified: modifiΓ©
460 label_modified: modifiΓ©
461 label_copied: copiΓ©
461 label_copied: copiΓ©
462 label_renamed: renommΓ©
462 label_renamed: renommΓ©
463 label_deleted: supprimΓ©
463 label_deleted: supprimΓ©
464 label_latest_revision: Dernière révision
464 label_latest_revision: Dernière révision
465 label_latest_revision_plural: Dernières révisions
465 label_latest_revision_plural: Dernières révisions
466 label_view_revisions: Voir les rΓ©visions
466 label_view_revisions: Voir les rΓ©visions
467 label_max_size: Taille maximale
467 label_max_size: Taille maximale
468 label_on: sur
468 label_on: sur
469 label_sort_highest: Remonter en premier
469 label_sort_highest: Remonter en premier
470 label_sort_higher: Remonter
470 label_sort_higher: Remonter
471 label_sort_lower: Descendre
471 label_sort_lower: Descendre
472 label_sort_lowest: Descendre en dernier
472 label_sort_lowest: Descendre en dernier
473 label_roadmap: Roadmap
473 label_roadmap: Roadmap
474 label_roadmap_due_in: EchΓ©ance dans %s
474 label_roadmap_due_in: EchΓ©ance dans %s
475 label_roadmap_overdue: En retard de %s
475 label_roadmap_overdue: En retard de %s
476 label_roadmap_no_issues: Aucune demande pour cette version
476 label_roadmap_no_issues: Aucune demande pour cette version
477 label_search: Recherche
477 label_search: Recherche
478 label_result_plural: RΓ©sultats
478 label_result_plural: RΓ©sultats
479 label_all_words: Tous les mots
479 label_all_words: Tous les mots
480 label_wiki: Wiki
480 label_wiki: Wiki
481 label_wiki_edit: RΓ©vision wiki
481 label_wiki_edit: RΓ©vision wiki
482 label_wiki_edit_plural: RΓ©visions wiki
482 label_wiki_edit_plural: RΓ©visions wiki
483 label_wiki_page: Page wiki
483 label_wiki_page: Page wiki
484 label_wiki_page_plural: Pages wiki
484 label_wiki_page_plural: Pages wiki
485 label_index_by_title: Index par titre
485 label_index_by_title: Index par titre
486 label_index_by_date: Index par date
486 label_index_by_date: Index par date
487 label_current_version: Version actuelle
487 label_current_version: Version actuelle
488 label_preview: PrΓ©visualisation
488 label_preview: PrΓ©visualisation
489 label_feed_plural: Flux RSS
489 label_feed_plural: Flux RSS
490 label_changes_details: DΓ©tails de tous les changements
490 label_changes_details: DΓ©tails de tous les changements
491 label_issue_tracking: Suivi des demandes
491 label_issue_tracking: Suivi des demandes
492 label_spent_time: Temps passΓ©
492 label_spent_time: Temps passΓ©
493 label_f_hour: %.2f heure
493 label_f_hour: %.2f heure
494 label_f_hour_plural: %.2f heures
494 label_f_hour_plural: %.2f heures
495 label_time_tracking: Suivi du temps
495 label_time_tracking: Suivi du temps
496 label_change_plural: Changements
496 label_change_plural: Changements
497 label_statistics: Statistiques
497 label_statistics: Statistiques
498 label_commits_per_month: Commits par mois
498 label_commits_per_month: Commits par mois
499 label_commits_per_author: Commits par auteur
499 label_commits_per_author: Commits par auteur
500 label_view_diff: Voir les diffΓ©rences
500 label_view_diff: Voir les diffΓ©rences
501 label_diff_inline: en ligne
501 label_diff_inline: en ligne
502 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
502 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
503 label_options: Options
503 label_options: Options
504 label_copy_workflow_from: Copier le workflow de
504 label_copy_workflow_from: Copier le workflow de
505 label_permissions_report: Synthèse des permissions
505 label_permissions_report: Synthèse des permissions
506 label_watched_issues: Demandes surveillΓ©es
506 label_watched_issues: Demandes surveillΓ©es
507 label_related_issues: Demandes liΓ©es
507 label_related_issues: Demandes liΓ©es
508 label_applied_status: Statut appliquΓ©
508 label_applied_status: Statut appliquΓ©
509 label_loading: Chargement...
509 label_loading: Chargement...
510 label_relation_new: Nouvelle relation
510 label_relation_new: Nouvelle relation
511 label_relation_delete: Supprimer la relation
511 label_relation_delete: Supprimer la relation
512 label_relates_to: liΓ© Γ 
512 label_relates_to: liΓ© Γ 
513 label_duplicates: duplique
513 label_duplicates: duplique
514 label_duplicated_by: dupliquΓ© par
514 label_duplicated_by: dupliquΓ© par
515 label_blocks: bloque
515 label_blocks: bloque
516 label_blocked_by: bloquΓ© par
516 label_blocked_by: bloquΓ© par
517 label_precedes: précède
517 label_precedes: précède
518 label_follows: suit
518 label_follows: suit
519 label_end_to_start: fin Γ  dΓ©but
519 label_end_to_start: fin Γ  dΓ©but
520 label_end_to_end: fin Γ  fin
520 label_end_to_end: fin Γ  fin
521 label_start_to_start: dΓ©but Γ  dΓ©but
521 label_start_to_start: dΓ©but Γ  dΓ©but
522 label_start_to_end: dΓ©but Γ  fin
522 label_start_to_end: dΓ©but Γ  fin
523 label_stay_logged_in: Rester connectΓ©
523 label_stay_logged_in: Rester connectΓ©
524 label_disabled: dΓ©sactivΓ©
524 label_disabled: dΓ©sactivΓ©
525 label_show_completed_versions: Voir les versions passΓ©es
525 label_show_completed_versions: Voir les versions passΓ©es
526 label_me: moi
526 label_me: moi
527 label_board: Forum
527 label_board: Forum
528 label_board_new: Nouveau forum
528 label_board_new: Nouveau forum
529 label_board_plural: Forums
529 label_board_plural: Forums
530 label_topic_plural: Discussions
530 label_topic_plural: Discussions
531 label_message_plural: Messages
531 label_message_plural: Messages
532 label_message_last: Dernier message
532 label_message_last: Dernier message
533 label_message_new: Nouveau message
533 label_message_new: Nouveau message
534 label_message_posted: Message ajoutΓ©
534 label_message_posted: Message ajoutΓ©
535 label_reply_plural: RΓ©ponses
535 label_reply_plural: RΓ©ponses
536 label_send_information: Envoyer les informations Γ  l'utilisateur
536 label_send_information: Envoyer les informations Γ  l'utilisateur
537 label_year: AnnΓ©e
537 label_year: AnnΓ©e
538 label_month: Mois
538 label_month: Mois
539 label_week: Semaine
539 label_week: Semaine
540 label_date_from: Du
540 label_date_from: Du
541 label_date_to: Au
541 label_date_to: Au
542 label_language_based: BasΓ© sur la langue de l'utilisateur
542 label_language_based: BasΓ© sur la langue de l'utilisateur
543 label_sort_by: Trier par %s
543 label_sort_by: Trier par %s
544 label_send_test_email: Envoyer un email de test
544 label_send_test_email: Envoyer un email de test
545 label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s
545 label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s
546 label_module_plural: Modules
546 label_module_plural: Modules
547 label_added_time_by: AjoutΓ© par %s il y a %s
547 label_added_time_by: AjoutΓ© par %s il y a %s
548 label_updated_time_by: Mis Γ  jour par %s il y a %s
548 label_updated_time_by: Mis Γ  jour par %s il y a %s
549 label_updated_time: Mis Γ  jour il y a %s
549 label_updated_time: Mis Γ  jour il y a %s
550 label_jump_to_a_project: Aller Γ  un projet...
550 label_jump_to_a_project: Aller Γ  un projet...
551 label_file_plural: Fichiers
551 label_file_plural: Fichiers
552 label_changeset_plural: RΓ©visions
552 label_changeset_plural: RΓ©visions
553 label_default_columns: Colonnes par dΓ©faut
553 label_default_columns: Colonnes par dΓ©faut
554 label_no_change_option: (Pas de changement)
554 label_no_change_option: (Pas de changement)
555 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
555 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
556 label_theme: Thème
556 label_theme: Thème
557 label_default: DΓ©faut
557 label_default: DΓ©faut
558 label_search_titles_only: Uniquement dans les titres
558 label_search_titles_only: Uniquement dans les titres
559 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
559 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
560 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
560 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
561 label_user_mail_option_none: "Seulement pour ce que je surveille ou Γ  quoi je participe"
561 label_user_mail_option_none: "Seulement pour ce que je surveille ou Γ  quoi je participe"
562 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
562 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
563 label_registration_activation_by_email: activation du compte par email
563 label_registration_activation_by_email: activation du compte par email
564 label_registration_manual_activation: activation manuelle du compte
564 label_registration_manual_activation: activation manuelle du compte
565 label_registration_automatic_activation: activation automatique du compte
565 label_registration_automatic_activation: activation automatique du compte
566 label_display_per_page: 'Par page: %s'
566 label_display_per_page: 'Par page: %s'
567 label_age: Age
567 label_age: Age
568 label_change_properties: Changer les propriΓ©tΓ©s
568 label_change_properties: Changer les propriΓ©tΓ©s
569 label_general: GΓ©nΓ©ral
569 label_general: GΓ©nΓ©ral
570 label_more: Plus
570 label_more: Plus
571 label_scm: SCM
571 label_scm: SCM
572 label_plugins: Plugins
572 label_plugins: Plugins
573 label_ldap_authentication: Authentification LDAP
573 label_ldap_authentication: Authentification LDAP
574 label_downloads_abbr: D/L
574 label_downloads_abbr: D/L
575 label_optional_description: Description facultative
575 label_optional_description: Description facultative
576 label_add_another_file: Ajouter un autre fichier
576 label_add_another_file: Ajouter un autre fichier
577 label_preferences: PrΓ©fΓ©rences
577 label_preferences: PrΓ©fΓ©rences
578 label_chronological_order: Dans l'ordre chronologique
578 label_chronological_order: Dans l'ordre chronologique
579 label_reverse_chronological_order: Dans l'ordre chronologique inverse
579 label_reverse_chronological_order: Dans l'ordre chronologique inverse
580 label_planning: Planning
580 label_planning: Planning
581 label_incoming_emails: Emails entrants
581 label_incoming_emails: Emails entrants
582 label_generate_key: GΓ©nΓ©rer une clΓ©
582 label_generate_key: GΓ©nΓ©rer une clΓ©
583 label_issue_watchers: Utilisateurs surveillant cette demande
583 label_issue_watchers: Observateurs
584 label_example: Exemple
584 label_example: Exemple
585
585
586 button_login: Connexion
586 button_login: Connexion
587 button_submit: Soumettre
587 button_submit: Soumettre
588 button_save: Sauvegarder
588 button_save: Sauvegarder
589 button_check_all: Tout cocher
589 button_check_all: Tout cocher
590 button_uncheck_all: Tout dΓ©cocher
590 button_uncheck_all: Tout dΓ©cocher
591 button_delete: Supprimer
591 button_delete: Supprimer
592 button_create: CrΓ©er
592 button_create: CrΓ©er
593 button_test: Tester
593 button_test: Tester
594 button_edit: Modifier
594 button_edit: Modifier
595 button_add: Ajouter
595 button_add: Ajouter
596 button_change: Changer
596 button_change: Changer
597 button_apply: Appliquer
597 button_apply: Appliquer
598 button_clear: Effacer
598 button_clear: Effacer
599 button_lock: Verrouiller
599 button_lock: Verrouiller
600 button_unlock: DΓ©verrouiller
600 button_unlock: DΓ©verrouiller
601 button_download: TΓ©lΓ©charger
601 button_download: TΓ©lΓ©charger
602 button_list: Lister
602 button_list: Lister
603 button_view: Voir
603 button_view: Voir
604 button_move: DΓ©placer
604 button_move: DΓ©placer
605 button_back: Retour
605 button_back: Retour
606 button_cancel: Annuler
606 button_cancel: Annuler
607 button_activate: Activer
607 button_activate: Activer
608 button_sort: Trier
608 button_sort: Trier
609 button_log_time: Saisir temps
609 button_log_time: Saisir temps
610 button_rollback: Revenir Γ  cette version
610 button_rollback: Revenir Γ  cette version
611 button_watch: Surveiller
611 button_watch: Surveiller
612 button_unwatch: Ne plus surveiller
612 button_unwatch: Ne plus surveiller
613 button_reply: RΓ©pondre
613 button_reply: RΓ©pondre
614 button_archive: Archiver
614 button_archive: Archiver
615 button_unarchive: DΓ©sarchiver
615 button_unarchive: DΓ©sarchiver
616 button_reset: RΓ©initialiser
616 button_reset: RΓ©initialiser
617 button_rename: Renommer
617 button_rename: Renommer
618 button_change_password: Changer de mot de passe
618 button_change_password: Changer de mot de passe
619 button_copy: Copier
619 button_copy: Copier
620 button_annotate: Annoter
620 button_annotate: Annoter
621 button_update: Mettre Γ  jour
621 button_update: Mettre Γ  jour
622 button_configure: Configurer
622 button_configure: Configurer
623 button_quote: Citer
623 button_quote: Citer
624
624
625 status_active: actif
625 status_active: actif
626 status_registered: enregistrΓ©
626 status_registered: enregistrΓ©
627 status_locked: vΓ©rouillΓ©
627 status_locked: vΓ©rouillΓ©
628
628
629 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
629 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
630 text_regexp_info: ex. ^[A-Z0-9]+$
630 text_regexp_info: ex. ^[A-Z0-9]+$
631 text_min_max_length_info: 0 pour aucune restriction
631 text_min_max_length_info: 0 pour aucune restriction
632 text_project_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce projet et toutes ses donnΓ©es ?
632 text_project_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce projet et toutes ses donnΓ©es ?
633 text_subprojects_destroy_warning: 'Ses sous-projets: %s seront Γ©galement supprimΓ©s.'
633 text_subprojects_destroy_warning: 'Ses sous-projets: %s seront Γ©galement supprimΓ©s.'
634 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
634 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
635 text_are_you_sure: Etes-vous sΓ»r ?
635 text_are_you_sure: Etes-vous sΓ»r ?
636 text_journal_changed: changΓ© de %s Γ  %s
636 text_journal_changed: changΓ© de %s Γ  %s
637 text_journal_set_to: mis Γ  %s
637 text_journal_set_to: mis Γ  %s
638 text_journal_deleted: supprimΓ©
638 text_journal_deleted: supprimΓ©
639 text_tip_task_begin_day: tΓ’che commenΓ§ant ce jour
639 text_tip_task_begin_day: tΓ’che commenΓ§ant ce jour
640 text_tip_task_end_day: tΓ’che finissant ce jour
640 text_tip_task_end_day: tΓ’che finissant ce jour
641 text_tip_task_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
641 text_tip_task_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
642 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres et tirets sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
642 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres et tirets sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
643 text_caracters_maximum: %d caractères maximum.
643 text_caracters_maximum: %d caractères maximum.
644 text_caracters_minimum: %d caractères minimum.
644 text_caracters_minimum: %d caractères minimum.
645 text_length_between: Longueur comprise entre %d et %d caractères.
645 text_length_between: Longueur comprise entre %d et %d caractères.
646 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
646 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
647 text_unallowed_characters: Caractères non autorisés
647 text_unallowed_characters: Caractères non autorisés
648 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
648 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
649 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
649 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
650 text_issue_added: La demande %s a Γ©tΓ© soumise par %s.
650 text_issue_added: La demande %s a Γ©tΓ© soumise par %s.
651 text_issue_updated: La demande %s a Γ©tΓ© mise Γ  jour par %s.
651 text_issue_updated: La demande %s a Γ©tΓ© mise Γ  jour par %s.
652 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
652 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
653 text_issue_category_destroy_question: %d demandes sont affectΓ©es Γ  cette catΓ©gories. Que voulez-vous faire ?
653 text_issue_category_destroy_question: %d demandes sont affectΓ©es Γ  cette catΓ©gories. Que voulez-vous faire ?
654 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
654 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
655 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
655 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
656 text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ  quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)."
656 text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ  quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)."
657 text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©."
657 text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©."
658 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
658 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
659 text_status_changed_by_changeset: AppliquΓ© par commit %s.
659 text_status_changed_by_changeset: AppliquΓ© par commit %s.
660 text_issues_destroy_confirmation: 'Etes-vous sΓ»r de vouloir supprimer le(s) demandes(s) selectionnΓ©e(s) ?'
660 text_issues_destroy_confirmation: 'Etes-vous sΓ»r de vouloir supprimer le(s) demandes(s) selectionnΓ©e(s) ?'
661 text_select_project_modules: 'Selectionner les modules Γ  activer pour ce project:'
661 text_select_project_modules: 'Selectionner les modules Γ  activer pour ce project:'
662 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
662 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
663 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
663 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
664 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
664 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
665 text_destroy_time_entries_question: %.02f heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?
665 text_destroy_time_entries_question: %.02f heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?
666 text_destroy_time_entries: Supprimer les heures
666 text_destroy_time_entries: Supprimer les heures
667 text_assign_time_entries_to_project: Reporter les heures sur le projet
667 text_assign_time_entries_to_project: Reporter les heures sur le projet
668 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
668 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
669 text_user_wrote: '%s a Γ©crit:'
669 text_user_wrote: '%s a Γ©crit:'
670 text_enumeration_destroy_question: 'Cette valeur est affectΓ©e Γ  %d objets.'
670 text_enumeration_destroy_question: 'Cette valeur est affectΓ©e Γ  %d objets.'
671 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
671 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
672 text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/email.yml et redΓ©marrez l'application pour les activer."
672 text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/email.yml et redΓ©marrez l'application pour les activer."
673 text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ  chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s."
673 text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ  chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s."
674 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
674 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
675
675
676 default_role_manager: Manager
676 default_role_manager: Manager
677 default_role_developper: DΓ©veloppeur
677 default_role_developper: DΓ©veloppeur
678 default_role_reporter: Rapporteur
678 default_role_reporter: Rapporteur
679 default_tracker_bug: Anomalie
679 default_tracker_bug: Anomalie
680 default_tracker_feature: Evolution
680 default_tracker_feature: Evolution
681 default_tracker_support: Assistance
681 default_tracker_support: Assistance
682 default_issue_status_new: Nouveau
682 default_issue_status_new: Nouveau
683 default_issue_status_assigned: AssignΓ©
683 default_issue_status_assigned: AssignΓ©
684 default_issue_status_resolved: RΓ©solu
684 default_issue_status_resolved: RΓ©solu
685 default_issue_status_feedback: Commentaire
685 default_issue_status_feedback: Commentaire
686 default_issue_status_closed: FermΓ©
686 default_issue_status_closed: FermΓ©
687 default_issue_status_rejected: RejetΓ©
687 default_issue_status_rejected: RejetΓ©
688 default_doc_category_user: Documentation utilisateur
688 default_doc_category_user: Documentation utilisateur
689 default_doc_category_tech: Documentation technique
689 default_doc_category_tech: Documentation technique
690 default_priority_low: Bas
690 default_priority_low: Bas
691 default_priority_normal: Normal
691 default_priority_normal: Normal
692 default_priority_high: Haut
692 default_priority_high: Haut
693 default_priority_urgent: Urgent
693 default_priority_urgent: Urgent
694 default_priority_immediate: ImmΓ©diat
694 default_priority_immediate: ImmΓ©diat
695 default_activity_design: Conception
695 default_activity_design: Conception
696 default_activity_development: DΓ©veloppement
696 default_activity_development: DΓ©veloppement
697
697
698 enumeration_issue_priorities: PrioritΓ©s des demandes
698 enumeration_issue_priorities: PrioritΓ©s des demandes
699 enumeration_doc_categories: CatΓ©gories des documents
699 enumeration_doc_categories: CatΓ©gories des documents
700 enumeration_activities: ActivitΓ©s (suivi du temps)
700 enumeration_activities: ActivitΓ©s (suivi du temps)
@@ -1,189 +1,196
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19 require 'rexml/document'
19 require 'rexml/document'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class DarcsAdapter < AbstractAdapter
24 class DarcsAdapter < AbstractAdapter
25 # Darcs executable name
25 # Darcs executable name
26 DARCS_BIN = "darcs"
26 DARCS_BIN = "darcs"
27
27
28 class << self
28 class << self
29 def client_version
29 def client_version
30 @@client_version ||= (darcs_binary_version || [])
30 @@client_version ||= (darcs_binary_version || [])
31 end
31 end
32
32
33 def darcs_binary_version
33 def darcs_binary_version
34 cmd = "#{DARCS_BIN} --version"
34 cmd = "#{DARCS_BIN} --version"
35 version = nil
35 version = nil
36 shellout(cmd) do |io|
36 shellout(cmd) do |io|
37 # Read darcs version in first returned line
37 # Read darcs version in first returned line
38 if m = io.gets.match(%r{((\d+\.)+\d+)})
38 if m = io.gets.match(%r{((\d+\.)+\d+)})
39 version = m[0].scan(%r{\d+}).collect(&:to_i)
39 version = m[0].scan(%r{\d+}).collect(&:to_i)
40 end
40 end
41 end
41 end
42 return nil if $? && $?.exitstatus != 0
42 return nil if $? && $?.exitstatus != 0
43 version
43 version
44 end
44 end
45 end
45 end
46
46
47 def initialize(url, root_url=nil, login=nil, password=nil)
47 def initialize(url, root_url=nil, login=nil, password=nil)
48 @url = url
48 @url = url
49 @root_url = url
49 @root_url = url
50 end
50 end
51
51
52 def supports_cat?
52 def supports_cat?
53 # cat supported in darcs 2.0.0 and higher
53 # cat supported in darcs 2.0.0 and higher
54 self.class.client_version_above?([2, 0, 0])
54 self.class.client_version_above?([2, 0, 0])
55 end
55 end
56
56
57 # Get info about the darcs repository
57 # Get info about the darcs repository
58 def info
58 def info
59 rev = revisions(nil,nil,nil,{:limit => 1})
59 rev = revisions(nil,nil,nil,{:limit => 1})
60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
61 end
61 end
62
62
63 # Returns an Entries collection
63 # Returns an Entries collection
64 # or nil if the given path doesn't exist in the repository
64 # or nil if the given path doesn't exist in the repository
65 def entries(path=nil, identifier=nil)
65 def entries(path=nil, identifier=nil)
66 path_prefix = (path.blank? ? '' : "#{path}/")
66 path_prefix = (path.blank? ? '' : "#{path}/")
67 path = '.' if path.blank?
67 path = '.' if path.blank?
68 entries = Entries.new
68 entries = Entries.new
69 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
69 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
70 cmd << " --match \"hash #{identifier}\"" if identifier
70 cmd << " --match \"hash #{identifier}\"" if identifier
71 cmd << " #{path}"
71 cmd << " #{path}"
72 shellout(cmd) do |io|
72 shellout(cmd) do |io|
73 begin
73 begin
74 doc = REXML::Document.new(io)
74 doc = REXML::Document.new(io)
75 if doc.root.name == 'directory'
75 if doc.root.name == 'directory'
76 doc.elements.each('directory/*') do |element|
76 doc.elements.each('directory/*') do |element|
77 next unless ['file', 'directory'].include? element.name
77 next unless ['file', 'directory'].include? element.name
78 entries << entry_from_xml(element, path_prefix)
78 entries << entry_from_xml(element, path_prefix)
79 end
79 end
80 elsif doc.root.name == 'file'
80 elsif doc.root.name == 'file'
81 entries << entry_from_xml(doc.root, path_prefix)
81 entries << entry_from_xml(doc.root, path_prefix)
82 end
82 end
83 rescue
83 rescue
84 end
84 end
85 end
85 end
86 return nil if $? && $?.exitstatus != 0
86 return nil if $? && $?.exitstatus != 0
87 entries.sort_by_name
87 entries.compact.sort_by_name
88 end
88 end
89
89
90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
91 path = '.' if path.blank?
91 path = '.' if path.blank?
92 revisions = Revisions.new
92 revisions = Revisions.new
93 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
93 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
94 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
94 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
96 shellout(cmd) do |io|
96 shellout(cmd) do |io|
97 begin
97 begin
98 doc = REXML::Document.new(io)
98 doc = REXML::Document.new(io)
99 doc.elements.each("changelog/patch") do |patch|
99 doc.elements.each("changelog/patch") do |patch|
100 message = patch.elements['name'].text
100 message = patch.elements['name'].text
101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
102 revisions << Revision.new({:identifier => nil,
102 revisions << Revision.new({:identifier => nil,
103 :author => patch.attributes['author'],
103 :author => patch.attributes['author'],
104 :scmid => patch.attributes['hash'],
104 :scmid => patch.attributes['hash'],
105 :time => Time.parse(patch.attributes['local_date']),
105 :time => Time.parse(patch.attributes['local_date']),
106 :message => message,
106 :message => message,
107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
108 })
108 })
109 end
109 end
110 rescue
110 rescue
111 end
111 end
112 end
112 end
113 return nil if $? && $?.exitstatus != 0
113 return nil if $? && $?.exitstatus != 0
114 revisions
114 revisions
115 end
115 end
116
116
117 def diff(path, identifier_from, identifier_to=nil)
117 def diff(path, identifier_from, identifier_to=nil)
118 path = '*' if path.blank?
118 path = '*' if path.blank?
119 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
119 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
120 if identifier_to.nil?
120 if identifier_to.nil?
121 cmd << " --match \"hash #{identifier_from}\""
121 cmd << " --match \"hash #{identifier_from}\""
122 else
122 else
123 cmd << " --to-match \"hash #{identifier_from}\""
123 cmd << " --to-match \"hash #{identifier_from}\""
124 cmd << " --from-match \"hash #{identifier_to}\""
124 cmd << " --from-match \"hash #{identifier_to}\""
125 end
125 end
126 cmd << " -u #{path}"
126 cmd << " -u #{path}"
127 diff = []
127 diff = []
128 shellout(cmd) do |io|
128 shellout(cmd) do |io|
129 io.each_line do |line|
129 io.each_line do |line|
130 diff << line
130 diff << line
131 end
131 end
132 end
132 end
133 return nil if $? && $?.exitstatus != 0
133 return nil if $? && $?.exitstatus != 0
134 diff
134 diff
135 end
135 end
136
136
137 def cat(path, identifier=nil)
137 def cat(path, identifier=nil)
138 cmd = "#{DARCS_BIN} show content --repodir #{@url}"
138 cmd = "#{DARCS_BIN} show content --repodir #{@url}"
139 cmd << " --match \"hash #{identifier}\"" if identifier
139 cmd << " --match \"hash #{identifier}\"" if identifier
140 cmd << " #{shell_quote path}"
140 cmd << " #{shell_quote path}"
141 cat = nil
141 cat = nil
142 shellout(cmd) do |io|
142 shellout(cmd) do |io|
143 io.binmode
143 io.binmode
144 cat = io.read
144 cat = io.read
145 end
145 end
146 return nil if $? && $?.exitstatus != 0
146 return nil if $? && $?.exitstatus != 0
147 cat
147 cat
148 end
148 end
149
149
150 private
150 private
151
151
152 # Returns an Entry from the given XML element
153 # or nil if the entry was deleted
152 def entry_from_xml(element, path_prefix)
154 def entry_from_xml(element, path_prefix)
155 modified_element = element.elements['modified']
156 if modified_element.elements['modified_how'].text.match(/removed/)
157 return nil
158 end
159
153 Entry.new({:name => element.attributes['name'],
160 Entry.new({:name => element.attributes['name'],
154 :path => path_prefix + element.attributes['name'],
161 :path => path_prefix + element.attributes['name'],
155 :kind => element.name == 'file' ? 'file' : 'dir',
162 :kind => element.name == 'file' ? 'file' : 'dir',
156 :size => nil,
163 :size => nil,
157 :lastrev => Revision.new({
164 :lastrev => Revision.new({
158 :identifier => nil,
165 :identifier => nil,
159 :scmid => element.elements['modified'].elements['patch'].attributes['hash']
166 :scmid => modified_element.elements['patch'].attributes['hash']
160 })
167 })
161 })
168 })
162 end
169 end
163
170
164 # Retrieve changed paths for a single patch
171 # Retrieve changed paths for a single patch
165 def get_paths_for_patch(hash)
172 def get_paths_for_patch(hash)
166 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
173 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
167 cmd << " --match \"hash #{hash}\" "
174 cmd << " --match \"hash #{hash}\" "
168 paths = []
175 paths = []
169 shellout(cmd) do |io|
176 shellout(cmd) do |io|
170 begin
177 begin
171 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
178 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
172 # A root element is added so that REXML doesn't raise an error
179 # A root element is added so that REXML doesn't raise an error
173 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
180 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
174 doc.elements.each('fake_root/summary/*') do |modif|
181 doc.elements.each('fake_root/summary/*') do |modif|
175 paths << {:action => modif.name[0,1].upcase,
182 paths << {:action => modif.name[0,1].upcase,
176 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
183 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
177 }
184 }
178 end
185 end
179 rescue
186 rescue
180 end
187 end
181 end
188 end
182 paths
189 paths
183 rescue CommandFailed
190 rescue CommandFailed
184 paths
191 paths
185 end
192 end
186 end
193 end
187 end
194 end
188 end
195 end
189 end
196 end
@@ -1,739 +1,763
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :versions,
31 :versions,
32 :trackers,
32 :trackers,
33 :projects_trackers,
33 :projects_trackers,
34 :issue_categories,
34 :issue_categories,
35 :enabled_modules,
35 :enabled_modules,
36 :enumerations,
36 :enumerations,
37 :attachments,
37 :attachments,
38 :workflows,
38 :workflows,
39 :custom_fields,
39 :custom_fields,
40 :custom_values,
40 :custom_values,
41 :custom_fields_trackers,
41 :custom_fields_trackers,
42 :time_entries,
42 :time_entries,
43 :journals,
43 :journals,
44 :journal_details
44 :journal_details
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 get :index
54 get :index
55 assert_response :success
55 assert_response :success
56 assert_template 'index.rhtml'
56 assert_template 'index.rhtml'
57 assert_not_nil assigns(:issues)
57 assert_not_nil assigns(:issues)
58 assert_nil assigns(:project)
58 assert_nil assigns(:project)
59 assert_tag :tag => 'a', :content => /Can't print recipes/
59 assert_tag :tag => 'a', :content => /Can't print recipes/
60 assert_tag :tag => 'a', :content => /Subproject issue/
60 assert_tag :tag => 'a', :content => /Subproject issue/
61 # private projects hidden
61 # private projects hidden
62 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
62 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
63 assert_no_tag :tag => 'a', :content => /Issue on project 2/
63 assert_no_tag :tag => 'a', :content => /Issue on project 2/
64 end
64 end
65
65
66 def test_index_should_not_list_issues_when_module_disabled
66 def test_index_should_not_list_issues_when_module_disabled
67 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
67 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
68 get :index
68 get :index
69 assert_response :success
69 assert_response :success
70 assert_template 'index.rhtml'
70 assert_template 'index.rhtml'
71 assert_not_nil assigns(:issues)
71 assert_not_nil assigns(:issues)
72 assert_nil assigns(:project)
72 assert_nil assigns(:project)
73 assert_no_tag :tag => 'a', :content => /Can't print recipes/
73 assert_no_tag :tag => 'a', :content => /Can't print recipes/
74 assert_tag :tag => 'a', :content => /Subproject issue/
74 assert_tag :tag => 'a', :content => /Subproject issue/
75 end
75 end
76
76
77 def test_index_with_project
77 def test_index_with_project
78 Setting.display_subprojects_issues = 0
78 Setting.display_subprojects_issues = 0
79 get :index, :project_id => 1
79 get :index, :project_id => 1
80 assert_response :success
80 assert_response :success
81 assert_template 'index.rhtml'
81 assert_template 'index.rhtml'
82 assert_not_nil assigns(:issues)
82 assert_not_nil assigns(:issues)
83 assert_tag :tag => 'a', :content => /Can't print recipes/
83 assert_tag :tag => 'a', :content => /Can't print recipes/
84 assert_no_tag :tag => 'a', :content => /Subproject issue/
84 assert_no_tag :tag => 'a', :content => /Subproject issue/
85 end
85 end
86
86
87 def test_index_with_project_and_subprojects
87 def test_index_with_project_and_subprojects
88 Setting.display_subprojects_issues = 1
88 Setting.display_subprojects_issues = 1
89 get :index, :project_id => 1
89 get :index, :project_id => 1
90 assert_response :success
90 assert_response :success
91 assert_template 'index.rhtml'
91 assert_template 'index.rhtml'
92 assert_not_nil assigns(:issues)
92 assert_not_nil assigns(:issues)
93 assert_tag :tag => 'a', :content => /Can't print recipes/
93 assert_tag :tag => 'a', :content => /Can't print recipes/
94 assert_tag :tag => 'a', :content => /Subproject issue/
94 assert_tag :tag => 'a', :content => /Subproject issue/
95 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
95 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
96 end
96 end
97
97
98 def test_index_with_project_and_subprojects_should_show_private_subprojects
98 def test_index_with_project_and_subprojects_should_show_private_subprojects
99 @request.session[:user_id] = 2
99 @request.session[:user_id] = 2
100 Setting.display_subprojects_issues = 1
100 Setting.display_subprojects_issues = 1
101 get :index, :project_id => 1
101 get :index, :project_id => 1
102 assert_response :success
102 assert_response :success
103 assert_template 'index.rhtml'
103 assert_template 'index.rhtml'
104 assert_not_nil assigns(:issues)
104 assert_not_nil assigns(:issues)
105 assert_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Can't print recipes/
106 assert_tag :tag => 'a', :content => /Subproject issue/
106 assert_tag :tag => 'a', :content => /Subproject issue/
107 assert_tag :tag => 'a', :content => /Issue of a private subproject/
107 assert_tag :tag => 'a', :content => /Issue of a private subproject/
108 end
108 end
109
109
110 def test_index_with_project_and_filter
110 def test_index_with_project_and_filter
111 get :index, :project_id => 1, :set_filter => 1
111 get :index, :project_id => 1, :set_filter => 1
112 assert_response :success
112 assert_response :success
113 assert_template 'index.rhtml'
113 assert_template 'index.rhtml'
114 assert_not_nil assigns(:issues)
114 assert_not_nil assigns(:issues)
115 end
115 end
116
116
117 def test_index_csv_with_project
117 def test_index_csv_with_project
118 get :index, :format => 'csv'
118 get :index, :format => 'csv'
119 assert_response :success
119 assert_response :success
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121 assert_equal 'text/csv', @response.content_type
121 assert_equal 'text/csv', @response.content_type
122
122
123 get :index, :project_id => 1, :format => 'csv'
123 get :index, :project_id => 1, :format => 'csv'
124 assert_response :success
124 assert_response :success
125 assert_not_nil assigns(:issues)
125 assert_not_nil assigns(:issues)
126 assert_equal 'text/csv', @response.content_type
126 assert_equal 'text/csv', @response.content_type
127 end
127 end
128
128
129 def test_index_pdf
129 def test_index_pdf
130 get :index, :format => 'pdf'
130 get :index, :format => 'pdf'
131 assert_response :success
131 assert_response :success
132 assert_not_nil assigns(:issues)
132 assert_not_nil assigns(:issues)
133 assert_equal 'application/pdf', @response.content_type
133 assert_equal 'application/pdf', @response.content_type
134
134
135 get :index, :project_id => 1, :format => 'pdf'
135 get :index, :project_id => 1, :format => 'pdf'
136 assert_response :success
136 assert_response :success
137 assert_not_nil assigns(:issues)
137 assert_not_nil assigns(:issues)
138 assert_equal 'application/pdf', @response.content_type
138 assert_equal 'application/pdf', @response.content_type
139 end
139 end
140
140
141 def test_index_sort
141 def test_index_sort
142 get :index, :sort_key => 'tracker'
142 get :index, :sort_key => 'tracker'
143 assert_response :success
143 assert_response :success
144
144
145 sort_params = @request.session['issuesindex_sort']
145 sort_params = @request.session['issuesindex_sort']
146 assert sort_params.is_a?(Hash)
146 assert sort_params.is_a?(Hash)
147 assert_equal 'tracker', sort_params[:key]
147 assert_equal 'tracker', sort_params[:key]
148 assert_equal 'ASC', sort_params[:order]
148 assert_equal 'ASC', sort_params[:order]
149 end
149 end
150
150
151 def test_gantt
151 def test_gantt
152 get :gantt, :project_id => 1
152 get :gantt, :project_id => 1
153 assert_response :success
153 assert_response :success
154 assert_template 'gantt.rhtml'
154 assert_template 'gantt.rhtml'
155 assert_not_nil assigns(:gantt)
155 assert_not_nil assigns(:gantt)
156 events = assigns(:gantt).events
156 events = assigns(:gantt).events
157 assert_not_nil events
157 assert_not_nil events
158 # Issue with start and due dates
158 # Issue with start and due dates
159 i = Issue.find(1)
159 i = Issue.find(1)
160 assert_not_nil i.due_date
160 assert_not_nil i.due_date
161 assert events.include?(Issue.find(1))
161 assert events.include?(Issue.find(1))
162 # Issue with without due date but targeted to a version with date
162 # Issue with without due date but targeted to a version with date
163 i = Issue.find(2)
163 i = Issue.find(2)
164 assert_nil i.due_date
164 assert_nil i.due_date
165 assert events.include?(i)
165 assert events.include?(i)
166 end
166 end
167
167
168 def test_cross_project_gantt
168 def test_cross_project_gantt
169 get :gantt
169 get :gantt
170 assert_response :success
170 assert_response :success
171 assert_template 'gantt.rhtml'
171 assert_template 'gantt.rhtml'
172 assert_not_nil assigns(:gantt)
172 assert_not_nil assigns(:gantt)
173 events = assigns(:gantt).events
173 events = assigns(:gantt).events
174 assert_not_nil events
174 assert_not_nil events
175 end
175 end
176
176
177 def test_gantt_export_to_pdf
177 def test_gantt_export_to_pdf
178 get :gantt, :project_id => 1, :format => 'pdf'
178 get :gantt, :project_id => 1, :format => 'pdf'
179 assert_response :success
179 assert_response :success
180 assert_template 'gantt.rfpdf'
180 assert_template 'gantt.rfpdf'
181 assert_equal 'application/pdf', @response.content_type
181 assert_equal 'application/pdf', @response.content_type
182 assert_not_nil assigns(:gantt)
182 assert_not_nil assigns(:gantt)
183 end
183 end
184
184
185 def test_cross_project_gantt_export_to_pdf
185 def test_cross_project_gantt_export_to_pdf
186 get :gantt, :format => 'pdf'
186 get :gantt, :format => 'pdf'
187 assert_response :success
187 assert_response :success
188 assert_template 'gantt.rfpdf'
188 assert_template 'gantt.rfpdf'
189 assert_equal 'application/pdf', @response.content_type
189 assert_equal 'application/pdf', @response.content_type
190 assert_not_nil assigns(:gantt)
190 assert_not_nil assigns(:gantt)
191 end
191 end
192
192
193 if Object.const_defined?(:Magick)
193 if Object.const_defined?(:Magick)
194 def test_gantt_image
194 def test_gantt_image
195 get :gantt, :project_id => 1, :format => 'png'
195 get :gantt, :project_id => 1, :format => 'png'
196 assert_response :success
196 assert_response :success
197 assert_equal 'image/png', @response.content_type
197 assert_equal 'image/png', @response.content_type
198 end
198 end
199 else
199 else
200 puts "RMagick not installed. Skipping tests !!!"
200 puts "RMagick not installed. Skipping tests !!!"
201 end
201 end
202
202
203 def test_calendar
203 def test_calendar
204 get :calendar, :project_id => 1
204 get :calendar, :project_id => 1
205 assert_response :success
205 assert_response :success
206 assert_template 'calendar'
206 assert_template 'calendar'
207 assert_not_nil assigns(:calendar)
207 assert_not_nil assigns(:calendar)
208 end
208 end
209
209
210 def test_cross_project_calendar
210 def test_cross_project_calendar
211 get :calendar
211 get :calendar
212 assert_response :success
212 assert_response :success
213 assert_template 'calendar'
213 assert_template 'calendar'
214 assert_not_nil assigns(:calendar)
214 assert_not_nil assigns(:calendar)
215 end
215 end
216
216
217 def test_changes
217 def test_changes
218 get :changes, :project_id => 1
218 get :changes, :project_id => 1
219 assert_response :success
219 assert_response :success
220 assert_not_nil assigns(:journals)
220 assert_not_nil assigns(:journals)
221 assert_equal 'application/atom+xml', @response.content_type
221 assert_equal 'application/atom+xml', @response.content_type
222 end
222 end
223
223
224 def test_show_by_anonymous
224 def test_show_by_anonymous
225 get :show, :id => 1
225 get :show, :id => 1
226 assert_response :success
226 assert_response :success
227 assert_template 'show.rhtml'
227 assert_template 'show.rhtml'
228 assert_not_nil assigns(:issue)
228 assert_not_nil assigns(:issue)
229 assert_equal Issue.find(1), assigns(:issue)
229 assert_equal Issue.find(1), assigns(:issue)
230
230
231 # anonymous role is allowed to add a note
231 # anonymous role is allowed to add a note
232 assert_tag :tag => 'form',
232 assert_tag :tag => 'form',
233 :descendant => { :tag => 'fieldset',
233 :descendant => { :tag => 'fieldset',
234 :child => { :tag => 'legend',
234 :child => { :tag => 'legend',
235 :content => /Notes/ } }
235 :content => /Notes/ } }
236 end
236 end
237
237
238 def test_show_by_manager
238 def test_show_by_manager
239 @request.session[:user_id] = 2
239 @request.session[:user_id] = 2
240 get :show, :id => 1
240 get :show, :id => 1
241 assert_response :success
241 assert_response :success
242
242
243 assert_tag :tag => 'form',
243 assert_tag :tag => 'form',
244 :descendant => { :tag => 'fieldset',
244 :descendant => { :tag => 'fieldset',
245 :child => { :tag => 'legend',
245 :child => { :tag => 'legend',
246 :content => /Change properties/ } },
246 :content => /Change properties/ } },
247 :descendant => { :tag => 'fieldset',
247 :descendant => { :tag => 'fieldset',
248 :child => { :tag => 'legend',
248 :child => { :tag => 'legend',
249 :content => /Log time/ } },
249 :content => /Log time/ } },
250 :descendant => { :tag => 'fieldset',
250 :descendant => { :tag => 'fieldset',
251 :child => { :tag => 'legend',
251 :child => { :tag => 'legend',
252 :content => /Notes/ } }
252 :content => /Notes/ } }
253 end
253 end
254
254
255 def test_get_new
255 def test_get_new
256 @request.session[:user_id] = 2
256 @request.session[:user_id] = 2
257 get :new, :project_id => 1, :tracker_id => 1
257 get :new, :project_id => 1, :tracker_id => 1
258 assert_response :success
258 assert_response :success
259 assert_template 'new'
259 assert_template 'new'
260
260
261 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
261 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
262 :value => 'Default string' }
262 :value => 'Default string' }
263 end
263 end
264
264
265 def test_get_new_without_tracker_id
265 def test_get_new_without_tracker_id
266 @request.session[:user_id] = 2
266 @request.session[:user_id] = 2
267 get :new, :project_id => 1
267 get :new, :project_id => 1
268 assert_response :success
268 assert_response :success
269 assert_template 'new'
269 assert_template 'new'
270
270
271 issue = assigns(:issue)
271 issue = assigns(:issue)
272 assert_not_nil issue
272 assert_not_nil issue
273 assert_equal Project.find(1).trackers.first, issue.tracker
273 assert_equal Project.find(1).trackers.first, issue.tracker
274 end
274 end
275
275
276 def test_update_new_form
276 def test_update_new_form
277 @request.session[:user_id] = 2
277 @request.session[:user_id] = 2
278 xhr :post, :new, :project_id => 1,
278 xhr :post, :new, :project_id => 1,
279 :issue => {:tracker_id => 2,
279 :issue => {:tracker_id => 2,
280 :subject => 'This is the test_new issue',
280 :subject => 'This is the test_new issue',
281 :description => 'This is the description',
281 :description => 'This is the description',
282 :priority_id => 5}
282 :priority_id => 5}
283 assert_response :success
283 assert_response :success
284 assert_template 'new'
284 assert_template 'new'
285 end
285 end
286
286
287 def test_post_new
287 def test_post_new
288 @request.session[:user_id] = 2
288 @request.session[:user_id] = 2
289 post :new, :project_id => 1,
289 post :new, :project_id => 1,
290 :issue => {:tracker_id => 3,
290 :issue => {:tracker_id => 3,
291 :subject => 'This is the test_new issue',
291 :subject => 'This is the test_new issue',
292 :description => 'This is the description',
292 :description => 'This is the description',
293 :priority_id => 5,
293 :priority_id => 5,
294 :estimated_hours => '',
294 :estimated_hours => '',
295 :custom_field_values => {'2' => 'Value for field 2'}}
295 :custom_field_values => {'2' => 'Value for field 2'}}
296 assert_redirected_to 'issues/show'
296 assert_redirected_to 'issues/show'
297
297
298 issue = Issue.find_by_subject('This is the test_new issue')
298 issue = Issue.find_by_subject('This is the test_new issue')
299 assert_not_nil issue
299 assert_not_nil issue
300 assert_equal 2, issue.author_id
300 assert_equal 2, issue.author_id
301 assert_equal 3, issue.tracker_id
301 assert_equal 3, issue.tracker_id
302 assert_nil issue.estimated_hours
302 assert_nil issue.estimated_hours
303 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
303 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
304 assert_not_nil v
304 assert_not_nil v
305 assert_equal 'Value for field 2', v.value
305 assert_equal 'Value for field 2', v.value
306 end
306 end
307
307
308 def test_post_new_without_custom_fields_param
308 def test_post_new_without_custom_fields_param
309 @request.session[:user_id] = 2
309 @request.session[:user_id] = 2
310 post :new, :project_id => 1,
310 post :new, :project_id => 1,
311 :issue => {:tracker_id => 1,
311 :issue => {:tracker_id => 1,
312 :subject => 'This is the test_new issue',
312 :subject => 'This is the test_new issue',
313 :description => 'This is the description',
313 :description => 'This is the description',
314 :priority_id => 5}
314 :priority_id => 5}
315 assert_redirected_to 'issues/show'
315 assert_redirected_to 'issues/show'
316 end
316 end
317
317
318 def test_post_new_with_required_custom_field_and_without_custom_fields_param
318 def test_post_new_with_required_custom_field_and_without_custom_fields_param
319 field = IssueCustomField.find_by_name('Database')
319 field = IssueCustomField.find_by_name('Database')
320 field.update_attribute(:is_required, true)
320 field.update_attribute(:is_required, true)
321
321
322 @request.session[:user_id] = 2
322 @request.session[:user_id] = 2
323 post :new, :project_id => 1,
323 post :new, :project_id => 1,
324 :issue => {:tracker_id => 1,
324 :issue => {:tracker_id => 1,
325 :subject => 'This is the test_new issue',
325 :subject => 'This is the test_new issue',
326 :description => 'This is the description',
326 :description => 'This is the description',
327 :priority_id => 5}
327 :priority_id => 5}
328 assert_response :success
328 assert_response :success
329 assert_template 'new'
329 assert_template 'new'
330 issue = assigns(:issue)
330 issue = assigns(:issue)
331 assert_not_nil issue
331 assert_not_nil issue
332 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
332 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
333 end
333 end
334
334
335 def test_post_new_with_watchers
336 @request.session[:user_id] = 2
337 ActionMailer::Base.deliveries.clear
338
339 assert_difference 'Watcher.count', 2 do
340 post :new, :project_id => 1,
341 :issue => {:tracker_id => 1,
342 :subject => 'This is a new issue with watchers',
343 :description => 'This is the description',
344 :priority_id => 5,
345 :watcher_user_ids => ['2', '3']}
346 end
347 assert_redirected_to 'issues/show'
348
349 issue = Issue.find_by_subject('This is a new issue with watchers')
350 # Watchers added
351 assert_equal [2, 3], issue.watcher_user_ids.sort
352 assert issue.watched_by?(User.find(3))
353 # Watchers notified
354 mail = ActionMailer::Base.deliveries.last
355 assert_kind_of TMail::Mail, mail
356 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
357 end
358
335 def test_post_should_preserve_fields_values_on_validation_failure
359 def test_post_should_preserve_fields_values_on_validation_failure
336 @request.session[:user_id] = 2
360 @request.session[:user_id] = 2
337 post :new, :project_id => 1,
361 post :new, :project_id => 1,
338 :issue => {:tracker_id => 1,
362 :issue => {:tracker_id => 1,
339 :subject => 'This is the test_new issue',
363 :subject => 'This is the test_new issue',
340 # empty description
364 # empty description
341 :description => '',
365 :description => '',
342 :priority_id => 6,
366 :priority_id => 6,
343 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
367 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
344 assert_response :success
368 assert_response :success
345 assert_template 'new'
369 assert_template 'new'
346
370
347 assert_tag :input, :attributes => { :name => 'issue[subject]',
371 assert_tag :input, :attributes => { :name => 'issue[subject]',
348 :value => 'This is the test_new issue' }
372 :value => 'This is the test_new issue' }
349 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
373 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
350 :child => { :tag => 'option', :attributes => { :selected => 'selected',
374 :child => { :tag => 'option', :attributes => { :selected => 'selected',
351 :value => '6' },
375 :value => '6' },
352 :content => 'High' }
376 :content => 'High' }
353 # Custom fields
377 # Custom fields
354 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
378 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
355 :child => { :tag => 'option', :attributes => { :selected => 'selected',
379 :child => { :tag => 'option', :attributes => { :selected => 'selected',
356 :value => 'Oracle' },
380 :value => 'Oracle' },
357 :content => 'Oracle' }
381 :content => 'Oracle' }
358 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
382 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
359 :value => 'Value for field 2'}
383 :value => 'Value for field 2'}
360 end
384 end
361
385
362 def test_copy_issue
386 def test_copy_issue
363 @request.session[:user_id] = 2
387 @request.session[:user_id] = 2
364 get :new, :project_id => 1, :copy_from => 1
388 get :new, :project_id => 1, :copy_from => 1
365 assert_template 'new'
389 assert_template 'new'
366 assert_not_nil assigns(:issue)
390 assert_not_nil assigns(:issue)
367 orig = Issue.find(1)
391 orig = Issue.find(1)
368 assert_equal orig.subject, assigns(:issue).subject
392 assert_equal orig.subject, assigns(:issue).subject
369 end
393 end
370
394
371 def test_get_edit
395 def test_get_edit
372 @request.session[:user_id] = 2
396 @request.session[:user_id] = 2
373 get :edit, :id => 1
397 get :edit, :id => 1
374 assert_response :success
398 assert_response :success
375 assert_template 'edit'
399 assert_template 'edit'
376 assert_not_nil assigns(:issue)
400 assert_not_nil assigns(:issue)
377 assert_equal Issue.find(1), assigns(:issue)
401 assert_equal Issue.find(1), assigns(:issue)
378 end
402 end
379
403
380 def test_get_edit_with_params
404 def test_get_edit_with_params
381 @request.session[:user_id] = 2
405 @request.session[:user_id] = 2
382 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
406 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
383 assert_response :success
407 assert_response :success
384 assert_template 'edit'
408 assert_template 'edit'
385
409
386 issue = assigns(:issue)
410 issue = assigns(:issue)
387 assert_not_nil issue
411 assert_not_nil issue
388
412
389 assert_equal 5, issue.status_id
413 assert_equal 5, issue.status_id
390 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
414 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
391 :child => { :tag => 'option',
415 :child => { :tag => 'option',
392 :content => 'Closed',
416 :content => 'Closed',
393 :attributes => { :selected => 'selected' } }
417 :attributes => { :selected => 'selected' } }
394
418
395 assert_equal 7, issue.priority_id
419 assert_equal 7, issue.priority_id
396 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
420 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
397 :child => { :tag => 'option',
421 :child => { :tag => 'option',
398 :content => 'Urgent',
422 :content => 'Urgent',
399 :attributes => { :selected => 'selected' } }
423 :attributes => { :selected => 'selected' } }
400 end
424 end
401
425
402 def test_reply_to_issue
426 def test_reply_to_issue
403 @request.session[:user_id] = 2
427 @request.session[:user_id] = 2
404 get :reply, :id => 1
428 get :reply, :id => 1
405 assert_response :success
429 assert_response :success
406 assert_select_rjs :show, "update"
430 assert_select_rjs :show, "update"
407 end
431 end
408
432
409 def test_reply_to_note
433 def test_reply_to_note
410 @request.session[:user_id] = 2
434 @request.session[:user_id] = 2
411 get :reply, :id => 1, :journal_id => 2
435 get :reply, :id => 1, :journal_id => 2
412 assert_response :success
436 assert_response :success
413 assert_select_rjs :show, "update"
437 assert_select_rjs :show, "update"
414 end
438 end
415
439
416 def test_post_edit_without_custom_fields_param
440 def test_post_edit_without_custom_fields_param
417 @request.session[:user_id] = 2
441 @request.session[:user_id] = 2
418 ActionMailer::Base.deliveries.clear
442 ActionMailer::Base.deliveries.clear
419
443
420 issue = Issue.find(1)
444 issue = Issue.find(1)
421 assert_equal '125', issue.custom_value_for(2).value
445 assert_equal '125', issue.custom_value_for(2).value
422 old_subject = issue.subject
446 old_subject = issue.subject
423 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
447 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
424
448
425 assert_difference('Journal.count') do
449 assert_difference('Journal.count') do
426 assert_difference('JournalDetail.count', 2) do
450 assert_difference('JournalDetail.count', 2) do
427 post :edit, :id => 1, :issue => {:subject => new_subject,
451 post :edit, :id => 1, :issue => {:subject => new_subject,
428 :priority_id => '6',
452 :priority_id => '6',
429 :category_id => '1' # no change
453 :category_id => '1' # no change
430 }
454 }
431 end
455 end
432 end
456 end
433 assert_redirected_to 'issues/show/1'
457 assert_redirected_to 'issues/show/1'
434 issue.reload
458 issue.reload
435 assert_equal new_subject, issue.subject
459 assert_equal new_subject, issue.subject
436 # Make sure custom fields were not cleared
460 # Make sure custom fields were not cleared
437 assert_equal '125', issue.custom_value_for(2).value
461 assert_equal '125', issue.custom_value_for(2).value
438
462
439 mail = ActionMailer::Base.deliveries.last
463 mail = ActionMailer::Base.deliveries.last
440 assert_kind_of TMail::Mail, mail
464 assert_kind_of TMail::Mail, mail
441 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
465 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
442 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
466 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
443 end
467 end
444
468
445 def test_post_edit_with_custom_field_change
469 def test_post_edit_with_custom_field_change
446 @request.session[:user_id] = 2
470 @request.session[:user_id] = 2
447 issue = Issue.find(1)
471 issue = Issue.find(1)
448 assert_equal '125', issue.custom_value_for(2).value
472 assert_equal '125', issue.custom_value_for(2).value
449
473
450 assert_difference('Journal.count') do
474 assert_difference('Journal.count') do
451 assert_difference('JournalDetail.count', 3) do
475 assert_difference('JournalDetail.count', 3) do
452 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
476 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
453 :priority_id => '6',
477 :priority_id => '6',
454 :category_id => '1', # no change
478 :category_id => '1', # no change
455 :custom_field_values => { '2' => 'New custom value' }
479 :custom_field_values => { '2' => 'New custom value' }
456 }
480 }
457 end
481 end
458 end
482 end
459 assert_redirected_to 'issues/show/1'
483 assert_redirected_to 'issues/show/1'
460 issue.reload
484 issue.reload
461 assert_equal 'New custom value', issue.custom_value_for(2).value
485 assert_equal 'New custom value', issue.custom_value_for(2).value
462
486
463 mail = ActionMailer::Base.deliveries.last
487 mail = ActionMailer::Base.deliveries.last
464 assert_kind_of TMail::Mail, mail
488 assert_kind_of TMail::Mail, mail
465 assert mail.body.include?("Searchable field changed from 125 to New custom value")
489 assert mail.body.include?("Searchable field changed from 125 to New custom value")
466 end
490 end
467
491
468 def test_post_edit_with_status_and_assignee_change
492 def test_post_edit_with_status_and_assignee_change
469 issue = Issue.find(1)
493 issue = Issue.find(1)
470 assert_equal 1, issue.status_id
494 assert_equal 1, issue.status_id
471 @request.session[:user_id] = 2
495 @request.session[:user_id] = 2
472 assert_difference('TimeEntry.count', 0) do
496 assert_difference('TimeEntry.count', 0) do
473 post :edit,
497 post :edit,
474 :id => 1,
498 :id => 1,
475 :issue => { :status_id => 2, :assigned_to_id => 3 },
499 :issue => { :status_id => 2, :assigned_to_id => 3 },
476 :notes => 'Assigned to dlopper',
500 :notes => 'Assigned to dlopper',
477 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
501 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
478 end
502 end
479 assert_redirected_to 'issues/show/1'
503 assert_redirected_to 'issues/show/1'
480 issue.reload
504 issue.reload
481 assert_equal 2, issue.status_id
505 assert_equal 2, issue.status_id
482 j = issue.journals.find(:first, :order => 'id DESC')
506 j = issue.journals.find(:first, :order => 'id DESC')
483 assert_equal 'Assigned to dlopper', j.notes
507 assert_equal 'Assigned to dlopper', j.notes
484 assert_equal 2, j.details.size
508 assert_equal 2, j.details.size
485
509
486 mail = ActionMailer::Base.deliveries.last
510 mail = ActionMailer::Base.deliveries.last
487 assert mail.body.include?("Status changed from New to Assigned")
511 assert mail.body.include?("Status changed from New to Assigned")
488 end
512 end
489
513
490 def test_post_edit_with_note_only
514 def test_post_edit_with_note_only
491 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
515 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
492 # anonymous user
516 # anonymous user
493 post :edit,
517 post :edit,
494 :id => 1,
518 :id => 1,
495 :notes => notes
519 :notes => notes
496 assert_redirected_to 'issues/show/1'
520 assert_redirected_to 'issues/show/1'
497 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
521 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
498 assert_equal notes, j.notes
522 assert_equal notes, j.notes
499 assert_equal 0, j.details.size
523 assert_equal 0, j.details.size
500 assert_equal User.anonymous, j.user
524 assert_equal User.anonymous, j.user
501
525
502 mail = ActionMailer::Base.deliveries.last
526 mail = ActionMailer::Base.deliveries.last
503 assert mail.body.include?(notes)
527 assert mail.body.include?(notes)
504 end
528 end
505
529
506 def test_post_edit_with_note_and_spent_time
530 def test_post_edit_with_note_and_spent_time
507 @request.session[:user_id] = 2
531 @request.session[:user_id] = 2
508 spent_hours_before = Issue.find(1).spent_hours
532 spent_hours_before = Issue.find(1).spent_hours
509 assert_difference('TimeEntry.count') do
533 assert_difference('TimeEntry.count') do
510 post :edit,
534 post :edit,
511 :id => 1,
535 :id => 1,
512 :notes => '2.5 hours added',
536 :notes => '2.5 hours added',
513 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
537 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
514 end
538 end
515 assert_redirected_to 'issues/show/1'
539 assert_redirected_to 'issues/show/1'
516
540
517 issue = Issue.find(1)
541 issue = Issue.find(1)
518
542
519 j = issue.journals.find(:first, :order => 'id DESC')
543 j = issue.journals.find(:first, :order => 'id DESC')
520 assert_equal '2.5 hours added', j.notes
544 assert_equal '2.5 hours added', j.notes
521 assert_equal 0, j.details.size
545 assert_equal 0, j.details.size
522
546
523 t = issue.time_entries.find(:first, :order => 'id DESC')
547 t = issue.time_entries.find(:first, :order => 'id DESC')
524 assert_not_nil t
548 assert_not_nil t
525 assert_equal 2.5, t.hours
549 assert_equal 2.5, t.hours
526 assert_equal spent_hours_before + 2.5, issue.spent_hours
550 assert_equal spent_hours_before + 2.5, issue.spent_hours
527 end
551 end
528
552
529 def test_post_edit_with_attachment_only
553 def test_post_edit_with_attachment_only
530 set_tmp_attachments_directory
554 set_tmp_attachments_directory
531
555
532 # Delete all fixtured journals, a race condition can occur causing the wrong
556 # Delete all fixtured journals, a race condition can occur causing the wrong
533 # journal to get fetched in the next find.
557 # journal to get fetched in the next find.
534 Journal.delete_all
558 Journal.delete_all
535
559
536 # anonymous user
560 # anonymous user
537 post :edit,
561 post :edit,
538 :id => 1,
562 :id => 1,
539 :notes => '',
563 :notes => '',
540 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
564 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
541 assert_redirected_to 'issues/show/1'
565 assert_redirected_to 'issues/show/1'
542 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
566 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
543 assert j.notes.blank?
567 assert j.notes.blank?
544 assert_equal 1, j.details.size
568 assert_equal 1, j.details.size
545 assert_equal 'testfile.txt', j.details.first.value
569 assert_equal 'testfile.txt', j.details.first.value
546 assert_equal User.anonymous, j.user
570 assert_equal User.anonymous, j.user
547
571
548 mail = ActionMailer::Base.deliveries.last
572 mail = ActionMailer::Base.deliveries.last
549 assert mail.body.include?('testfile.txt')
573 assert mail.body.include?('testfile.txt')
550 end
574 end
551
575
552 def test_post_edit_with_no_change
576 def test_post_edit_with_no_change
553 issue = Issue.find(1)
577 issue = Issue.find(1)
554 issue.journals.clear
578 issue.journals.clear
555 ActionMailer::Base.deliveries.clear
579 ActionMailer::Base.deliveries.clear
556
580
557 post :edit,
581 post :edit,
558 :id => 1,
582 :id => 1,
559 :notes => ''
583 :notes => ''
560 assert_redirected_to 'issues/show/1'
584 assert_redirected_to 'issues/show/1'
561
585
562 issue.reload
586 issue.reload
563 assert issue.journals.empty?
587 assert issue.journals.empty?
564 # No email should be sent
588 # No email should be sent
565 assert ActionMailer::Base.deliveries.empty?
589 assert ActionMailer::Base.deliveries.empty?
566 end
590 end
567
591
568 def test_bulk_edit
592 def test_bulk_edit
569 @request.session[:user_id] = 2
593 @request.session[:user_id] = 2
570 # update issues priority
594 # update issues priority
571 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
595 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
572 assert_response 302
596 assert_response 302
573 # check that the issues were updated
597 # check that the issues were updated
574 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
598 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
575 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
599 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
576 end
600 end
577
601
578 def test_bulk_unassign
602 def test_bulk_unassign
579 assert_not_nil Issue.find(2).assigned_to
603 assert_not_nil Issue.find(2).assigned_to
580 @request.session[:user_id] = 2
604 @request.session[:user_id] = 2
581 # unassign issues
605 # unassign issues
582 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
606 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
583 assert_response 302
607 assert_response 302
584 # check that the issues were updated
608 # check that the issues were updated
585 assert_nil Issue.find(2).assigned_to
609 assert_nil Issue.find(2).assigned_to
586 end
610 end
587
611
588 def test_move_one_issue_to_another_project
612 def test_move_one_issue_to_another_project
589 @request.session[:user_id] = 1
613 @request.session[:user_id] = 1
590 post :move, :id => 1, :new_project_id => 2
614 post :move, :id => 1, :new_project_id => 2
591 assert_redirected_to 'projects/ecookbook/issues'
615 assert_redirected_to 'projects/ecookbook/issues'
592 assert_equal 2, Issue.find(1).project_id
616 assert_equal 2, Issue.find(1).project_id
593 end
617 end
594
618
595 def test_bulk_move_to_another_project
619 def test_bulk_move_to_another_project
596 @request.session[:user_id] = 1
620 @request.session[:user_id] = 1
597 post :move, :ids => [1, 2], :new_project_id => 2
621 post :move, :ids => [1, 2], :new_project_id => 2
598 assert_redirected_to 'projects/ecookbook/issues'
622 assert_redirected_to 'projects/ecookbook/issues'
599 # Issues moved to project 2
623 # Issues moved to project 2
600 assert_equal 2, Issue.find(1).project_id
624 assert_equal 2, Issue.find(1).project_id
601 assert_equal 2, Issue.find(2).project_id
625 assert_equal 2, Issue.find(2).project_id
602 # No tracker change
626 # No tracker change
603 assert_equal 1, Issue.find(1).tracker_id
627 assert_equal 1, Issue.find(1).tracker_id
604 assert_equal 2, Issue.find(2).tracker_id
628 assert_equal 2, Issue.find(2).tracker_id
605 end
629 end
606
630
607 def test_bulk_move_to_another_tracker
631 def test_bulk_move_to_another_tracker
608 @request.session[:user_id] = 1
632 @request.session[:user_id] = 1
609 post :move, :ids => [1, 2], :new_tracker_id => 2
633 post :move, :ids => [1, 2], :new_tracker_id => 2
610 assert_redirected_to 'projects/ecookbook/issues'
634 assert_redirected_to 'projects/ecookbook/issues'
611 assert_equal 2, Issue.find(1).tracker_id
635 assert_equal 2, Issue.find(1).tracker_id
612 assert_equal 2, Issue.find(2).tracker_id
636 assert_equal 2, Issue.find(2).tracker_id
613 end
637 end
614
638
615 def test_context_menu_one_issue
639 def test_context_menu_one_issue
616 @request.session[:user_id] = 2
640 @request.session[:user_id] = 2
617 get :context_menu, :ids => [1]
641 get :context_menu, :ids => [1]
618 assert_response :success
642 assert_response :success
619 assert_template 'context_menu'
643 assert_template 'context_menu'
620 assert_tag :tag => 'a', :content => 'Edit',
644 assert_tag :tag => 'a', :content => 'Edit',
621 :attributes => { :href => '/issues/edit/1',
645 :attributes => { :href => '/issues/edit/1',
622 :class => 'icon-edit' }
646 :class => 'icon-edit' }
623 assert_tag :tag => 'a', :content => 'Closed',
647 assert_tag :tag => 'a', :content => 'Closed',
624 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
648 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
625 :class => '' }
649 :class => '' }
626 assert_tag :tag => 'a', :content => 'Immediate',
650 assert_tag :tag => 'a', :content => 'Immediate',
627 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
651 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
628 :class => '' }
652 :class => '' }
629 assert_tag :tag => 'a', :content => 'Dave Lopper',
653 assert_tag :tag => 'a', :content => 'Dave Lopper',
630 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
654 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
631 :class => '' }
655 :class => '' }
632 assert_tag :tag => 'a', :content => 'Copy',
656 assert_tag :tag => 'a', :content => 'Copy',
633 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
657 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
634 :class => 'icon-copy' }
658 :class => 'icon-copy' }
635 assert_tag :tag => 'a', :content => 'Move',
659 assert_tag :tag => 'a', :content => 'Move',
636 :attributes => { :href => '/issues/move?ids%5B%5D=1',
660 :attributes => { :href => '/issues/move?ids%5B%5D=1',
637 :class => 'icon-move' }
661 :class => 'icon-move' }
638 assert_tag :tag => 'a', :content => 'Delete',
662 assert_tag :tag => 'a', :content => 'Delete',
639 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
663 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
640 :class => 'icon-del' }
664 :class => 'icon-del' }
641 end
665 end
642
666
643 def test_context_menu_one_issue_by_anonymous
667 def test_context_menu_one_issue_by_anonymous
644 get :context_menu, :ids => [1]
668 get :context_menu, :ids => [1]
645 assert_response :success
669 assert_response :success
646 assert_template 'context_menu'
670 assert_template 'context_menu'
647 assert_tag :tag => 'a', :content => 'Delete',
671 assert_tag :tag => 'a', :content => 'Delete',
648 :attributes => { :href => '#',
672 :attributes => { :href => '#',
649 :class => 'icon-del disabled' }
673 :class => 'icon-del disabled' }
650 end
674 end
651
675
652 def test_context_menu_multiple_issues_of_same_project
676 def test_context_menu_multiple_issues_of_same_project
653 @request.session[:user_id] = 2
677 @request.session[:user_id] = 2
654 get :context_menu, :ids => [1, 2]
678 get :context_menu, :ids => [1, 2]
655 assert_response :success
679 assert_response :success
656 assert_template 'context_menu'
680 assert_template 'context_menu'
657 assert_tag :tag => 'a', :content => 'Edit',
681 assert_tag :tag => 'a', :content => 'Edit',
658 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
682 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
659 :class => 'icon-edit' }
683 :class => 'icon-edit' }
660 assert_tag :tag => 'a', :content => 'Immediate',
684 assert_tag :tag => 'a', :content => 'Immediate',
661 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
685 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
662 :class => '' }
686 :class => '' }
663 assert_tag :tag => 'a', :content => 'Dave Lopper',
687 assert_tag :tag => 'a', :content => 'Dave Lopper',
664 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
688 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
665 :class => '' }
689 :class => '' }
666 assert_tag :tag => 'a', :content => 'Move',
690 assert_tag :tag => 'a', :content => 'Move',
667 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
691 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
668 :class => 'icon-move' }
692 :class => 'icon-move' }
669 assert_tag :tag => 'a', :content => 'Delete',
693 assert_tag :tag => 'a', :content => 'Delete',
670 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
694 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
671 :class => 'icon-del' }
695 :class => 'icon-del' }
672 end
696 end
673
697
674 def test_context_menu_multiple_issues_of_different_project
698 def test_context_menu_multiple_issues_of_different_project
675 @request.session[:user_id] = 2
699 @request.session[:user_id] = 2
676 get :context_menu, :ids => [1, 2, 4]
700 get :context_menu, :ids => [1, 2, 4]
677 assert_response :success
701 assert_response :success
678 assert_template 'context_menu'
702 assert_template 'context_menu'
679 assert_tag :tag => 'a', :content => 'Delete',
703 assert_tag :tag => 'a', :content => 'Delete',
680 :attributes => { :href => '#',
704 :attributes => { :href => '#',
681 :class => 'icon-del disabled' }
705 :class => 'icon-del disabled' }
682 end
706 end
683
707
684 def test_destroy_issue_with_no_time_entries
708 def test_destroy_issue_with_no_time_entries
685 assert_nil TimeEntry.find_by_issue_id(2)
709 assert_nil TimeEntry.find_by_issue_id(2)
686 @request.session[:user_id] = 2
710 @request.session[:user_id] = 2
687 post :destroy, :id => 2
711 post :destroy, :id => 2
688 assert_redirected_to 'projects/ecookbook/issues'
712 assert_redirected_to 'projects/ecookbook/issues'
689 assert_nil Issue.find_by_id(2)
713 assert_nil Issue.find_by_id(2)
690 end
714 end
691
715
692 def test_destroy_issues_with_time_entries
716 def test_destroy_issues_with_time_entries
693 @request.session[:user_id] = 2
717 @request.session[:user_id] = 2
694 post :destroy, :ids => [1, 3]
718 post :destroy, :ids => [1, 3]
695 assert_response :success
719 assert_response :success
696 assert_template 'destroy'
720 assert_template 'destroy'
697 assert_not_nil assigns(:hours)
721 assert_not_nil assigns(:hours)
698 assert Issue.find_by_id(1) && Issue.find_by_id(3)
722 assert Issue.find_by_id(1) && Issue.find_by_id(3)
699 end
723 end
700
724
701 def test_destroy_issues_and_destroy_time_entries
725 def test_destroy_issues_and_destroy_time_entries
702 @request.session[:user_id] = 2
726 @request.session[:user_id] = 2
703 post :destroy, :ids => [1, 3], :todo => 'destroy'
727 post :destroy, :ids => [1, 3], :todo => 'destroy'
704 assert_redirected_to 'projects/ecookbook/issues'
728 assert_redirected_to 'projects/ecookbook/issues'
705 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
729 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
706 assert_nil TimeEntry.find_by_id([1, 2])
730 assert_nil TimeEntry.find_by_id([1, 2])
707 end
731 end
708
732
709 def test_destroy_issues_and_assign_time_entries_to_project
733 def test_destroy_issues_and_assign_time_entries_to_project
710 @request.session[:user_id] = 2
734 @request.session[:user_id] = 2
711 post :destroy, :ids => [1, 3], :todo => 'nullify'
735 post :destroy, :ids => [1, 3], :todo => 'nullify'
712 assert_redirected_to 'projects/ecookbook/issues'
736 assert_redirected_to 'projects/ecookbook/issues'
713 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
737 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
714 assert_nil TimeEntry.find(1).issue_id
738 assert_nil TimeEntry.find(1).issue_id
715 assert_nil TimeEntry.find(2).issue_id
739 assert_nil TimeEntry.find(2).issue_id
716 end
740 end
717
741
718 def test_destroy_issues_and_reassign_time_entries_to_another_issue
742 def test_destroy_issues_and_reassign_time_entries_to_another_issue
719 @request.session[:user_id] = 2
743 @request.session[:user_id] = 2
720 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
744 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
721 assert_redirected_to 'projects/ecookbook/issues'
745 assert_redirected_to 'projects/ecookbook/issues'
722 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
746 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
723 assert_equal 2, TimeEntry.find(1).issue_id
747 assert_equal 2, TimeEntry.find(1).issue_id
724 assert_equal 2, TimeEntry.find(2).issue_id
748 assert_equal 2, TimeEntry.find(2).issue_id
725 end
749 end
726
750
727 def test_destroy_attachment
751 def test_destroy_attachment
728 issue = Issue.find(3)
752 issue = Issue.find(3)
729 a = issue.attachments.size
753 a = issue.attachments.size
730 @request.session[:user_id] = 2
754 @request.session[:user_id] = 2
731 post :destroy_attachment, :id => 3, :attachment_id => 1
755 post :destroy_attachment, :id => 3, :attachment_id => 1
732 assert_redirected_to 'issues/show/3'
756 assert_redirected_to 'issues/show/3'
733 assert_nil Attachment.find_by_id(1)
757 assert_nil Attachment.find_by_id(1)
734 issue.reload
758 issue.reload
735 assert_equal((a-1), issue.attachments.size)
759 assert_equal((a-1), issue.attachments.size)
736 j = issue.journals.find(:first, :order => 'created_on DESC')
760 j = issue.journals.find(:first, :order => 'created_on DESC')
737 assert_equal 'attachment', j.details.first.property
761 assert_equal 'attachment', j.details.first.property
738 end
762 end
739 end
763 end
@@ -1,62 +1,68
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class RepositoryDarcsTest < Test::Unit::TestCase
20 class RepositoryDarcsTest < Test::Unit::TestCase
21 fixtures :projects
21 fixtures :projects
22
22
23 # No '..' in the repository path
23 # No '..' in the repository path
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository'
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository'
25
25
26 def setup
26 def setup
27 @project = Project.find(1)
27 @project = Project.find(1)
28 assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH)
28 assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH)
29 end
29 end
30
30
31 if File.directory?(REPOSITORY_PATH)
31 if File.directory?(REPOSITORY_PATH)
32 def test_fetch_changesets_from_scratch
32 def test_fetch_changesets_from_scratch
33 @repository.fetch_changesets
33 @repository.fetch_changesets
34 @repository.reload
34 @repository.reload
35
35
36 assert_equal 6, @repository.changesets.count
36 assert_equal 6, @repository.changesets.count
37 assert_equal 13, @repository.changes.count
37 assert_equal 13, @repository.changes.count
38 assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments
38 assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments
39 end
39 end
40
40
41 def test_fetch_changesets_incremental
41 def test_fetch_changesets_incremental
42 @repository.fetch_changesets
42 @repository.fetch_changesets
43 # Remove changesets with revision > 3
43 # Remove changesets with revision > 3
44 @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3}
44 @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3}
45 @repository.reload
45 @repository.reload
46 assert_equal 3, @repository.changesets.count
46 assert_equal 3, @repository.changesets.count
47
47
48 @repository.fetch_changesets
48 @repository.fetch_changesets
49 assert_equal 6, @repository.changesets.count
49 assert_equal 6, @repository.changesets.count
50 end
50 end
51
51
52 def test_deleted_files_should_not_be_listed
53 entries = @repository.entries('sources')
54 assert entries.detect {|e| e.name == 'watchers_controller.rb'}
55 assert_nil entries.detect {|e| e.name == 'welcome_controller.rb'}
56 end
57
52 def test_cat
58 def test_cat
53 @repository.fetch_changesets
59 @repository.fetch_changesets
54 cat = @repository.cat("sources/welcome_controller.rb", 2)
60 cat = @repository.cat("sources/welcome_controller.rb", 2)
55 assert_not_nil cat
61 assert_not_nil cat
56 assert cat.include?('class WelcomeController < ApplicationController')
62 assert cat.include?('class WelcomeController < ApplicationController')
57 end
63 end
58 else
64 else
59 puts "Darcs test repository NOT FOUND. Skipping unit tests !!!"
65 puts "Darcs test repository NOT FOUND. Skipping unit tests !!!"
60 def test_fake; assert true end
66 def test_fake; assert true end
61 end
67 end
62 end
68 end
@@ -1,69 +1,71
1 # ActsAsWatchable
1 # ActsAsWatchable
2 module Redmine
2 module Redmine
3 module Acts
3 module Acts
4 module Watchable
4 module Watchable
5 def self.included(base)
5 def self.included(base)
6 base.extend ClassMethods
6 base.extend ClassMethods
7 end
7 end
8
8
9 module ClassMethods
9 module ClassMethods
10 def acts_as_watchable(options = {})
10 def acts_as_watchable(options = {})
11 return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods)
11 return if self.included_modules.include?(Redmine::Acts::Watchable::InstanceMethods)
12 send :include, Redmine::Acts::Watchable::InstanceMethods
12 send :include, Redmine::Acts::Watchable::InstanceMethods
13
13
14 class_eval do
14 class_eval do
15 has_many :watchers, :as => :watchable, :dependent => :delete_all
15 has_many :watchers, :as => :watchable, :dependent => :delete_all
16 has_many :watcher_users, :through => :watchers, :source => :user
16 has_many :watcher_users, :through => :watchers, :source => :user
17
18 attr_protected :watcher_ids, :watcher_user_ids
17 end
19 end
18 end
20 end
19 end
21 end
20
22
21 module InstanceMethods
23 module InstanceMethods
22 def self.included(base)
24 def self.included(base)
23 base.extend ClassMethods
25 base.extend ClassMethods
24 end
26 end
25
27
26 # Returns an array of users that are proposed as watchers
28 # Returns an array of users that are proposed as watchers
27 def addable_watcher_users
29 def addable_watcher_users
28 self.project.users.sort - self.watcher_users
30 self.project.users.sort - self.watcher_users
29 end
31 end
30
32
31 # Adds user as a watcher
33 # Adds user as a watcher
32 def add_watcher(user)
34 def add_watcher(user)
33 self.watchers << Watcher.new(:user => user)
35 self.watchers << Watcher.new(:user => user)
34 end
36 end
35
37
36 # Removes user from the watchers list
38 # Removes user from the watchers list
37 def remove_watcher(user)
39 def remove_watcher(user)
38 return nil unless user && user.is_a?(User)
40 return nil unless user && user.is_a?(User)
39 Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}"
41 Watcher.delete_all "watchable_type = '#{self.class}' AND watchable_id = #{self.id} AND user_id = #{user.id}"
40 end
42 end
41
43
42 # Adds/removes watcher
44 # Adds/removes watcher
43 def set_watcher(user, watching=true)
45 def set_watcher(user, watching=true)
44 watching ? add_watcher(user) : remove_watcher(user)
46 watching ? add_watcher(user) : remove_watcher(user)
45 end
47 end
46
48
47 # Returns if object is watched by user
49 # Returns if object is watched by user
48 def watched_by?(user)
50 def watched_by?(user)
49 !self.watchers.find(:first,
51 !self.watchers.find(:first,
50 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil?
52 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id]).nil?
51 end
53 end
52
54
53 # Returns an array of watchers' email addresses
55 # Returns an array of watchers' email addresses
54 def watcher_recipients
56 def watcher_recipients
55 self.watchers.collect { |w| w.user.mail if w.user.active? }.compact
57 self.watchers.collect { |w| w.user.mail if w.user.active? }.compact
56 end
58 end
57
59
58 module ClassMethods
60 module ClassMethods
59 # Returns the objects that are watched by user
61 # Returns the objects that are watched by user
60 def watched_by(user)
62 def watched_by(user)
61 find(:all,
63 find(:all,
62 :include => :watchers,
64 :include => :watchers,
63 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id])
65 :conditions => ["#{Watcher.table_name}.user_id = ?", user.id])
64 end
66 end
65 end
67 end
66 end
68 end
67 end
69 end
68 end
70 end
69 end
71 end
General Comments 0
You need to be logged in to leave comments. Login now