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