##// END OF EJS Templates
Ability to define commit keywords per tracker (#7590)....
Jean-Philippe Lang -
r11978:b6cb7aa8e3b9
parent child
Show More
@@ -1,74 +1,74
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class SettingsController < ApplicationController
18 class SettingsController < ApplicationController
19 layout 'admin'
19 layout 'admin'
20 menu_item :plugins, :only => :plugin
20 menu_item :plugins, :only => :plugin
21
21
22 helper :queries
22 helper :queries
23
23
24 before_filter :require_admin
24 before_filter :require_admin
25
25
26 def index
26 def index
27 edit
27 edit
28 render :action => 'edit'
28 render :action => 'edit'
29 end
29 end
30
30
31 def edit
31 def edit
32 @notifiables = Redmine::Notifiable.all
32 @notifiables = Redmine::Notifiable.all
33 if request.post? && params[:settings] && params[:settings].is_a?(Hash)
33 if request.post? && params[:settings] && params[:settings].is_a?(Hash)
34 settings = (params[:settings] || {}).dup.symbolize_keys
34 settings = (params[:settings] || {}).dup.symbolize_keys
35 settings.each do |name, value|
35 settings.each do |name, value|
36 Setting.set_from_params name, value
36 Setting.set_from_params name, value
37 end
37 end
38 flash[:notice] = l(:notice_successful_update)
38 flash[:notice] = l(:notice_successful_update)
39 redirect_to settings_path(:tab => params[:tab])
39 redirect_to settings_path(:tab => params[:tab])
40 else
40 else
41 @options = {}
41 @options = {}
42 user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
42 user_format = User::USER_FORMATS.collect{|key, value| [key, value[:setting_order]]}.sort{|a, b| a[1] <=> b[1]}
43 @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
43 @options[:user_format] = user_format.collect{|f| [User.current.name(f[0]), f[0].to_s]}
44 @deliveries = ActionMailer::Base.perform_deliveries
44 @deliveries = ActionMailer::Base.perform_deliveries
45
45
46 @guessed_host_and_path = request.host_with_port.dup
46 @guessed_host_and_path = request.host_with_port.dup
47 @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
47 @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank?
48
48
49 @commit_update_keywords = Setting.commit_update_keywords.dup
49 @commit_update_keywords = Setting.commit_update_keywords.dup
50 @commit_update_keywords[''] = {} if @commit_update_keywords.blank?
50 @commit_update_keywords << {} if @commit_update_keywords.blank?
51
51
52 Redmine::Themes.rescan
52 Redmine::Themes.rescan
53 end
53 end
54 end
54 end
55
55
56 def plugin
56 def plugin
57 @plugin = Redmine::Plugin.find(params[:id])
57 @plugin = Redmine::Plugin.find(params[:id])
58 unless @plugin.configurable?
58 unless @plugin.configurable?
59 render_404
59 render_404
60 return
60 return
61 end
61 end
62
62
63 if request.post?
63 if request.post?
64 Setting.send "plugin_#{@plugin.id}=", params[:settings]
64 Setting.send "plugin_#{@plugin.id}=", params[:settings]
65 flash[:notice] = l(:notice_successful_update)
65 flash[:notice] = l(:notice_successful_update)
66 redirect_to plugin_settings_path(@plugin)
66 redirect_to plugin_settings_path(@plugin)
67 else
67 else
68 @partial = @plugin.settings[:partial]
68 @partial = @plugin.settings[:partial]
69 @settings = Setting.send "plugin_#{@plugin.id}"
69 @settings = Setting.send "plugin_#{@plugin.id}"
70 end
70 end
71 rescue Redmine::PluginNotFound
71 rescue Redmine::PluginNotFound
72 render_404
72 render_404
73 end
73 end
74 end
74 end
@@ -1,276 +1,278
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Changeset < ActiveRecord::Base
18 class Changeset < ActiveRecord::Base
19 belongs_to :repository
19 belongs_to :repository
20 belongs_to :user
20 belongs_to :user
21 has_many :filechanges, :class_name => 'Change', :dependent => :delete_all
21 has_many :filechanges, :class_name => 'Change', :dependent => :delete_all
22 has_and_belongs_to_many :issues
22 has_and_belongs_to_many :issues
23 has_and_belongs_to_many :parents,
23 has_and_belongs_to_many :parents,
24 :class_name => "Changeset",
24 :class_name => "Changeset",
25 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
25 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
26 :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id'
26 :association_foreign_key => 'parent_id', :foreign_key => 'changeset_id'
27 has_and_belongs_to_many :children,
27 has_and_belongs_to_many :children,
28 :class_name => "Changeset",
28 :class_name => "Changeset",
29 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
29 :join_table => "#{table_name_prefix}changeset_parents#{table_name_suffix}",
30 :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
30 :association_foreign_key => 'changeset_id', :foreign_key => 'parent_id'
31
31
32 acts_as_event :title => Proc.new {|o| o.title},
32 acts_as_event :title => Proc.new {|o| o.title},
33 :description => :long_comments,
33 :description => :long_comments,
34 :datetime => :committed_on,
34 :datetime => :committed_on,
35 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}}
35 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}}
36
36
37 acts_as_searchable :columns => 'comments',
37 acts_as_searchable :columns => 'comments',
38 :include => {:repository => :project},
38 :include => {:repository => :project},
39 :project_key => "#{Repository.table_name}.project_id",
39 :project_key => "#{Repository.table_name}.project_id",
40 :date_column => 'committed_on'
40 :date_column => 'committed_on'
41
41
42 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
42 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
43 :author_key => :user_id,
43 :author_key => :user_id,
44 :find_options => {:include => [:user, {:repository => :project}]}
44 :find_options => {:include => [:user, {:repository => :project}]}
45
45
46 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
46 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
47 validates_uniqueness_of :revision, :scope => :repository_id
47 validates_uniqueness_of :revision, :scope => :repository_id
48 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
48 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
49
49
50 scope :visible, lambda {|*args|
50 scope :visible, lambda {|*args|
51 includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
51 includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args))
52 }
52 }
53
53
54 after_create :scan_for_issues
54 after_create :scan_for_issues
55 before_create :before_create_cs
55 before_create :before_create_cs
56
56
57 def revision=(r)
57 def revision=(r)
58 write_attribute :revision, (r.nil? ? nil : r.to_s)
58 write_attribute :revision, (r.nil? ? nil : r.to_s)
59 end
59 end
60
60
61 # Returns the identifier of this changeset; depending on repository backends
61 # Returns the identifier of this changeset; depending on repository backends
62 def identifier
62 def identifier
63 if repository.class.respond_to? :changeset_identifier
63 if repository.class.respond_to? :changeset_identifier
64 repository.class.changeset_identifier self
64 repository.class.changeset_identifier self
65 else
65 else
66 revision.to_s
66 revision.to_s
67 end
67 end
68 end
68 end
69
69
70 def committed_on=(date)
70 def committed_on=(date)
71 self.commit_date = date
71 self.commit_date = date
72 super
72 super
73 end
73 end
74
74
75 # Returns the readable identifier
75 # Returns the readable identifier
76 def format_identifier
76 def format_identifier
77 if repository.class.respond_to? :format_changeset_identifier
77 if repository.class.respond_to? :format_changeset_identifier
78 repository.class.format_changeset_identifier self
78 repository.class.format_changeset_identifier self
79 else
79 else
80 identifier
80 identifier
81 end
81 end
82 end
82 end
83
83
84 def project
84 def project
85 repository.project
85 repository.project
86 end
86 end
87
87
88 def author
88 def author
89 user || committer.to_s.split('<').first
89 user || committer.to_s.split('<').first
90 end
90 end
91
91
92 def before_create_cs
92 def before_create_cs
93 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
93 self.committer = self.class.to_utf8(self.committer, repository.repo_log_encoding)
94 self.comments = self.class.normalize_comments(
94 self.comments = self.class.normalize_comments(
95 self.comments, repository.repo_log_encoding)
95 self.comments, repository.repo_log_encoding)
96 self.user = repository.find_committer_user(self.committer)
96 self.user = repository.find_committer_user(self.committer)
97 end
97 end
98
98
99 def scan_for_issues
99 def scan_for_issues
100 scan_comment_for_issue_ids
100 scan_comment_for_issue_ids
101 end
101 end
102
102
103 TIMELOG_RE = /
103 TIMELOG_RE = /
104 (
104 (
105 ((\d+)(h|hours?))((\d+)(m|min)?)?
105 ((\d+)(h|hours?))((\d+)(m|min)?)?
106 |
106 |
107 ((\d+)(h|hours?|m|min))
107 ((\d+)(h|hours?|m|min))
108 |
108 |
109 (\d+):(\d+)
109 (\d+):(\d+)
110 |
110 |
111 (\d+([\.,]\d+)?)h?
111 (\d+([\.,]\d+)?)h?
112 )
112 )
113 /x
113 /x
114
114
115 def scan_comment_for_issue_ids
115 def scan_comment_for_issue_ids
116 return if comments.blank?
116 return if comments.blank?
117 # keywords used to reference issues
117 # keywords used to reference issues
118 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
118 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
119 ref_keywords_any = ref_keywords.delete('*')
119 ref_keywords_any = ref_keywords.delete('*')
120 # keywords used to fix issues
120 # keywords used to fix issues
121 fix_keywords = Setting.commit_update_by_keyword.keys
121 fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact
122
122
123 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
123 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
124
124
125 referenced_issues = []
125 referenced_issues = []
126
126
127 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
127 comments.scan(/([\s\(\[,-]|^)((#{kw_regexp})[\s:]+)?(#\d+(\s+@#{TIMELOG_RE})?([\s,;&]+#\d+(\s+@#{TIMELOG_RE})?)*)(?=[[:punct:]]|\s|<|$)/i) do |match|
128 action, refs = match[2].to_s.downcase, match[3]
128 action, refs = match[2].to_s.downcase, match[3]
129 next unless action.present? || ref_keywords_any
129 next unless action.present? || ref_keywords_any
130
130
131 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
131 refs.scan(/#(\d+)(\s+@#{TIMELOG_RE})?/).each do |m|
132 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
132 issue, hours = find_referenced_issue_by_id(m[0].to_i), m[2]
133 if issue
133 if issue
134 referenced_issues << issue
134 referenced_issues << issue
135 # Don't update issues or log time when importing old commits
135 # Don't update issues or log time when importing old commits
136 unless repository.created_on && committed_on && committed_on < repository.created_on
136 unless repository.created_on && committed_on && committed_on < repository.created_on
137 fix_issue(issue, action) if fix_keywords.include?(action)
137 fix_issue(issue, action) if fix_keywords.include?(action)
138 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
138 log_time(issue, hours) if hours && Setting.commit_logtime_enabled?
139 end
139 end
140 end
140 end
141 end
141 end
142 end
142 end
143
143
144 referenced_issues.uniq!
144 referenced_issues.uniq!
145 self.issues = referenced_issues unless referenced_issues.empty?
145 self.issues = referenced_issues unless referenced_issues.empty?
146 end
146 end
147
147
148 def short_comments
148 def short_comments
149 @short_comments || split_comments.first
149 @short_comments || split_comments.first
150 end
150 end
151
151
152 def long_comments
152 def long_comments
153 @long_comments || split_comments.last
153 @long_comments || split_comments.last
154 end
154 end
155
155
156 def text_tag(ref_project=nil)
156 def text_tag(ref_project=nil)
157 tag = if scmid?
157 tag = if scmid?
158 "commit:#{scmid}"
158 "commit:#{scmid}"
159 else
159 else
160 "r#{revision}"
160 "r#{revision}"
161 end
161 end
162 if repository && repository.identifier.present?
162 if repository && repository.identifier.present?
163 tag = "#{repository.identifier}|#{tag}"
163 tag = "#{repository.identifier}|#{tag}"
164 end
164 end
165 if ref_project && project && ref_project != project
165 if ref_project && project && ref_project != project
166 tag = "#{project.identifier}:#{tag}"
166 tag = "#{project.identifier}:#{tag}"
167 end
167 end
168 tag
168 tag
169 end
169 end
170
170
171 # Returns the title used for the changeset in the activity/search results
171 # Returns the title used for the changeset in the activity/search results
172 def title
172 def title
173 repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
173 repo = (repository && repository.identifier.present?) ? " (#{repository.identifier})" : ''
174 comm = short_comments.blank? ? '' : (': ' + short_comments)
174 comm = short_comments.blank? ? '' : (': ' + short_comments)
175 "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
175 "#{l(:label_revision)} #{format_identifier}#{repo}#{comm}"
176 end
176 end
177
177
178 # Returns the previous changeset
178 # Returns the previous changeset
179 def previous
179 def previous
180 @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first
180 @previous ||= Changeset.where(["id < ? AND repository_id = ?", id, repository_id]).order('id DESC').first
181 end
181 end
182
182
183 # Returns the next changeset
183 # Returns the next changeset
184 def next
184 def next
185 @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first
185 @next ||= Changeset.where(["id > ? AND repository_id = ?", id, repository_id]).order('id ASC').first
186 end
186 end
187
187
188 # Creates a new Change from it's common parameters
188 # Creates a new Change from it's common parameters
189 def create_change(change)
189 def create_change(change)
190 Change.create(:changeset => self,
190 Change.create(:changeset => self,
191 :action => change[:action],
191 :action => change[:action],
192 :path => change[:path],
192 :path => change[:path],
193 :from_path => change[:from_path],
193 :from_path => change[:from_path],
194 :from_revision => change[:from_revision])
194 :from_revision => change[:from_revision])
195 end
195 end
196
196
197 # Finds an issue that can be referenced by the commit message
197 # Finds an issue that can be referenced by the commit message
198 def find_referenced_issue_by_id(id)
198 def find_referenced_issue_by_id(id)
199 return nil if id.blank?
199 return nil if id.blank?
200 issue = Issue.find_by_id(id.to_i, :include => :project)
200 issue = Issue.find_by_id(id.to_i, :include => :project)
201 if Setting.commit_cross_project_ref?
201 if Setting.commit_cross_project_ref?
202 # all issues can be referenced/fixed
202 # all issues can be referenced/fixed
203 elsif issue
203 elsif issue
204 # issue that belong to the repository project, a subproject or a parent project only
204 # issue that belong to the repository project, a subproject or a parent project only
205 unless issue.project &&
205 unless issue.project &&
206 (project == issue.project || project.is_ancestor_of?(issue.project) ||
206 (project == issue.project || project.is_ancestor_of?(issue.project) ||
207 project.is_descendant_of?(issue.project))
207 project.is_descendant_of?(issue.project))
208 issue = nil
208 issue = nil
209 end
209 end
210 end
210 end
211 issue
211 issue
212 end
212 end
213
213
214 private
214 private
215
215
216 # Updates the +issue+ according to +action+
216 # Updates the +issue+ according to +action+
217 def fix_issue(issue, action)
217 def fix_issue(issue, action)
218 updates = Setting.commit_update_by_keyword[action]
219 return unless updates.is_a?(Hash)
220
221 # the issue may have been updated by the closure of another one (eg. duplicate)
218 # the issue may have been updated by the closure of another one (eg. duplicate)
222 issue.reload
219 issue.reload
223 # don't change the status is the issue is closed
220 # don't change the status is the issue is closed
224 return if issue.status && issue.status.is_closed?
221 return if issue.status && issue.status.is_closed?
225
222
226 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
223 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project)))
227 issue.assign_attributes updates.slice(*Issue.attribute_names)
224 rule = Setting.commit_update_keywords_array.detect do |rule|
225 rule['keywords'].include?(action) && (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s)
226 end
227 if rule
228 issue.assign_attributes rule.slice(*Issue.attribute_names)
229 end
228 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
230 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
229 { :changeset => self, :issue => issue, :action => action })
231 { :changeset => self, :issue => issue, :action => action })
230 unless issue.save
232 unless issue.save
231 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
233 logger.warn("Issue ##{issue.id} could not be saved by changeset #{id}: #{issue.errors.full_messages}") if logger
232 end
234 end
233 issue
235 issue
234 end
236 end
235
237
236 def log_time(issue, hours)
238 def log_time(issue, hours)
237 time_entry = TimeEntry.new(
239 time_entry = TimeEntry.new(
238 :user => user,
240 :user => user,
239 :hours => hours,
241 :hours => hours,
240 :issue => issue,
242 :issue => issue,
241 :spent_on => commit_date,
243 :spent_on => commit_date,
242 :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
244 :comments => l(:text_time_logged_by_changeset, :value => text_tag(issue.project),
243 :locale => Setting.default_language)
245 :locale => Setting.default_language)
244 )
246 )
245 time_entry.activity = log_time_activity unless log_time_activity.nil?
247 time_entry.activity = log_time_activity unless log_time_activity.nil?
246
248
247 unless time_entry.save
249 unless time_entry.save
248 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
250 logger.warn("TimeEntry could not be created by changeset #{id}: #{time_entry.errors.full_messages}") if logger
249 end
251 end
250 time_entry
252 time_entry
251 end
253 end
252
254
253 def log_time_activity
255 def log_time_activity
254 if Setting.commit_logtime_activity_id.to_i > 0
256 if Setting.commit_logtime_activity_id.to_i > 0
255 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
257 TimeEntryActivity.find_by_id(Setting.commit_logtime_activity_id.to_i)
256 end
258 end
257 end
259 end
258
260
259 def split_comments
261 def split_comments
260 comments =~ /\A(.+?)\r?\n(.*)$/m
262 comments =~ /\A(.+?)\r?\n(.*)$/m
261 @short_comments = $1 || comments
263 @short_comments = $1 || comments
262 @long_comments = $2.to_s.strip
264 @long_comments = $2.to_s.strip
263 return @short_comments, @long_comments
265 return @short_comments, @long_comments
264 end
266 end
265
267
266 public
268 public
267
269
268 # Strips and reencodes a commit log before insertion into the database
270 # Strips and reencodes a commit log before insertion into the database
269 def self.normalize_comments(str, encoding)
271 def self.normalize_comments(str, encoding)
270 Changeset.to_utf8(str.to_s.strip, encoding)
272 Changeset.to_utf8(str.to_s.strip, encoding)
271 end
273 end
272
274
273 def self.to_utf8(str, encoding)
275 def self.to_utf8(str, encoding)
274 Redmine::CodesetUtil.to_utf8(str, encoding)
276 Redmine::CodesetUtil.to_utf8(str, encoding)
275 end
277 end
276 end
278 end
@@ -1,250 +1,250
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Setting < ActiveRecord::Base
18 class Setting < ActiveRecord::Base
19
19
20 DATE_FORMATS = [
20 DATE_FORMATS = [
21 '%Y-%m-%d',
21 '%Y-%m-%d',
22 '%d/%m/%Y',
22 '%d/%m/%Y',
23 '%d.%m.%Y',
23 '%d.%m.%Y',
24 '%d-%m-%Y',
24 '%d-%m-%Y',
25 '%m/%d/%Y',
25 '%m/%d/%Y',
26 '%d %b %Y',
26 '%d %b %Y',
27 '%d %B %Y',
27 '%d %B %Y',
28 '%b %d, %Y',
28 '%b %d, %Y',
29 '%B %d, %Y'
29 '%B %d, %Y'
30 ]
30 ]
31
31
32 TIME_FORMATS = [
32 TIME_FORMATS = [
33 '%H:%M',
33 '%H:%M',
34 '%I:%M %p'
34 '%I:%M %p'
35 ]
35 ]
36
36
37 ENCODINGS = %w(US-ASCII
37 ENCODINGS = %w(US-ASCII
38 windows-1250
38 windows-1250
39 windows-1251
39 windows-1251
40 windows-1252
40 windows-1252
41 windows-1253
41 windows-1253
42 windows-1254
42 windows-1254
43 windows-1255
43 windows-1255
44 windows-1256
44 windows-1256
45 windows-1257
45 windows-1257
46 windows-1258
46 windows-1258
47 windows-31j
47 windows-31j
48 ISO-2022-JP
48 ISO-2022-JP
49 ISO-2022-KR
49 ISO-2022-KR
50 ISO-8859-1
50 ISO-8859-1
51 ISO-8859-2
51 ISO-8859-2
52 ISO-8859-3
52 ISO-8859-3
53 ISO-8859-4
53 ISO-8859-4
54 ISO-8859-5
54 ISO-8859-5
55 ISO-8859-6
55 ISO-8859-6
56 ISO-8859-7
56 ISO-8859-7
57 ISO-8859-8
57 ISO-8859-8
58 ISO-8859-9
58 ISO-8859-9
59 ISO-8859-13
59 ISO-8859-13
60 ISO-8859-15
60 ISO-8859-15
61 KOI8-R
61 KOI8-R
62 UTF-8
62 UTF-8
63 UTF-16
63 UTF-16
64 UTF-16BE
64 UTF-16BE
65 UTF-16LE
65 UTF-16LE
66 EUC-JP
66 EUC-JP
67 Shift_JIS
67 Shift_JIS
68 CP932
68 CP932
69 GB18030
69 GB18030
70 GBK
70 GBK
71 ISCII91
71 ISCII91
72 EUC-KR
72 EUC-KR
73 Big5
73 Big5
74 Big5-HKSCS
74 Big5-HKSCS
75 TIS-620)
75 TIS-620)
76
76
77 cattr_accessor :available_settings
77 cattr_accessor :available_settings
78 @@available_settings = YAML::load(File.open("#{Rails.root}/config/settings.yml"))
78 @@available_settings = YAML::load(File.open("#{Rails.root}/config/settings.yml"))
79 Redmine::Plugin.all.each do |plugin|
79 Redmine::Plugin.all.each do |plugin|
80 next unless plugin.settings
80 next unless plugin.settings
81 @@available_settings["plugin_#{plugin.id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
81 @@available_settings["plugin_#{plugin.id}"] = {'default' => plugin.settings[:default], 'serialized' => true}
82 end
82 end
83
83
84 validates_uniqueness_of :name
84 validates_uniqueness_of :name
85 validates_inclusion_of :name, :in => @@available_settings.keys
85 validates_inclusion_of :name, :in => @@available_settings.keys
86 validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
86 validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| @@available_settings[setting.name]['format'] == 'int' }
87
87
88 # Hash used to cache setting values
88 # Hash used to cache setting values
89 @cached_settings = {}
89 @cached_settings = {}
90 @cached_cleared_on = Time.now
90 @cached_cleared_on = Time.now
91
91
92 def value
92 def value
93 v = read_attribute(:value)
93 v = read_attribute(:value)
94 # Unserialize serialized settings
94 # Unserialize serialized settings
95 v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String)
95 v = YAML::load(v) if @@available_settings[name]['serialized'] && v.is_a?(String)
96 v = v.to_sym if @@available_settings[name]['format'] == 'symbol' && !v.blank?
96 v = v.to_sym if @@available_settings[name]['format'] == 'symbol' && !v.blank?
97 v
97 v
98 end
98 end
99
99
100 def value=(v)
100 def value=(v)
101 v = v.to_yaml if v && @@available_settings[name] && @@available_settings[name]['serialized']
101 v = v.to_yaml if v && @@available_settings[name] && @@available_settings[name]['serialized']
102 write_attribute(:value, v.to_s)
102 write_attribute(:value, v.to_s)
103 end
103 end
104
104
105 # Returns the value of the setting named name
105 # Returns the value of the setting named name
106 def self.[](name)
106 def self.[](name)
107 v = @cached_settings[name]
107 v = @cached_settings[name]
108 v ? v : (@cached_settings[name] = find_or_default(name).value)
108 v ? v : (@cached_settings[name] = find_or_default(name).value)
109 end
109 end
110
110
111 def self.[]=(name, v)
111 def self.[]=(name, v)
112 setting = find_or_default(name)
112 setting = find_or_default(name)
113 setting.value = (v ? v : "")
113 setting.value = (v ? v : "")
114 @cached_settings[name] = nil
114 @cached_settings[name] = nil
115 setting.save
115 setting.save
116 setting.value
116 setting.value
117 end
117 end
118
118
119 # Defines getter and setter for each setting
119 # Defines getter and setter for each setting
120 # Then setting values can be read using: Setting.some_setting_name
120 # Then setting values can be read using: Setting.some_setting_name
121 # or set using Setting.some_setting_name = "some value"
121 # or set using Setting.some_setting_name = "some value"
122 @@available_settings.each do |name, params|
122 @@available_settings.each do |name, params|
123 src = <<-END_SRC
123 src = <<-END_SRC
124 def self.#{name}
124 def self.#{name}
125 self[:#{name}]
125 self[:#{name}]
126 end
126 end
127
127
128 def self.#{name}?
128 def self.#{name}?
129 self[:#{name}].to_i > 0
129 self[:#{name}].to_i > 0
130 end
130 end
131
131
132 def self.#{name}=(value)
132 def self.#{name}=(value)
133 self[:#{name}] = value
133 self[:#{name}] = value
134 end
134 end
135 END_SRC
135 END_SRC
136 class_eval src, __FILE__, __LINE__
136 class_eval src, __FILE__, __LINE__
137 end
137 end
138
138
139 # Sets a setting value from params
139 # Sets a setting value from params
140 def self.set_from_params(name, params)
140 def self.set_from_params(name, params)
141 params = params.dup
141 params = params.dup
142 params.delete_if {|v| v.blank? } if params.is_a?(Array)
142 params.delete_if {|v| v.blank? } if params.is_a?(Array)
143
143
144 m = "#{name}_from_params"
144 m = "#{name}_from_params"
145 if respond_to? m
145 if respond_to? m
146 self[name.to_sym] = send m, params
146 self[name.to_sym] = send m, params
147 else
147 else
148 self[name.to_sym] = params
148 self[name.to_sym] = params
149 end
149 end
150 end
150 end
151
151
152 # Returns a hash suitable for commit_update_keywords setting
152 # Returns a hash suitable for commit_update_keywords setting
153 #
153 #
154 # Example:
154 # Example:
155 # params = {:keywords => ['fixes', 'closes'], :status_id => ["3", "5"], :done_ratio => ["", "100"]}
155 # params = {:keywords => ['fixes', 'closes'], :status_id => ["3", "5"], :done_ratio => ["", "100"]}
156 # Setting.commit_update_keywords_from_params(params)
156 # Setting.commit_update_keywords_from_params(params)
157 # # => {'fixes' => {'status_id' => "3"}, 'closes' => {'status_id' => "5", 'done_ratio' => "100"}}
157 # # => [{'keywords => 'fixes', 'status_id' => "3"}, {'keywords => 'closes', 'status_id' => "5", 'done_ratio' => "100"}]
158 def self.commit_update_keywords_from_params(params)
158 def self.commit_update_keywords_from_params(params)
159 s = {}
159 s = []
160 if params.is_a?(Hash) && params.key?(:keywords) && params.values.all? {|v| v.is_a? Array}
160 if params.is_a?(Hash) && params.key?(:keywords) && params.values.all? {|v| v.is_a? Array}
161 attributes = params.except(:keywords).keys
161 attributes = params.except(:keywords).keys
162 params[:keywords].each_with_index do |keywords, i|
162 params[:keywords].each_with_index do |keywords, i|
163 next if keywords.blank?
163 next if keywords.blank?
164 s[keywords] = attributes.inject({}) {|h, a|
164 s << attributes.inject({}) {|h, a|
165 value = params[a][i].to_s
165 value = params[a][i].to_s
166 h[a.to_s] = value if value.present?
166 h[a.to_s] = value if value.present?
167 h
167 h
168 }
168 }.merge('keywords' => keywords)
169 end
169 end
170 end
170 end
171 s
171 s
172 end
172 end
173
173
174 # Helper that returns an array based on per_page_options setting
174 # Helper that returns an array based on per_page_options setting
175 def self.per_page_options_array
175 def self.per_page_options_array
176 per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort
176 per_page_options.split(%r{[\s,]}).collect(&:to_i).select {|n| n > 0}.sort
177 end
177 end
178
178
179 # Helper that returns a Hash with single update keywords as keys
179 # Helper that returns a Hash with single update keywords as keys
180 def self.commit_update_by_keyword
180 def self.commit_update_keywords_array
181 h = {}
181 a = []
182 if commit_update_keywords.is_a?(Hash)
182 if commit_update_keywords.is_a?(Array)
183 commit_update_keywords.each do |keywords, attribute_updates|
183 commit_update_keywords.each do |rule|
184 next unless attribute_updates.is_a?(Hash)
184 next unless rule.is_a?(Hash)
185 attribute_updates = attribute_updates.dup
185 rule = rule.dup
186 attribute_updates.delete_if {|k, v| v.blank?}
186 rule.delete_if {|k, v| v.blank?}
187 keywords.to_s.split(",").map(&:strip).reject(&:blank?).each do |keyword|
187 keywords = rule['keywords'].to_s.downcase.split(",").map(&:strip).reject(&:blank?)
188 h[keyword.downcase] = attribute_updates
188 next if keywords.empty?
189 a << rule.merge('keywords' => keywords)
189 end
190 end
190 end
191 end
191 end
192 a
192 h
193 end
193 end
194
194
195 def self.commit_fix_keywords
195 def self.commit_fix_keywords
196 ActiveSupport::Deprecation.warn "Setting.commit_fix_keywords is deprecated and will be removed in Redmine 3"
196 ActiveSupport::Deprecation.warn "Setting.commit_fix_keywords is deprecated and will be removed in Redmine 3"
197 if commit_update_keywords.is_a?(Hash)
197 if commit_update_keywords.is_a?(Array)
198 commit_update_keywords.keys.first
198 commit_update_keywords.first && commit_update_keywords.first['keywords']
199 end
199 end
200 end
200 end
201
201
202 def self.commit_fix_status_id
202 def self.commit_fix_status_id
203 ActiveSupport::Deprecation.warn "Setting.commit_fix_status_id is deprecated and will be removed in Redmine 3"
203 ActiveSupport::Deprecation.warn "Setting.commit_fix_status_id is deprecated and will be removed in Redmine 3"
204 if commit_update_keywords.is_a?(Hash)
204 if commit_update_keywords.is_a?(Array)
205 commit_update_keywords[commit_fix_keywords]['status_id']
205 commit_update_keywords.first && commit_update_keywords.first['status_id']
206 end
206 end
207 end
207 end
208
208
209 def self.commit_fix_done_ratio
209 def self.commit_fix_done_ratio
210 ActiveSupport::Deprecation.warn "Setting.commit_fix_done_ratio is deprecated and will be removed in Redmine 3"
210 ActiveSupport::Deprecation.warn "Setting.commit_fix_done_ratio is deprecated and will be removed in Redmine 3"
211 if commit_update_keywords.is_a?(Hash)
211 if commit_update_keywords.is_a?(Array)
212 commit_update_keywords[commit_fix_keywords]['done_ratio']
212 commit_update_keywords.first && commit_update_keywords.first['done_ratio']
213 end
213 end
214 end
214 end
215
215
216 def self.openid?
216 def self.openid?
217 Object.const_defined?(:OpenID) && self[:openid].to_i > 0
217 Object.const_defined?(:OpenID) && self[:openid].to_i > 0
218 end
218 end
219
219
220 # Checks if settings have changed since the values were read
220 # Checks if settings have changed since the values were read
221 # and clears the cache hash if it's the case
221 # and clears the cache hash if it's the case
222 # Called once per request
222 # Called once per request
223 def self.check_cache
223 def self.check_cache
224 settings_updated_on = Setting.maximum(:updated_on)
224 settings_updated_on = Setting.maximum(:updated_on)
225 if settings_updated_on && @cached_cleared_on <= settings_updated_on
225 if settings_updated_on && @cached_cleared_on <= settings_updated_on
226 clear_cache
226 clear_cache
227 end
227 end
228 end
228 end
229
229
230 # Clears the settings cache
230 # Clears the settings cache
231 def self.clear_cache
231 def self.clear_cache
232 @cached_settings.clear
232 @cached_settings.clear
233 @cached_cleared_on = Time.now
233 @cached_cleared_on = Time.now
234 logger.info "Settings cache cleared." if logger
234 logger.info "Settings cache cleared." if logger
235 end
235 end
236
236
237 private
237 private
238 # Returns the Setting instance for the setting named name
238 # Returns the Setting instance for the setting named name
239 # (record found in database or new record with default value)
239 # (record found in database or new record with default value)
240 def self.find_or_default(name)
240 def self.find_or_default(name)
241 name = name.to_s
241 name = name.to_s
242 raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
242 raise "There's no setting named #{name}" unless @@available_settings.has_key?(name)
243 setting = find_by_name(name)
243 setting = find_by_name(name)
244 unless setting
244 unless setting
245 setting = new(:name => name)
245 setting = new(:name => name)
246 setting.value = @@available_settings[name]['default']
246 setting.value = @@available_settings[name]['default']
247 end
247 end
248 setting
248 setting
249 end
249 end
250 end
250 end
@@ -1,124 +1,127
1 <%= form_tag({:action => 'edit', :tab => 'repositories'}) do %>
1 <%= form_tag({:action => 'edit', :tab => 'repositories'}) do %>
2
2
3 <fieldset class="box settings enabled_scm">
3 <fieldset class="box settings enabled_scm">
4 <legend><%= l(:setting_enabled_scm) %></legend>
4 <legend><%= l(:setting_enabled_scm) %></legend>
5 <%= hidden_field_tag 'settings[enabled_scm][]', '' %>
5 <%= hidden_field_tag 'settings[enabled_scm][]', '' %>
6 <table>
6 <table>
7 <tr>
7 <tr>
8 <th></th>
8 <th></th>
9 <th><%= l(:text_scm_command) %></th>
9 <th><%= l(:text_scm_command) %></th>
10 <th><%= l(:text_scm_command_version) %></th>
10 <th><%= l(:text_scm_command_version) %></th>
11 </tr>
11 </tr>
12 <% Redmine::Scm::Base.all.collect do |choice| %>
12 <% Redmine::Scm::Base.all.collect do |choice| %>
13 <% scm_class = "Repository::#{choice}".constantize %>
13 <% scm_class = "Repository::#{choice}".constantize %>
14 <% text, value = (choice.is_a?(Array) ? choice : [choice, choice]) %>
14 <% text, value = (choice.is_a?(Array) ? choice : [choice, choice]) %>
15 <% setting = :enabled_scm %>
15 <% setting = :enabled_scm %>
16 <% enabled = Setting.send(setting).include?(value) %>
16 <% enabled = Setting.send(setting).include?(value) %>
17 <tr>
17 <tr>
18 <td class="scm_name">
18 <td class="scm_name">
19 <label>
19 <label>
20 <%= check_box_tag("settings[#{setting}][]", value, enabled, :id => nil) %>
20 <%= check_box_tag("settings[#{setting}][]", value, enabled, :id => nil) %>
21 <%= text.to_s %>
21 <%= text.to_s %>
22 </label>
22 </label>
23 </td>
23 </td>
24 <td>
24 <td>
25 <% if enabled %>
25 <% if enabled %>
26 <%=
26 <%=
27 image_tag(
27 image_tag(
28 (scm_class.scm_available ? 'true.png' : 'exclamation.png'),
28 (scm_class.scm_available ? 'true.png' : 'exclamation.png'),
29 :style => "vertical-align:bottom;"
29 :style => "vertical-align:bottom;"
30 )
30 )
31 %>
31 %>
32 <%= scm_class.scm_command %>
32 <%= scm_class.scm_command %>
33 <% end %>
33 <% end %>
34 </td>
34 </td>
35 <td>
35 <td>
36 <%= scm_class.scm_version_string if enabled %>
36 <%= scm_class.scm_version_string if enabled %>
37 </td>
37 </td>
38 </tr>
38 </tr>
39 <% end %>
39 <% end %>
40 </table>
40 </table>
41 <p><em class="info"><%= l(:text_scm_config) %></em></p>
41 <p><em class="info"><%= l(:text_scm_config) %></em></p>
42 </fieldset>
42 </fieldset>
43
43
44 <div class="box tabular settings">
44 <div class="box tabular settings">
45 <p><%= setting_check_box :autofetch_changesets %></p>
45 <p><%= setting_check_box :autofetch_changesets %></p>
46
46
47 <p><%= setting_check_box :sys_api_enabled,
47 <p><%= setting_check_box :sys_api_enabled,
48 :onclick =>
48 :onclick =>
49 "if (this.checked) { $('#settings_sys_api_key').removeAttr('disabled'); } else { $('#settings_sys_api_key').attr('disabled', true); }" %></p>
49 "if (this.checked) { $('#settings_sys_api_key').removeAttr('disabled'); } else { $('#settings_sys_api_key').attr('disabled', true); }" %></p>
50
50
51 <p><%= setting_text_field :sys_api_key,
51 <p><%= setting_text_field :sys_api_key,
52 :size => 30,
52 :size => 30,
53 :id => 'settings_sys_api_key',
53 :id => 'settings_sys_api_key',
54 :disabled => !Setting.sys_api_enabled?,
54 :disabled => !Setting.sys_api_enabled?,
55 :label => :setting_mail_handler_api_key %>
55 :label => :setting_mail_handler_api_key %>
56 <%= link_to_function l(:label_generate_key),
56 <%= link_to_function l(:label_generate_key),
57 "if (!$('#settings_sys_api_key').attr('disabled')) { $('#settings_sys_api_key').val(randomKey(20)) }" %>
57 "if (!$('#settings_sys_api_key').attr('disabled')) { $('#settings_sys_api_key').val(randomKey(20)) }" %>
58 </p>
58 </p>
59
59
60 <p><%= setting_text_field :repository_log_display_limit, :size => 6 %></p>
60 <p><%= setting_text_field :repository_log_display_limit, :size => 6 %></p>
61 </div>
61 </div>
62
62
63 <fieldset class="box tabular settings">
63 <fieldset class="box tabular settings">
64 <legend><%= l(:text_issues_ref_in_commit_messages) %></legend>
64 <legend><%= l(:text_issues_ref_in_commit_messages) %></legend>
65 <p><%= setting_text_field :commit_ref_keywords, :size => 30 %>
65 <p><%= setting_text_field :commit_ref_keywords, :size => 30 %>
66 <em class="info"><%= l(:text_comma_separated) %></em></p>
66 <em class="info"><%= l(:text_comma_separated) %></em></p>
67
67
68 <p><%= setting_check_box :commit_cross_project_ref %></p>
68 <p><%= setting_check_box :commit_cross_project_ref %></p>
69
69
70 <p><%= setting_check_box :commit_logtime_enabled,
70 <p><%= setting_check_box :commit_logtime_enabled,
71 :onclick =>
71 :onclick =>
72 "if (this.checked) { $('#settings_commit_logtime_activity_id').removeAttr('disabled'); } else { $('#settings_commit_logtime_activity_id').attr('disabled', true); }"%></p>
72 "if (this.checked) { $('#settings_commit_logtime_activity_id').removeAttr('disabled'); } else { $('#settings_commit_logtime_activity_id').attr('disabled', true); }"%></p>
73
73
74 <p><%= setting_select :commit_logtime_activity_id,
74 <p><%= setting_select :commit_logtime_activity_id,
75 [[l(:label_default), 0]] +
75 [[l(:label_default), 0]] +
76 TimeEntryActivity.shared.active.collect{|activity| [activity.name, activity.id.to_s]},
76 TimeEntryActivity.shared.active.collect{|activity| [activity.name, activity.id.to_s]},
77 :disabled => !Setting.commit_logtime_enabled?%></p>
77 :disabled => !Setting.commit_logtime_enabled?%></p>
78 </fieldset>
78 </fieldset>
79
79
80 <table class="list" id="commit-keywords">
80 <table class="list" id="commit-keywords">
81 <thead>
81 <thead>
82 <tr>
82 <tr>
83 <th><%= l(:label_tracker) %></th>
83 <th><%= l(:setting_commit_fix_keywords) %></th>
84 <th><%= l(:setting_commit_fix_keywords) %></th>
84 <th><%= l(:label_applied_status) %></th>
85 <th><%= l(:label_applied_status) %></th>
85 <th><%= l(:field_done_ratio) %></th>
86 <th><%= l(:field_done_ratio) %></th>
86 <th class="buttons"></th>
87 <th class="buttons"></th>
87 </tr>
88 </tr>
88 </thead>
89 </thead>
89 <tbody>
90 <tbody>
90 <% @commit_update_keywords.each do |keywords, updates| %>
91 <% @commit_update_keywords.each do |rule| %>
91 <tr class="commit-keywords">
92 <tr class="commit-keywords">
92 <td><%= text_field_tag "settings[commit_update_keywords][keywords][]", keywords, :size => 30 %></td>
93 <td><%= select_tag "settings[commit_update_keywords][if_tracker_id][]", options_for_select([[l(:label_all), ""]] + Tracker.sorted.all.map {|t| [t.name, t.id.to_s]}, rule['if_tracker_id']) %></td>
93 <td><%= select_tag "settings[commit_update_keywords][status_id][]", options_for_select([["", 0]] + IssueStatus.sorted.all.collect{|status| [status.name, status.id.to_s]}, updates['status_id']) %></td>
94 <td><%= text_field_tag "settings[commit_update_keywords][keywords][]", rule['keywords'], :size => 30 %></td>
94 <td><%= select_tag "settings[commit_update_keywords][done_ratio][]", options_for_select([["", ""]] + (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, updates['done_ratio']) %></td>
95 <td><%= select_tag "settings[commit_update_keywords][status_id][]", options_for_select([["", 0]] + IssueStatus.sorted.all.collect{|status| [status.name, status.id.to_s]}, rule['status_id']) %></td>
96 <td><%= select_tag "settings[commit_update_keywords][done_ratio][]", options_for_select([["", ""]] + (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, rule['done_ratio']) %></td>
95 <td class="buttons"><%= link_to image_tag('delete.png'), '#', :class => 'delete-commit-keywords' %></td>
97 <td class="buttons"><%= link_to image_tag('delete.png'), '#', :class => 'delete-commit-keywords' %></td>
96 </tr>
98 </tr>
97 <% end %>
99 <% end %>
98 <tr>
100 <tr>
101 <td></td>
99 <td><em class="info"><%= l(:text_comma_separated) %></em></td>
102 <td><em class="info"><%= l(:text_comma_separated) %></em></td>
100 <td></td>
103 <td></td>
101 <td></td>
104 <td></td>
102 <td class="buttons"><%= link_to image_tag('add.png'), '#', :class => 'add-commit-keywords' %></td>
105 <td class="buttons"><%= link_to image_tag('add.png'), '#', :class => 'add-commit-keywords' %></td>
103 </tr>
106 </tr>
104 </tbody>
107 </tbody>
105 </table>
108 </table>
106
109
107 <p><%= submit_tag l(:button_save) %></p>
110 <p><%= submit_tag l(:button_save) %></p>
108 <% end %>
111 <% end %>
109
112
110 <%= javascript_tag do %>
113 <%= javascript_tag do %>
111 $('#commit-keywords').on('click', 'a.delete-commit-keywords', function(e){
114 $('#commit-keywords').on('click', 'a.delete-commit-keywords', function(e){
112 e.preventDefault();
115 e.preventDefault();
113 if ($('#commit-keywords tbody tr.commit-keywords').length > 1) {
116 if ($('#commit-keywords tbody tr.commit-keywords').length > 1) {
114 $(this).parents('#commit-keywords tr').remove();
117 $(this).parents('#commit-keywords tr').remove();
115 } else {
118 } else {
116 $('#commit-keywords tbody tr.commit-keywords').find('input, select').val('');
119 $('#commit-keywords tbody tr.commit-keywords').find('input, select').val('');
117 }
120 }
118 });
121 });
119 $('#commit-keywords').on('click', 'a.add-commit-keywords', function(e){
122 $('#commit-keywords').on('click', 'a.add-commit-keywords', function(e){
120 e.preventDefault();
123 e.preventDefault();
121 var row = $('#commit-keywords tr.commit-keywords:last');
124 var row = $('#commit-keywords tr.commit-keywords:last');
122 row.clone().insertAfter(row).find('input, select').val('');
125 row.clone().insertAfter(row).find('input, select').val('');
123 });
126 });
124 <% end %>
127 <% end %>
@@ -1,230 +1,230
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18
18
19 # DO NOT MODIFY THIS FILE !!!
19 # DO NOT MODIFY THIS FILE !!!
20 # Settings can be defined through the application in Admin -> Settings
20 # Settings can be defined through the application in Admin -> Settings
21
21
22 app_title:
22 app_title:
23 default: Redmine
23 default: Redmine
24 app_subtitle:
24 app_subtitle:
25 default: Project management
25 default: Project management
26 welcome_text:
26 welcome_text:
27 default:
27 default:
28 login_required:
28 login_required:
29 default: 0
29 default: 0
30 self_registration:
30 self_registration:
31 default: '2'
31 default: '2'
32 lost_password:
32 lost_password:
33 default: 1
33 default: 1
34 unsubscribe:
34 unsubscribe:
35 default: 1
35 default: 1
36 password_min_length:
36 password_min_length:
37 format: int
37 format: int
38 default: 8
38 default: 8
39 # Maximum lifetime of user sessions in minutes
39 # Maximum lifetime of user sessions in minutes
40 session_lifetime:
40 session_lifetime:
41 format: int
41 format: int
42 default: 0
42 default: 0
43 # User session timeout in minutes
43 # User session timeout in minutes
44 session_timeout:
44 session_timeout:
45 format: int
45 format: int
46 default: 0
46 default: 0
47 attachment_max_size:
47 attachment_max_size:
48 format: int
48 format: int
49 default: 5120
49 default: 5120
50 issues_export_limit:
50 issues_export_limit:
51 format: int
51 format: int
52 default: 500
52 default: 500
53 activity_days_default:
53 activity_days_default:
54 format: int
54 format: int
55 default: 30
55 default: 30
56 per_page_options:
56 per_page_options:
57 default: '25,50,100'
57 default: '25,50,100'
58 mail_from:
58 mail_from:
59 default: redmine@example.net
59 default: redmine@example.net
60 bcc_recipients:
60 bcc_recipients:
61 default: 1
61 default: 1
62 plain_text_mail:
62 plain_text_mail:
63 default: 0
63 default: 0
64 text_formatting:
64 text_formatting:
65 default: textile
65 default: textile
66 cache_formatted_text:
66 cache_formatted_text:
67 default: 0
67 default: 0
68 wiki_compression:
68 wiki_compression:
69 default: ""
69 default: ""
70 default_language:
70 default_language:
71 default: en
71 default: en
72 host_name:
72 host_name:
73 default: localhost:3000
73 default: localhost:3000
74 protocol:
74 protocol:
75 default: http
75 default: http
76 feeds_limit:
76 feeds_limit:
77 format: int
77 format: int
78 default: 15
78 default: 15
79 gantt_items_limit:
79 gantt_items_limit:
80 format: int
80 format: int
81 default: 500
81 default: 500
82 # Maximum size of files that can be displayed
82 # Maximum size of files that can be displayed
83 # inline through the file viewer (in KB)
83 # inline through the file viewer (in KB)
84 file_max_size_displayed:
84 file_max_size_displayed:
85 format: int
85 format: int
86 default: 512
86 default: 512
87 diff_max_lines_displayed:
87 diff_max_lines_displayed:
88 format: int
88 format: int
89 default: 1500
89 default: 1500
90 enabled_scm:
90 enabled_scm:
91 serialized: true
91 serialized: true
92 default:
92 default:
93 - Subversion
93 - Subversion
94 - Darcs
94 - Darcs
95 - Mercurial
95 - Mercurial
96 - Cvs
96 - Cvs
97 - Bazaar
97 - Bazaar
98 - Git
98 - Git
99 autofetch_changesets:
99 autofetch_changesets:
100 default: 1
100 default: 1
101 sys_api_enabled:
101 sys_api_enabled:
102 default: 0
102 default: 0
103 sys_api_key:
103 sys_api_key:
104 default: ''
104 default: ''
105 commit_cross_project_ref:
105 commit_cross_project_ref:
106 default: 0
106 default: 0
107 commit_ref_keywords:
107 commit_ref_keywords:
108 default: 'refs,references,IssueID'
108 default: 'refs,references,IssueID'
109 commit_update_keywords:
109 commit_update_keywords:
110 serialized: true
110 serialized: true
111 default: {}
111 default: []
112 commit_logtime_enabled:
112 commit_logtime_enabled:
113 default: 0
113 default: 0
114 commit_logtime_activity_id:
114 commit_logtime_activity_id:
115 format: int
115 format: int
116 default: 0
116 default: 0
117 # autologin duration in days
117 # autologin duration in days
118 # 0 means autologin is disabled
118 # 0 means autologin is disabled
119 autologin:
119 autologin:
120 format: int
120 format: int
121 default: 0
121 default: 0
122 # date format
122 # date format
123 date_format:
123 date_format:
124 default: ''
124 default: ''
125 time_format:
125 time_format:
126 default: ''
126 default: ''
127 user_format:
127 user_format:
128 default: :firstname_lastname
128 default: :firstname_lastname
129 format: symbol
129 format: symbol
130 cross_project_issue_relations:
130 cross_project_issue_relations:
131 default: 0
131 default: 0
132 # Enables subtasks to be in other projects
132 # Enables subtasks to be in other projects
133 cross_project_subtasks:
133 cross_project_subtasks:
134 default: 'tree'
134 default: 'tree'
135 issue_group_assignment:
135 issue_group_assignment:
136 default: 0
136 default: 0
137 default_issue_start_date_to_creation_date:
137 default_issue_start_date_to_creation_date:
138 default: 1
138 default: 1
139 notified_events:
139 notified_events:
140 serialized: true
140 serialized: true
141 default:
141 default:
142 - issue_added
142 - issue_added
143 - issue_updated
143 - issue_updated
144 mail_handler_body_delimiters:
144 mail_handler_body_delimiters:
145 default: ''
145 default: ''
146 mail_handler_excluded_filenames:
146 mail_handler_excluded_filenames:
147 default: ''
147 default: ''
148 mail_handler_api_enabled:
148 mail_handler_api_enabled:
149 default: 0
149 default: 0
150 mail_handler_api_key:
150 mail_handler_api_key:
151 default:
151 default:
152 issue_list_default_columns:
152 issue_list_default_columns:
153 serialized: true
153 serialized: true
154 default:
154 default:
155 - tracker
155 - tracker
156 - status
156 - status
157 - priority
157 - priority
158 - subject
158 - subject
159 - assigned_to
159 - assigned_to
160 - updated_on
160 - updated_on
161 display_subprojects_issues:
161 display_subprojects_issues:
162 default: 1
162 default: 1
163 issue_done_ratio:
163 issue_done_ratio:
164 default: 'issue_field'
164 default: 'issue_field'
165 default_projects_public:
165 default_projects_public:
166 default: 1
166 default: 1
167 default_projects_modules:
167 default_projects_modules:
168 serialized: true
168 serialized: true
169 default:
169 default:
170 - issue_tracking
170 - issue_tracking
171 - time_tracking
171 - time_tracking
172 - news
172 - news
173 - documents
173 - documents
174 - files
174 - files
175 - wiki
175 - wiki
176 - repository
176 - repository
177 - boards
177 - boards
178 - calendar
178 - calendar
179 - gantt
179 - gantt
180 default_projects_tracker_ids:
180 default_projects_tracker_ids:
181 serialized: true
181 serialized: true
182 default:
182 default:
183 # Role given to a non-admin user who creates a project
183 # Role given to a non-admin user who creates a project
184 new_project_user_role_id:
184 new_project_user_role_id:
185 format: int
185 format: int
186 default: ''
186 default: ''
187 sequential_project_identifiers:
187 sequential_project_identifiers:
188 default: 0
188 default: 0
189 # encodings used to convert repository files content to UTF-8
189 # encodings used to convert repository files content to UTF-8
190 # multiple values accepted, comma separated
190 # multiple values accepted, comma separated
191 repositories_encodings:
191 repositories_encodings:
192 default: ''
192 default: ''
193 # encoding used to convert commit logs to UTF-8
193 # encoding used to convert commit logs to UTF-8
194 commit_logs_encoding:
194 commit_logs_encoding:
195 default: 'UTF-8'
195 default: 'UTF-8'
196 repository_log_display_limit:
196 repository_log_display_limit:
197 format: int
197 format: int
198 default: 100
198 default: 100
199 ui_theme:
199 ui_theme:
200 default: ''
200 default: ''
201 emails_footer:
201 emails_footer:
202 default: |-
202 default: |-
203 You have received this notification because you have either subscribed to it, or are involved in it.
203 You have received this notification because you have either subscribed to it, or are involved in it.
204 To change your notification preferences, please click here: http://hostname/my/account
204 To change your notification preferences, please click here: http://hostname/my/account
205 gravatar_enabled:
205 gravatar_enabled:
206 default: 0
206 default: 0
207 openid:
207 openid:
208 default: 0
208 default: 0
209 gravatar_default:
209 gravatar_default:
210 default: ''
210 default: ''
211 start_of_week:
211 start_of_week:
212 default: ''
212 default: ''
213 rest_api_enabled:
213 rest_api_enabled:
214 default: 0
214 default: 0
215 jsonp_enabled:
215 jsonp_enabled:
216 default: 0
216 default: 0
217 default_notification_option:
217 default_notification_option:
218 default: 'only_my_events'
218 default: 'only_my_events'
219 emails_header:
219 emails_header:
220 default: ''
220 default: ''
221 thumbnails_enabled:
221 thumbnails_enabled:
222 default: 0
222 default: 0
223 thumbnails_size:
223 thumbnails_size:
224 format: int
224 format: int
225 default: 100
225 default: 100
226 non_working_week_days:
226 non_working_week_days:
227 serialized: true
227 serialized: true
228 default:
228 default:
229 - '6'
229 - '6'
230 - '7'
230 - '7'
@@ -1,182 +1,186
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class SettingsControllerTest < ActionController::TestCase
20 class SettingsControllerTest < ActionController::TestCase
21 fixtures :users
21 fixtures :users
22
22
23 def setup
23 def setup
24 User.current = nil
24 User.current = nil
25 @request.session[:user_id] = 1 # admin
25 @request.session[:user_id] = 1 # admin
26 end
26 end
27
27
28 def test_index
28 def test_index
29 get :index
29 get :index
30 assert_response :success
30 assert_response :success
31 assert_template 'edit'
31 assert_template 'edit'
32 end
32 end
33
33
34 def test_get_edit
34 def test_get_edit
35 get :edit
35 get :edit
36 assert_response :success
36 assert_response :success
37 assert_template 'edit'
37 assert_template 'edit'
38
38
39 assert_tag 'input', :attributes => {:name => 'settings[enabled_scm][]', :value => ''}
39 assert_tag 'input', :attributes => {:name => 'settings[enabled_scm][]', :value => ''}
40 end
40 end
41
41
42 def test_get_edit_should_preselect_default_issue_list_columns
42 def test_get_edit_should_preselect_default_issue_list_columns
43 with_settings :issue_list_default_columns => %w(tracker subject status updated_on) do
43 with_settings :issue_list_default_columns => %w(tracker subject status updated_on) do
44 get :edit
44 get :edit
45 assert_response :success
45 assert_response :success
46 end
46 end
47
47
48 assert_select 'select[id=selected_columns][name=?]', 'settings[issue_list_default_columns][]' do
48 assert_select 'select[id=selected_columns][name=?]', 'settings[issue_list_default_columns][]' do
49 assert_select 'option', 4
49 assert_select 'option', 4
50 assert_select 'option[value=tracker]', :text => 'Tracker'
50 assert_select 'option[value=tracker]', :text => 'Tracker'
51 assert_select 'option[value=subject]', :text => 'Subject'
51 assert_select 'option[value=subject]', :text => 'Subject'
52 assert_select 'option[value=status]', :text => 'Status'
52 assert_select 'option[value=status]', :text => 'Status'
53 assert_select 'option[value=updated_on]', :text => 'Updated'
53 assert_select 'option[value=updated_on]', :text => 'Updated'
54 end
54 end
55
55
56 assert_select 'select[id=available_columns]' do
56 assert_select 'select[id=available_columns]' do
57 assert_select 'option[value=tracker]', 0
57 assert_select 'option[value=tracker]', 0
58 assert_select 'option[value=priority]', :text => 'Priority'
58 assert_select 'option[value=priority]', :text => 'Priority'
59 end
59 end
60 end
60 end
61
61
62 def test_get_edit_without_trackers_should_succeed
62 def test_get_edit_without_trackers_should_succeed
63 Tracker.delete_all
63 Tracker.delete_all
64
64
65 get :edit
65 get :edit
66 assert_response :success
66 assert_response :success
67 end
67 end
68
68
69 def test_post_edit_notifications
69 def test_post_edit_notifications
70 post :edit, :settings => {:mail_from => 'functional@test.foo',
70 post :edit, :settings => {:mail_from => 'functional@test.foo',
71 :bcc_recipients => '0',
71 :bcc_recipients => '0',
72 :notified_events => %w(issue_added issue_updated news_added),
72 :notified_events => %w(issue_added issue_updated news_added),
73 :emails_footer => 'Test footer'
73 :emails_footer => 'Test footer'
74 }
74 }
75 assert_redirected_to '/settings'
75 assert_redirected_to '/settings'
76 assert_equal 'functional@test.foo', Setting.mail_from
76 assert_equal 'functional@test.foo', Setting.mail_from
77 assert !Setting.bcc_recipients?
77 assert !Setting.bcc_recipients?
78 assert_equal %w(issue_added issue_updated news_added), Setting.notified_events
78 assert_equal %w(issue_added issue_updated news_added), Setting.notified_events
79 assert_equal 'Test footer', Setting.emails_footer
79 assert_equal 'Test footer', Setting.emails_footer
80 Setting.clear_cache
80 Setting.clear_cache
81 end
81 end
82
82
83 def test_edit_commit_update_keywords
83 def test_edit_commit_update_keywords
84 with_settings :commit_update_keywords => {
84 with_settings :commit_update_keywords => [
85 "fixes, resolves" => {"status_id" => "3"},
85 {"keywords" => "fixes, resolves", "status_id" => "3"},
86 "closes" => {"status_id" => "5", "done_ratio" => "100"}
86 {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"}
87 } do
87 ] do
88 get :edit
88 get :edit
89 end
89 end
90 assert_response :success
90 assert_response :success
91 assert_select 'tr.commit-keywords', 2
91 assert_select 'tr.commit-keywords', 2
92 assert_select 'tr.commit-keywords' do
92 assert_select 'tr.commit-keywords:nth-child(1)' do
93 assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'fixes, resolves'
93 assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'fixes, resolves'
94 assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do
94 assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do
95 assert_select 'option[value=3][selected=selected]'
95 assert_select 'option[value=3][selected=selected]'
96 end
96 end
97 end
97 end
98 assert_select 'tr.commit-keywords' do
98 assert_select 'tr.commit-keywords:nth-child(2)' do
99 assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'closes'
99 assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'closes'
100 assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do
100 assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do
101 assert_select 'option[value=5][selected=selected]'
101 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
102 end
102 end
103 assert_select 'select[name=?]', 'settings[commit_update_keywords][done_ratio][]' do
103 assert_select 'select[name=?]', 'settings[commit_update_keywords][done_ratio][]' do
104 assert_select 'option[value=100][selected=selected]'
104 assert_select 'option[value=100][selected=selected]', :text => '100 %'
105 end
106 assert_select 'select[name=?]', 'settings[commit_update_keywords][if_tracker_id][]' do
107 assert_select 'option[value=2][selected=selected]', :text => 'Feature request'
105 end
108 end
106 end
109 end
107 end
110 end
108
111
109 def test_edit_without_commit_update_keywords_should_show_blank_line
112 def test_edit_without_commit_update_keywords_should_show_blank_line
110 with_settings :commit_update_keywords => {} do
113 with_settings :commit_update_keywords => [] do
111 get :edit
114 get :edit
112 end
115 end
113 assert_response :success
116 assert_response :success
114 assert_select 'tr.commit-keywords', 1 do
117 assert_select 'tr.commit-keywords', 1 do
115 assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', ''
118 assert_select 'input[name=?]:not([value])', 'settings[commit_update_keywords][keywords][]'
116 end
119 end
117 end
120 end
118
121
119 def test_post_edit_commit_update_keywords
122 def test_post_edit_commit_update_keywords
120 post :edit, :settings => {
123 post :edit, :settings => {
121 :commit_update_keywords => {
124 :commit_update_keywords => {
122 :keywords => ["resolves", "closes"],
125 :keywords => ["resolves", "closes"],
123 :status_id => ["3", "5"],
126 :status_id => ["3", "5"],
124 :done_ratio => ["", "100"]
127 :done_ratio => ["", "100"],
128 :if_tracker_id => ["", "2"]
125 }
129 }
126 }
130 }
127 assert_redirected_to '/settings'
131 assert_redirected_to '/settings'
128 assert_equal({
132 assert_equal([
129 "resolves" => {"status_id" => "3"},
133 {"keywords" => "resolves", "status_id" => "3"},
130 "closes" => {"status_id" => "5", "done_ratio" => "100"}
134 {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"}
131 }, Setting.commit_update_keywords)
135 ], Setting.commit_update_keywords)
132 end
136 end
133
137
134 def test_get_plugin_settings
138 def test_get_plugin_settings
135 Setting.stubs(:plugin_foo).returns({'sample_setting' => 'Plugin setting value'})
139 Setting.stubs(:plugin_foo).returns({'sample_setting' => 'Plugin setting value'})
136 ActionController::Base.append_view_path(File.join(Rails.root, "test/fixtures/plugins"))
140 ActionController::Base.append_view_path(File.join(Rails.root, "test/fixtures/plugins"))
137 Redmine::Plugin.register :foo do
141 Redmine::Plugin.register :foo do
138 settings :partial => "foo_plugin/foo_plugin_settings"
142 settings :partial => "foo_plugin/foo_plugin_settings"
139 end
143 end
140
144
141 get :plugin, :id => 'foo'
145 get :plugin, :id => 'foo'
142 assert_response :success
146 assert_response :success
143 assert_template 'plugin'
147 assert_template 'plugin'
144 assert_tag 'form', :attributes => {:action => '/settings/plugin/foo'},
148 assert_tag 'form', :attributes => {:action => '/settings/plugin/foo'},
145 :descendant => {:tag => 'input', :attributes => {:name => 'settings[sample_setting]', :value => 'Plugin setting value'}}
149 :descendant => {:tag => 'input', :attributes => {:name => 'settings[sample_setting]', :value => 'Plugin setting value'}}
146
150
147 Redmine::Plugin.clear
151 Redmine::Plugin.clear
148 end
152 end
149
153
150 def test_get_invalid_plugin_settings
154 def test_get_invalid_plugin_settings
151 get :plugin, :id => 'none'
155 get :plugin, :id => 'none'
152 assert_response 404
156 assert_response 404
153 end
157 end
154
158
155 def test_get_non_configurable_plugin_settings
159 def test_get_non_configurable_plugin_settings
156 Redmine::Plugin.register(:foo) {}
160 Redmine::Plugin.register(:foo) {}
157
161
158 get :plugin, :id => 'foo'
162 get :plugin, :id => 'foo'
159 assert_response 404
163 assert_response 404
160
164
161 Redmine::Plugin.clear
165 Redmine::Plugin.clear
162 end
166 end
163
167
164 def test_post_plugin_settings
168 def test_post_plugin_settings
165 Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true)
169 Setting.expects(:plugin_foo=).with({'sample_setting' => 'Value'}).returns(true)
166 Redmine::Plugin.register(:foo) do
170 Redmine::Plugin.register(:foo) do
167 settings :partial => 'not blank' # so that configurable? is true
171 settings :partial => 'not blank' # so that configurable? is true
168 end
172 end
169
173
170 post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'}
174 post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'}
171 assert_redirected_to '/settings/plugin/foo'
175 assert_redirected_to '/settings/plugin/foo'
172 end
176 end
173
177
174 def test_post_non_configurable_plugin_settings
178 def test_post_non_configurable_plugin_settings
175 Redmine::Plugin.register(:foo) {}
179 Redmine::Plugin.register(:foo) {}
176
180
177 post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'}
181 post :plugin, :id => 'foo', :settings => {'sample_setting' => 'Value'}
178 assert_response 404
182 assert_response 404
179
183
180 Redmine::Plugin.clear
184 Redmine::Plugin.clear
181 end
185 end
182 end
186 end
@@ -1,169 +1,181
1 module ObjectHelpers
1 module ObjectHelpers
2 def User.generate!(attributes={})
2 def User.generate!(attributes={})
3 @generated_user_login ||= 'user0'
3 @generated_user_login ||= 'user0'
4 @generated_user_login.succ!
4 @generated_user_login.succ!
5 user = User.new(attributes)
5 user = User.new(attributes)
6 user.login = @generated_user_login.dup if user.login.blank?
6 user.login = @generated_user_login.dup if user.login.blank?
7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
8 user.firstname = "Bob" if user.firstname.blank?
8 user.firstname = "Bob" if user.firstname.blank?
9 user.lastname = "Doe" if user.lastname.blank?
9 user.lastname = "Doe" if user.lastname.blank?
10 yield user if block_given?
10 yield user if block_given?
11 user.save!
11 user.save!
12 user
12 user
13 end
13 end
14
14
15 def User.add_to_project(user, project, roles=nil)
15 def User.add_to_project(user, project, roles=nil)
16 roles = Role.find(1) if roles.nil?
16 roles = Role.find(1) if roles.nil?
17 roles = [roles] unless roles.is_a?(Array)
17 roles = [roles] unless roles.is_a?(Array)
18 Member.create!(:principal => user, :project => project, :roles => roles)
18 Member.create!(:principal => user, :project => project, :roles => roles)
19 end
19 end
20
20
21 def Group.generate!(attributes={})
21 def Group.generate!(attributes={})
22 @generated_group_name ||= 'Group 0'
22 @generated_group_name ||= 'Group 0'
23 @generated_group_name.succ!
23 @generated_group_name.succ!
24 group = Group.new(attributes)
24 group = Group.new(attributes)
25 group.name = @generated_group_name.dup if group.name.blank?
25 group.name = @generated_group_name.dup if group.name.blank?
26 yield group if block_given?
26 yield group if block_given?
27 group.save!
27 group.save!
28 group
28 group
29 end
29 end
30
30
31 def Project.generate!(attributes={})
31 def Project.generate!(attributes={})
32 @generated_project_identifier ||= 'project-0000'
32 @generated_project_identifier ||= 'project-0000'
33 @generated_project_identifier.succ!
33 @generated_project_identifier.succ!
34 project = Project.new(attributes)
34 project = Project.new(attributes)
35 project.name = @generated_project_identifier.dup if project.name.blank?
35 project.name = @generated_project_identifier.dup if project.name.blank?
36 project.identifier = @generated_project_identifier.dup if project.identifier.blank?
36 project.identifier = @generated_project_identifier.dup if project.identifier.blank?
37 yield project if block_given?
37 yield project if block_given?
38 project.save!
38 project.save!
39 project
39 project
40 end
40 end
41
41
42 def Project.generate_with_parent!(parent, attributes={})
42 def Project.generate_with_parent!(parent, attributes={})
43 project = Project.generate!(attributes)
43 project = Project.generate!(attributes)
44 project.set_parent!(parent)
44 project.set_parent!(parent)
45 project
45 project
46 end
46 end
47
47
48 def Tracker.generate!(attributes={})
48 def Tracker.generate!(attributes={})
49 @generated_tracker_name ||= 'Tracker 0'
49 @generated_tracker_name ||= 'Tracker 0'
50 @generated_tracker_name.succ!
50 @generated_tracker_name.succ!
51 tracker = Tracker.new(attributes)
51 tracker = Tracker.new(attributes)
52 tracker.name = @generated_tracker_name.dup if tracker.name.blank?
52 tracker.name = @generated_tracker_name.dup if tracker.name.blank?
53 yield tracker if block_given?
53 yield tracker if block_given?
54 tracker.save!
54 tracker.save!
55 tracker
55 tracker
56 end
56 end
57
57
58 def Role.generate!(attributes={})
58 def Role.generate!(attributes={})
59 @generated_role_name ||= 'Role 0'
59 @generated_role_name ||= 'Role 0'
60 @generated_role_name.succ!
60 @generated_role_name.succ!
61 role = Role.new(attributes)
61 role = Role.new(attributes)
62 role.name = @generated_role_name.dup if role.name.blank?
62 role.name = @generated_role_name.dup if role.name.blank?
63 yield role if block_given?
63 yield role if block_given?
64 role.save!
64 role.save!
65 role
65 role
66 end
66 end
67
67
68 # Generates an unsaved Issue
68 # Generates an unsaved Issue
69 def Issue.generate(attributes={})
69 def Issue.generate(attributes={})
70 issue = Issue.new(attributes)
70 issue = Issue.new(attributes)
71 issue.project ||= Project.find(1)
71 issue.project ||= Project.find(1)
72 issue.tracker ||= issue.project.trackers.first
72 issue.tracker ||= issue.project.trackers.first
73 issue.subject = 'Generated' if issue.subject.blank?
73 issue.subject = 'Generated' if issue.subject.blank?
74 issue.author ||= User.find(2)
74 issue.author ||= User.find(2)
75 yield issue if block_given?
75 yield issue if block_given?
76 issue
76 issue
77 end
77 end
78
78
79 # Generates a saved Issue
79 # Generates a saved Issue
80 def Issue.generate!(attributes={}, &block)
80 def Issue.generate!(attributes={}, &block)
81 issue = Issue.generate(attributes, &block)
81 issue = Issue.generate(attributes, &block)
82 issue.save!
82 issue.save!
83 issue
83 issue
84 end
84 end
85
85
86 # Generates an issue with 2 children and a grandchild
86 # Generates an issue with 2 children and a grandchild
87 def Issue.generate_with_descendants!(attributes={})
87 def Issue.generate_with_descendants!(attributes={})
88 issue = Issue.generate!(attributes)
88 issue = Issue.generate!(attributes)
89 child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id)
89 child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id)
90 Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id)
90 Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id)
91 Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id)
91 Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id)
92 issue.reload
92 issue.reload
93 end
93 end
94
94
95 def Journal.generate!(attributes={})
95 def Journal.generate!(attributes={})
96 journal = Journal.new(attributes)
96 journal = Journal.new(attributes)
97 journal.user ||= User.first
97 journal.user ||= User.first
98 journal.journalized ||= Issue.first
98 journal.journalized ||= Issue.first
99 yield journal if block_given?
99 yield journal if block_given?
100 journal.save!
100 journal.save!
101 journal
101 journal
102 end
102 end
103
103
104 def Version.generate!(attributes={})
104 def Version.generate!(attributes={})
105 @generated_version_name ||= 'Version 0'
105 @generated_version_name ||= 'Version 0'
106 @generated_version_name.succ!
106 @generated_version_name.succ!
107 version = Version.new(attributes)
107 version = Version.new(attributes)
108 version.name = @generated_version_name.dup if version.name.blank?
108 version.name = @generated_version_name.dup if version.name.blank?
109 yield version if block_given?
109 yield version if block_given?
110 version.save!
110 version.save!
111 version
111 version
112 end
112 end
113
113
114 def TimeEntry.generate!(attributes={})
114 def TimeEntry.generate!(attributes={})
115 entry = TimeEntry.new(attributes)
115 entry = TimeEntry.new(attributes)
116 entry.user ||= User.find(2)
116 entry.user ||= User.find(2)
117 entry.issue ||= Issue.find(1) unless entry.project
117 entry.issue ||= Issue.find(1) unless entry.project
118 entry.project ||= entry.issue.project
118 entry.project ||= entry.issue.project
119 entry.activity ||= TimeEntryActivity.first
119 entry.activity ||= TimeEntryActivity.first
120 entry.spent_on ||= Date.today
120 entry.spent_on ||= Date.today
121 entry.hours ||= 1.0
121 entry.hours ||= 1.0
122 entry.save!
122 entry.save!
123 entry
123 entry
124 end
124 end
125
125
126 def AuthSource.generate!(attributes={})
126 def AuthSource.generate!(attributes={})
127 @generated_auth_source_name ||= 'Auth 0'
127 @generated_auth_source_name ||= 'Auth 0'
128 @generated_auth_source_name.succ!
128 @generated_auth_source_name.succ!
129 source = AuthSource.new(attributes)
129 source = AuthSource.new(attributes)
130 source.name = @generated_auth_source_name.dup if source.name.blank?
130 source.name = @generated_auth_source_name.dup if source.name.blank?
131 yield source if block_given?
131 yield source if block_given?
132 source.save!
132 source.save!
133 source
133 source
134 end
134 end
135
135
136 def Board.generate!(attributes={})
136 def Board.generate!(attributes={})
137 @generated_board_name ||= 'Forum 0'
137 @generated_board_name ||= 'Forum 0'
138 @generated_board_name.succ!
138 @generated_board_name.succ!
139 board = Board.new(attributes)
139 board = Board.new(attributes)
140 board.name = @generated_board_name.dup if board.name.blank?
140 board.name = @generated_board_name.dup if board.name.blank?
141 board.description = @generated_board_name.dup if board.description.blank?
141 board.description = @generated_board_name.dup if board.description.blank?
142 yield board if block_given?
142 yield board if block_given?
143 board.save!
143 board.save!
144 board
144 board
145 end
145 end
146
146
147 def Attachment.generate!(attributes={})
147 def Attachment.generate!(attributes={})
148 @generated_filename ||= 'testfile0'
148 @generated_filename ||= 'testfile0'
149 @generated_filename.succ!
149 @generated_filename.succ!
150 attributes = attributes.dup
150 attributes = attributes.dup
151 attachment = Attachment.new(attributes)
151 attachment = Attachment.new(attributes)
152 attachment.container ||= Issue.find(1)
152 attachment.container ||= Issue.find(1)
153 attachment.author ||= User.find(2)
153 attachment.author ||= User.find(2)
154 attachment.filename = @generated_filename.dup if attachment.filename.blank?
154 attachment.filename = @generated_filename.dup if attachment.filename.blank?
155 attachment.save!
155 attachment.save!
156 attachment
156 attachment
157 end
157 end
158
158
159 def CustomField.generate!(attributes={})
159 def CustomField.generate!(attributes={})
160 @generated_custom_field_name ||= 'Custom field 0'
160 @generated_custom_field_name ||= 'Custom field 0'
161 @generated_custom_field_name.succ!
161 @generated_custom_field_name.succ!
162 field = new(attributes)
162 field = new(attributes)
163 field.name = @generated_custom_field_name.dup if field.name.blank?
163 field.name = @generated_custom_field_name.dup if field.name.blank?
164 field.field_format = 'string' if field.field_format.blank?
164 field.field_format = 'string' if field.field_format.blank?
165 yield field if block_given?
165 yield field if block_given?
166 field.save!
166 field.save!
167 field
167 field
168 end
168 end
169
170 def Changeset.generate!(attributes={})
171 @generated_changeset_rev ||= '123456'
172 @generated_changeset_rev.succ!
173 changeset = new(attributes)
174 changeset.repository ||= Project.find(1).repository
175 changeset.revision ||= @generated_changeset_rev
176 changeset.committed_on ||= Time.now
177 yield changeset if block_given?
178 changeset.save!
179 changeset
180 end
169 end
181 end
@@ -1,514 +1,535
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class ChangesetTest < ActiveSupport::TestCase
22 class ChangesetTest < ActiveSupport::TestCase
23 fixtures :projects, :repositories,
23 fixtures :projects, :repositories,
24 :issues, :issue_statuses, :issue_categories,
24 :issues, :issue_statuses, :issue_categories,
25 :changesets, :changes,
25 :changesets, :changes,
26 :enumerations,
26 :enumerations,
27 :custom_fields, :custom_values,
27 :custom_fields, :custom_values,
28 :users, :members, :member_roles, :trackers,
28 :users, :members, :member_roles, :trackers,
29 :enabled_modules, :roles
29 :enabled_modules, :roles
30
30
31 def test_ref_keywords_any
31 def test_ref_keywords_any
32 ActionMailer::Base.deliveries.clear
32 ActionMailer::Base.deliveries.clear
33 Setting.commit_ref_keywords = '*'
33 Setting.commit_ref_keywords = '*'
34 Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}}
34 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => '5', 'done_ratio' => '90'}]
35
35
36 c = Changeset.new(:repository => Project.find(1).repository,
36 c = Changeset.new(:repository => Project.find(1).repository,
37 :committed_on => Time.now,
37 :committed_on => Time.now,
38 :comments => 'New commit (#2). Fixes #1',
38 :comments => 'New commit (#2). Fixes #1',
39 :revision => '12345')
39 :revision => '12345')
40 assert c.save
40 assert c.save
41 assert_equal [1, 2], c.issue_ids.sort
41 assert_equal [1, 2], c.issue_ids.sort
42 fixed = Issue.find(1)
42 fixed = Issue.find(1)
43 assert fixed.closed?
43 assert fixed.closed?
44 assert_equal 90, fixed.done_ratio
44 assert_equal 90, fixed.done_ratio
45 assert_equal 1, ActionMailer::Base.deliveries.size
45 assert_equal 1, ActionMailer::Base.deliveries.size
46 end
46 end
47
47
48 def test_ref_keywords
48 def test_ref_keywords
49 Setting.commit_ref_keywords = 'refs'
49 Setting.commit_ref_keywords = 'refs'
50 Setting.commit_update_keywords = ''
50 Setting.commit_update_keywords = ''
51 c = Changeset.new(:repository => Project.find(1).repository,
51 c = Changeset.new(:repository => Project.find(1).repository,
52 :committed_on => Time.now,
52 :committed_on => Time.now,
53 :comments => 'Ignores #2. Refs #1',
53 :comments => 'Ignores #2. Refs #1',
54 :revision => '12345')
54 :revision => '12345')
55 assert c.save
55 assert c.save
56 assert_equal [1], c.issue_ids.sort
56 assert_equal [1], c.issue_ids.sort
57 end
57 end
58
58
59 def test_ref_keywords_any_only
59 def test_ref_keywords_any_only
60 Setting.commit_ref_keywords = '*'
60 Setting.commit_ref_keywords = '*'
61 Setting.commit_update_keywords = ''
61 Setting.commit_update_keywords = ''
62 c = Changeset.new(:repository => Project.find(1).repository,
62 c = Changeset.new(:repository => Project.find(1).repository,
63 :committed_on => Time.now,
63 :committed_on => Time.now,
64 :comments => 'Ignores #2. Refs #1',
64 :comments => 'Ignores #2. Refs #1',
65 :revision => '12345')
65 :revision => '12345')
66 assert c.save
66 assert c.save
67 assert_equal [1, 2], c.issue_ids.sort
67 assert_equal [1, 2], c.issue_ids.sort
68 end
68 end
69
69
70 def test_ref_keywords_any_with_timelog
70 def test_ref_keywords_any_with_timelog
71 Setting.commit_ref_keywords = '*'
71 Setting.commit_ref_keywords = '*'
72 Setting.commit_logtime_enabled = '1'
72 Setting.commit_logtime_enabled = '1'
73
73
74 {
74 {
75 '2' => 2.0,
75 '2' => 2.0,
76 '2h' => 2.0,
76 '2h' => 2.0,
77 '2hours' => 2.0,
77 '2hours' => 2.0,
78 '15m' => 0.25,
78 '15m' => 0.25,
79 '15min' => 0.25,
79 '15min' => 0.25,
80 '3h15' => 3.25,
80 '3h15' => 3.25,
81 '3h15m' => 3.25,
81 '3h15m' => 3.25,
82 '3h15min' => 3.25,
82 '3h15min' => 3.25,
83 '3:15' => 3.25,
83 '3:15' => 3.25,
84 '3.25' => 3.25,
84 '3.25' => 3.25,
85 '3.25h' => 3.25,
85 '3.25h' => 3.25,
86 '3,25' => 3.25,
86 '3,25' => 3.25,
87 '3,25h' => 3.25,
87 '3,25h' => 3.25,
88 }.each do |syntax, expected_hours|
88 }.each do |syntax, expected_hours|
89 c = Changeset.new(:repository => Project.find(1).repository,
89 c = Changeset.new(:repository => Project.find(1).repository,
90 :committed_on => 24.hours.ago,
90 :committed_on => 24.hours.ago,
91 :comments => "Worked on this issue #1 @#{syntax}",
91 :comments => "Worked on this issue #1 @#{syntax}",
92 :revision => '520',
92 :revision => '520',
93 :user => User.find(2))
93 :user => User.find(2))
94 assert_difference 'TimeEntry.count' do
94 assert_difference 'TimeEntry.count' do
95 c.scan_comment_for_issue_ids
95 c.scan_comment_for_issue_ids
96 end
96 end
97 assert_equal [1], c.issue_ids.sort
97 assert_equal [1], c.issue_ids.sort
98
98
99 time = TimeEntry.first(:order => 'id desc')
99 time = TimeEntry.first(:order => 'id desc')
100 assert_equal 1, time.issue_id
100 assert_equal 1, time.issue_id
101 assert_equal 1, time.project_id
101 assert_equal 1, time.project_id
102 assert_equal 2, time.user_id
102 assert_equal 2, time.user_id
103 assert_equal expected_hours, time.hours,
103 assert_equal expected_hours, time.hours,
104 "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}"
104 "@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}"
105 assert_equal Date.yesterday, time.spent_on
105 assert_equal Date.yesterday, time.spent_on
106 assert time.activity.is_default?
106 assert time.activity.is_default?
107 assert time.comments.include?('r520'),
107 assert time.comments.include?('r520'),
108 "r520 was expected in time_entry comments: #{time.comments}"
108 "r520 was expected in time_entry comments: #{time.comments}"
109 end
109 end
110 end
110 end
111
111
112 def test_ref_keywords_closing_with_timelog
112 def test_ref_keywords_closing_with_timelog
113 Setting.commit_ref_keywords = '*'
113 Setting.commit_ref_keywords = '*'
114 Setting.commit_update_keywords = {
114 Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s}]
115 'fixes , closes' => {
116 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s
117 }
118 }
119 Setting.commit_logtime_enabled = '1'
115 Setting.commit_logtime_enabled = '1'
120
116
121 c = Changeset.new(:repository => Project.find(1).repository,
117 c = Changeset.new(:repository => Project.find(1).repository,
122 :committed_on => Time.now,
118 :committed_on => Time.now,
123 :comments => 'This is a comment. Fixes #1 @4.5, #2 @1',
119 :comments => 'This is a comment. Fixes #1 @4.5, #2 @1',
124 :user => User.find(2))
120 :user => User.find(2))
125 assert_difference 'TimeEntry.count', 2 do
121 assert_difference 'TimeEntry.count', 2 do
126 c.scan_comment_for_issue_ids
122 c.scan_comment_for_issue_ids
127 end
123 end
128
124
129 assert_equal [1, 2], c.issue_ids.sort
125 assert_equal [1, 2], c.issue_ids.sort
130 assert Issue.find(1).closed?
126 assert Issue.find(1).closed?
131 assert Issue.find(2).closed?
127 assert Issue.find(2).closed?
132
128
133 times = TimeEntry.all(:order => 'id desc', :limit => 2)
129 times = TimeEntry.all(:order => 'id desc', :limit => 2)
134 assert_equal [1, 2], times.collect(&:issue_id).sort
130 assert_equal [1, 2], times.collect(&:issue_id).sort
135 end
131 end
136
132
137 def test_ref_keywords_any_line_start
133 def test_ref_keywords_any_line_start
138 Setting.commit_ref_keywords = '*'
134 Setting.commit_ref_keywords = '*'
139 c = Changeset.new(:repository => Project.find(1).repository,
135 c = Changeset.new(:repository => Project.find(1).repository,
140 :committed_on => Time.now,
136 :committed_on => Time.now,
141 :comments => '#1 is the reason of this commit',
137 :comments => '#1 is the reason of this commit',
142 :revision => '12345')
138 :revision => '12345')
143 assert c.save
139 assert c.save
144 assert_equal [1], c.issue_ids.sort
140 assert_equal [1], c.issue_ids.sort
145 end
141 end
146
142
147 def test_ref_keywords_allow_brackets_around_a_issue_number
143 def test_ref_keywords_allow_brackets_around_a_issue_number
148 Setting.commit_ref_keywords = '*'
144 Setting.commit_ref_keywords = '*'
149 c = Changeset.new(:repository => Project.find(1).repository,
145 c = Changeset.new(:repository => Project.find(1).repository,
150 :committed_on => Time.now,
146 :committed_on => Time.now,
151 :comments => '[#1] Worked on this issue',
147 :comments => '[#1] Worked on this issue',
152 :revision => '12345')
148 :revision => '12345')
153 assert c.save
149 assert c.save
154 assert_equal [1], c.issue_ids.sort
150 assert_equal [1], c.issue_ids.sort
155 end
151 end
156
152
157 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
153 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
158 Setting.commit_ref_keywords = '*'
154 Setting.commit_ref_keywords = '*'
159 c = Changeset.new(:repository => Project.find(1).repository,
155 c = Changeset.new(:repository => Project.find(1).repository,
160 :committed_on => Time.now,
156 :committed_on => Time.now,
161 :comments => '[#1 #2, #3] Worked on these',
157 :comments => '[#1 #2, #3] Worked on these',
162 :revision => '12345')
158 :revision => '12345')
163 assert c.save
159 assert c.save
164 assert_equal [1,2,3], c.issue_ids.sort
160 assert_equal [1,2,3], c.issue_ids.sort
165 end
161 end
166
162
167 def test_update_keywords_with_multiple_rules
163 def test_update_keywords_with_multiple_rules
168 Setting.commit_update_keywords = {
164 with_settings :commit_update_keywords => [
169 'fixes, closes' => {'status_id' => '5'},
165 {'keywords' => 'fixes, closes', 'status_id' => '5'},
170 'resolves' => {'status_id' => '3'}
166 {'keywords' => 'resolves', 'status_id' => '3'}
171 }
167 ] do
168
172 issue1 = Issue.generate!
169 issue1 = Issue.generate!
173 issue2 = Issue.generate!
170 issue2 = Issue.generate!
171 Changeset.generate!(:comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}")
172 assert_equal 5, issue1.reload.status_id
173 assert_equal 3, issue2.reload.status_id
174 end
175 end
174
176
175 c = Changeset.new(:repository => Project.find(1).repository,
177 def test_update_keywords_with_multiple_rules_should_match_tracker
176 :committed_on => Time.now,
178 with_settings :commit_update_keywords => [
177 :comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}",
179 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
178 :revision => '12345')
180 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => ''}
179 assert c.save
181 ] do
182
183 issue1 = Issue.generate!(:tracker_id => 2)
184 issue2 = Issue.generate!
185 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
180 assert_equal 5, issue1.reload.status_id
186 assert_equal 5, issue1.reload.status_id
181 assert_equal 3, issue2.reload.status_id
187 assert_equal 3, issue2.reload.status_id
182 end
188 end
189 end
190
191 def test_update_keywords_with_multiple_rules_and_no_match
192 with_settings :commit_update_keywords => [
193 {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'},
194 {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => '3'}
195 ] do
196
197 issue1 = Issue.generate!(:tracker_id => 2)
198 issue2 = Issue.generate!
199 Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}")
200 assert_equal 5, issue1.reload.status_id
201 assert_equal 1, issue2.reload.status_id # no updates
202 end
203 end
183
204
184 def test_commit_referencing_a_subproject_issue
205 def test_commit_referencing_a_subproject_issue
185 c = Changeset.new(:repository => Project.find(1).repository,
206 c = Changeset.new(:repository => Project.find(1).repository,
186 :committed_on => Time.now,
207 :committed_on => Time.now,
187 :comments => 'refs #5, a subproject issue',
208 :comments => 'refs #5, a subproject issue',
188 :revision => '12345')
209 :revision => '12345')
189 assert c.save
210 assert c.save
190 assert_equal [5], c.issue_ids.sort
211 assert_equal [5], c.issue_ids.sort
191 assert c.issues.first.project != c.project
212 assert c.issues.first.project != c.project
192 end
213 end
193
214
194 def test_commit_closing_a_subproject_issue
215 def test_commit_closing_a_subproject_issue
195 with_settings :commit_update_keywords => {'closes' => {'status_id' => '5'}},
216 with_settings :commit_update_keywords => [{'keywords' => 'closes', 'status_id' => '5'}],
196 :default_language => 'en' do
217 :default_language => 'en' do
197 issue = Issue.find(5)
218 issue = Issue.find(5)
198 assert !issue.closed?
219 assert !issue.closed?
199 assert_difference 'Journal.count' do
220 assert_difference 'Journal.count' do
200 c = Changeset.new(:repository => Project.find(1).repository,
221 c = Changeset.new(:repository => Project.find(1).repository,
201 :committed_on => Time.now,
222 :committed_on => Time.now,
202 :comments => 'closes #5, a subproject issue',
223 :comments => 'closes #5, a subproject issue',
203 :revision => '12345')
224 :revision => '12345')
204 assert c.save
225 assert c.save
205 end
226 end
206 assert issue.reload.closed?
227 assert issue.reload.closed?
207 journal = Journal.first(:order => 'id DESC')
228 journal = Journal.first(:order => 'id DESC')
208 assert_equal issue, journal.issue
229 assert_equal issue, journal.issue
209 assert_include "Applied in changeset ecookbook:r12345.", journal.notes
230 assert_include "Applied in changeset ecookbook:r12345.", journal.notes
210 end
231 end
211 end
232 end
212
233
213 def test_commit_referencing_a_parent_project_issue
234 def test_commit_referencing_a_parent_project_issue
214 # repository of child project
235 # repository of child project
215 r = Repository::Subversion.create!(
236 r = Repository::Subversion.create!(
216 :project => Project.find(3),
237 :project => Project.find(3),
217 :url => 'svn://localhost/test')
238 :url => 'svn://localhost/test')
218 c = Changeset.new(:repository => r,
239 c = Changeset.new(:repository => r,
219 :committed_on => Time.now,
240 :committed_on => Time.now,
220 :comments => 'refs #2, an issue of a parent project',
241 :comments => 'refs #2, an issue of a parent project',
221 :revision => '12345')
242 :revision => '12345')
222 assert c.save
243 assert c.save
223 assert_equal [2], c.issue_ids.sort
244 assert_equal [2], c.issue_ids.sort
224 assert c.issues.first.project != c.project
245 assert c.issues.first.project != c.project
225 end
246 end
226
247
227 def test_commit_referencing_a_project_with_commit_cross_project_ref_disabled
248 def test_commit_referencing_a_project_with_commit_cross_project_ref_disabled
228 r = Repository::Subversion.create!(
249 r = Repository::Subversion.create!(
229 :project => Project.find(3),
250 :project => Project.find(3),
230 :url => 'svn://localhost/test')
251 :url => 'svn://localhost/test')
231
252
232 with_settings :commit_cross_project_ref => '0' do
253 with_settings :commit_cross_project_ref => '0' do
233 c = Changeset.new(:repository => r,
254 c = Changeset.new(:repository => r,
234 :committed_on => Time.now,
255 :committed_on => Time.now,
235 :comments => 'refs #4, an issue of a different project',
256 :comments => 'refs #4, an issue of a different project',
236 :revision => '12345')
257 :revision => '12345')
237 assert c.save
258 assert c.save
238 assert_equal [], c.issue_ids
259 assert_equal [], c.issue_ids
239 end
260 end
240 end
261 end
241
262
242 def test_commit_referencing_a_project_with_commit_cross_project_ref_enabled
263 def test_commit_referencing_a_project_with_commit_cross_project_ref_enabled
243 r = Repository::Subversion.create!(
264 r = Repository::Subversion.create!(
244 :project => Project.find(3),
265 :project => Project.find(3),
245 :url => 'svn://localhost/test')
266 :url => 'svn://localhost/test')
246
267
247 with_settings :commit_cross_project_ref => '1' do
268 with_settings :commit_cross_project_ref => '1' do
248 c = Changeset.new(:repository => r,
269 c = Changeset.new(:repository => r,
249 :committed_on => Time.now,
270 :committed_on => Time.now,
250 :comments => 'refs #4, an issue of a different project',
271 :comments => 'refs #4, an issue of a different project',
251 :revision => '12345')
272 :revision => '12345')
252 assert c.save
273 assert c.save
253 assert_equal [4], c.issue_ids
274 assert_equal [4], c.issue_ids
254 end
275 end
255 end
276 end
256
277
257 def test_old_commits_should_not_update_issues_nor_log_time
278 def test_old_commits_should_not_update_issues_nor_log_time
258 Setting.commit_ref_keywords = '*'
279 Setting.commit_ref_keywords = '*'
259 Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}}
280 Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}}
260 Setting.commit_logtime_enabled = '1'
281 Setting.commit_logtime_enabled = '1'
261
282
262 repository = Project.find(1).repository
283 repository = Project.find(1).repository
263 repository.created_on = Time.now
284 repository.created_on = Time.now
264 repository.save!
285 repository.save!
265
286
266 c = Changeset.new(:repository => repository,
287 c = Changeset.new(:repository => repository,
267 :committed_on => 1.month.ago,
288 :committed_on => 1.month.ago,
268 :comments => 'New commit (#2). Fixes #1 @1h',
289 :comments => 'New commit (#2). Fixes #1 @1h',
269 :revision => '12345')
290 :revision => '12345')
270 assert_no_difference 'TimeEntry.count' do
291 assert_no_difference 'TimeEntry.count' do
271 assert c.save
292 assert c.save
272 end
293 end
273 assert_equal [1, 2], c.issue_ids.sort
294 assert_equal [1, 2], c.issue_ids.sort
274 issue = Issue.find(1)
295 issue = Issue.find(1)
275 assert_equal 1, issue.status_id
296 assert_equal 1, issue.status_id
276 assert_equal 0, issue.done_ratio
297 assert_equal 0, issue.done_ratio
277 end
298 end
278
299
279 def test_text_tag_revision
300 def test_text_tag_revision
280 c = Changeset.new(:revision => '520')
301 c = Changeset.new(:revision => '520')
281 assert_equal 'r520', c.text_tag
302 assert_equal 'r520', c.text_tag
282 end
303 end
283
304
284 def test_text_tag_revision_with_same_project
305 def test_text_tag_revision_with_same_project
285 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
306 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
286 assert_equal 'r520', c.text_tag(Project.find(1))
307 assert_equal 'r520', c.text_tag(Project.find(1))
287 end
308 end
288
309
289 def test_text_tag_revision_with_different_project
310 def test_text_tag_revision_with_different_project
290 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
311 c = Changeset.new(:revision => '520', :repository => Project.find(1).repository)
291 assert_equal 'ecookbook:r520', c.text_tag(Project.find(2))
312 assert_equal 'ecookbook:r520', c.text_tag(Project.find(2))
292 end
313 end
293
314
294 def test_text_tag_revision_with_repository_identifier
315 def test_text_tag_revision_with_repository_identifier
295 r = Repository::Subversion.create!(
316 r = Repository::Subversion.create!(
296 :project_id => 1,
317 :project_id => 1,
297 :url => 'svn://localhost/test',
318 :url => 'svn://localhost/test',
298 :identifier => 'documents')
319 :identifier => 'documents')
299
320
300 c = Changeset.new(:revision => '520', :repository => r)
321 c = Changeset.new(:revision => '520', :repository => r)
301 assert_equal 'documents|r520', c.text_tag
322 assert_equal 'documents|r520', c.text_tag
302 assert_equal 'ecookbook:documents|r520', c.text_tag(Project.find(2))
323 assert_equal 'ecookbook:documents|r520', c.text_tag(Project.find(2))
303 end
324 end
304
325
305 def test_text_tag_hash
326 def test_text_tag_hash
306 c = Changeset.new(
327 c = Changeset.new(
307 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
328 :scmid => '7234cb2750b63f47bff735edc50a1c0a433c2518',
308 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
329 :revision => '7234cb2750b63f47bff735edc50a1c0a433c2518')
309 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
330 assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag
310 end
331 end
311
332
312 def test_text_tag_hash_with_same_project
333 def test_text_tag_hash_with_same_project
313 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
334 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
314 assert_equal 'commit:7234cb27', c.text_tag(Project.find(1))
335 assert_equal 'commit:7234cb27', c.text_tag(Project.find(1))
315 end
336 end
316
337
317 def test_text_tag_hash_with_different_project
338 def test_text_tag_hash_with_different_project
318 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
339 c = Changeset.new(:revision => '7234cb27', :scmid => '7234cb27', :repository => Project.find(1).repository)
319 assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2))
340 assert_equal 'ecookbook:commit:7234cb27', c.text_tag(Project.find(2))
320 end
341 end
321
342
322 def test_text_tag_hash_all_number
343 def test_text_tag_hash_all_number
323 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
344 c = Changeset.new(:scmid => '0123456789', :revision => '0123456789')
324 assert_equal 'commit:0123456789', c.text_tag
345 assert_equal 'commit:0123456789', c.text_tag
325 end
346 end
326
347
327 def test_previous
348 def test_previous
328 changeset = Changeset.find_by_revision('3')
349 changeset = Changeset.find_by_revision('3')
329 assert_equal Changeset.find_by_revision('2'), changeset.previous
350 assert_equal Changeset.find_by_revision('2'), changeset.previous
330 end
351 end
331
352
332 def test_previous_nil
353 def test_previous_nil
333 changeset = Changeset.find_by_revision('1')
354 changeset = Changeset.find_by_revision('1')
334 assert_nil changeset.previous
355 assert_nil changeset.previous
335 end
356 end
336
357
337 def test_next
358 def test_next
338 changeset = Changeset.find_by_revision('2')
359 changeset = Changeset.find_by_revision('2')
339 assert_equal Changeset.find_by_revision('3'), changeset.next
360 assert_equal Changeset.find_by_revision('3'), changeset.next
340 end
361 end
341
362
342 def test_next_nil
363 def test_next_nil
343 changeset = Changeset.find_by_revision('10')
364 changeset = Changeset.find_by_revision('10')
344 assert_nil changeset.next
365 assert_nil changeset.next
345 end
366 end
346
367
347 def test_comments_should_be_converted_to_utf8
368 def test_comments_should_be_converted_to_utf8
348 proj = Project.find(3)
369 proj = Project.find(3)
349 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
370 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
350 str = "Texte encod\xe9 en ISO-8859-1."
371 str = "Texte encod\xe9 en ISO-8859-1."
351 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
372 str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
352 r = Repository::Bazaar.create!(
373 r = Repository::Bazaar.create!(
353 :project => proj,
374 :project => proj,
354 :url => '/tmp/test/bazaar',
375 :url => '/tmp/test/bazaar',
355 :log_encoding => 'ISO-8859-1' )
376 :log_encoding => 'ISO-8859-1' )
356 assert r
377 assert r
357 c = Changeset.new(:repository => r,
378 c = Changeset.new(:repository => r,
358 :committed_on => Time.now,
379 :committed_on => Time.now,
359 :revision => '123',
380 :revision => '123',
360 :scmid => '12345',
381 :scmid => '12345',
361 :comments => str)
382 :comments => str)
362 assert( c.save )
383 assert( c.save )
363 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1."
384 str_utf8 = "Texte encod\xc3\xa9 en ISO-8859-1."
364 str_utf8.force_encoding("UTF-8") if str_utf8.respond_to?(:force_encoding)
385 str_utf8.force_encoding("UTF-8") if str_utf8.respond_to?(:force_encoding)
365 assert_equal str_utf8, c.comments
386 assert_equal str_utf8, c.comments
366 end
387 end
367
388
368 def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1
389 def test_invalid_utf8_sequences_in_comments_should_be_replaced_latin1
369 proj = Project.find(3)
390 proj = Project.find(3)
370 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
391 # str = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
371 str1 = "Texte encod\xe9 en ISO-8859-1."
392 str1 = "Texte encod\xe9 en ISO-8859-1."
372 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
393 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
373 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
394 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
374 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
395 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
375 r = Repository::Bazaar.create!(
396 r = Repository::Bazaar.create!(
376 :project => proj,
397 :project => proj,
377 :url => '/tmp/test/bazaar',
398 :url => '/tmp/test/bazaar',
378 :log_encoding => 'UTF-8' )
399 :log_encoding => 'UTF-8' )
379 assert r
400 assert r
380 c = Changeset.new(:repository => r,
401 c = Changeset.new(:repository => r,
381 :committed_on => Time.now,
402 :committed_on => Time.now,
382 :revision => '123',
403 :revision => '123',
383 :scmid => '12345',
404 :scmid => '12345',
384 :comments => str1,
405 :comments => str1,
385 :committer => str2)
406 :committer => str2)
386 assert( c.save )
407 assert( c.save )
387 assert_equal "Texte encod? en ISO-8859-1.", c.comments
408 assert_equal "Texte encod? en ISO-8859-1.", c.comments
388 assert_equal "?a?b?c?d?e test", c.committer
409 assert_equal "?a?b?c?d?e test", c.committer
389 end
410 end
390
411
391 def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis
412 def test_invalid_utf8_sequences_in_comments_should_be_replaced_ja_jis
392 proj = Project.find(3)
413 proj = Project.find(3)
393 str = "test\xb5\xfetest\xb5\xfe"
414 str = "test\xb5\xfetest\xb5\xfe"
394 if str.respond_to?(:force_encoding)
415 if str.respond_to?(:force_encoding)
395 str.force_encoding('ASCII-8BIT')
416 str.force_encoding('ASCII-8BIT')
396 end
417 end
397 r = Repository::Bazaar.create!(
418 r = Repository::Bazaar.create!(
398 :project => proj,
419 :project => proj,
399 :url => '/tmp/test/bazaar',
420 :url => '/tmp/test/bazaar',
400 :log_encoding => 'ISO-2022-JP' )
421 :log_encoding => 'ISO-2022-JP' )
401 assert r
422 assert r
402 c = Changeset.new(:repository => r,
423 c = Changeset.new(:repository => r,
403 :committed_on => Time.now,
424 :committed_on => Time.now,
404 :revision => '123',
425 :revision => '123',
405 :scmid => '12345',
426 :scmid => '12345',
406 :comments => str)
427 :comments => str)
407 assert( c.save )
428 assert( c.save )
408 assert_equal "test??test??", c.comments
429 assert_equal "test??test??", c.comments
409 end
430 end
410
431
411 def test_comments_should_be_converted_all_latin1_to_utf8
432 def test_comments_should_be_converted_all_latin1_to_utf8
412 s1 = "\xC2\x80"
433 s1 = "\xC2\x80"
413 s2 = "\xc3\x82\xc2\x80"
434 s2 = "\xc3\x82\xc2\x80"
414 s4 = s2.dup
435 s4 = s2.dup
415 if s1.respond_to?(:force_encoding)
436 if s1.respond_to?(:force_encoding)
416 s3 = s1.dup
437 s3 = s1.dup
417 s1.force_encoding('ASCII-8BIT')
438 s1.force_encoding('ASCII-8BIT')
418 s2.force_encoding('ASCII-8BIT')
439 s2.force_encoding('ASCII-8BIT')
419 s3.force_encoding('ISO-8859-1')
440 s3.force_encoding('ISO-8859-1')
420 s4.force_encoding('UTF-8')
441 s4.force_encoding('UTF-8')
421 assert_equal s3.encode('UTF-8'), s4
442 assert_equal s3.encode('UTF-8'), s4
422 end
443 end
423 proj = Project.find(3)
444 proj = Project.find(3)
424 r = Repository::Bazaar.create!(
445 r = Repository::Bazaar.create!(
425 :project => proj,
446 :project => proj,
426 :url => '/tmp/test/bazaar',
447 :url => '/tmp/test/bazaar',
427 :log_encoding => 'ISO-8859-1' )
448 :log_encoding => 'ISO-8859-1' )
428 assert r
449 assert r
429 c = Changeset.new(:repository => r,
450 c = Changeset.new(:repository => r,
430 :committed_on => Time.now,
451 :committed_on => Time.now,
431 :revision => '123',
452 :revision => '123',
432 :scmid => '12345',
453 :scmid => '12345',
433 :comments => s1)
454 :comments => s1)
434 assert( c.save )
455 assert( c.save )
435 assert_equal s4, c.comments
456 assert_equal s4, c.comments
436 end
457 end
437
458
438 def test_invalid_utf8_sequences_in_paths_should_be_replaced
459 def test_invalid_utf8_sequences_in_paths_should_be_replaced
439 proj = Project.find(3)
460 proj = Project.find(3)
440 str1 = "Texte encod\xe9 en ISO-8859-1"
461 str1 = "Texte encod\xe9 en ISO-8859-1"
441 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
462 str2 = "\xe9a\xe9b\xe9c\xe9d\xe9e test"
442 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
463 str1.force_encoding("UTF-8") if str1.respond_to?(:force_encoding)
443 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
464 str2.force_encoding("ASCII-8BIT") if str2.respond_to?(:force_encoding)
444 r = Repository::Bazaar.create!(
465 r = Repository::Bazaar.create!(
445 :project => proj,
466 :project => proj,
446 :url => '/tmp/test/bazaar',
467 :url => '/tmp/test/bazaar',
447 :log_encoding => 'UTF-8' )
468 :log_encoding => 'UTF-8' )
448 assert r
469 assert r
449 cs = Changeset.new(
470 cs = Changeset.new(
450 :repository => r,
471 :repository => r,
451 :committed_on => Time.now,
472 :committed_on => Time.now,
452 :revision => '123',
473 :revision => '123',
453 :scmid => '12345',
474 :scmid => '12345',
454 :comments => "test")
475 :comments => "test")
455 assert(cs.save)
476 assert(cs.save)
456 ch = Change.new(
477 ch = Change.new(
457 :changeset => cs,
478 :changeset => cs,
458 :action => "A",
479 :action => "A",
459 :path => str1,
480 :path => str1,
460 :from_path => str2,
481 :from_path => str2,
461 :from_revision => "345")
482 :from_revision => "345")
462 assert(ch.save)
483 assert(ch.save)
463 assert_equal "Texte encod? en ISO-8859-1", ch.path
484 assert_equal "Texte encod? en ISO-8859-1", ch.path
464 assert_equal "?a?b?c?d?e test", ch.from_path
485 assert_equal "?a?b?c?d?e test", ch.from_path
465 end
486 end
466
487
467 def test_comments_nil
488 def test_comments_nil
468 proj = Project.find(3)
489 proj = Project.find(3)
469 r = Repository::Bazaar.create!(
490 r = Repository::Bazaar.create!(
470 :project => proj,
491 :project => proj,
471 :url => '/tmp/test/bazaar',
492 :url => '/tmp/test/bazaar',
472 :log_encoding => 'ISO-8859-1' )
493 :log_encoding => 'ISO-8859-1' )
473 assert r
494 assert r
474 c = Changeset.new(:repository => r,
495 c = Changeset.new(:repository => r,
475 :committed_on => Time.now,
496 :committed_on => Time.now,
476 :revision => '123',
497 :revision => '123',
477 :scmid => '12345',
498 :scmid => '12345',
478 :comments => nil,
499 :comments => nil,
479 :committer => nil)
500 :committer => nil)
480 assert( c.save )
501 assert( c.save )
481 assert_equal "", c.comments
502 assert_equal "", c.comments
482 assert_equal nil, c.committer
503 assert_equal nil, c.committer
483 if c.comments.respond_to?(:force_encoding)
504 if c.comments.respond_to?(:force_encoding)
484 assert_equal "UTF-8", c.comments.encoding.to_s
505 assert_equal "UTF-8", c.comments.encoding.to_s
485 end
506 end
486 end
507 end
487
508
488 def test_comments_empty
509 def test_comments_empty
489 proj = Project.find(3)
510 proj = Project.find(3)
490 r = Repository::Bazaar.create!(
511 r = Repository::Bazaar.create!(
491 :project => proj,
512 :project => proj,
492 :url => '/tmp/test/bazaar',
513 :url => '/tmp/test/bazaar',
493 :log_encoding => 'ISO-8859-1' )
514 :log_encoding => 'ISO-8859-1' )
494 assert r
515 assert r
495 c = Changeset.new(:repository => r,
516 c = Changeset.new(:repository => r,
496 :committed_on => Time.now,
517 :committed_on => Time.now,
497 :revision => '123',
518 :revision => '123',
498 :scmid => '12345',
519 :scmid => '12345',
499 :comments => "",
520 :comments => "",
500 :committer => "")
521 :committer => "")
501 assert( c.save )
522 assert( c.save )
502 assert_equal "", c.comments
523 assert_equal "", c.comments
503 assert_equal "", c.committer
524 assert_equal "", c.committer
504 if c.comments.respond_to?(:force_encoding)
525 if c.comments.respond_to?(:force_encoding)
505 assert_equal "UTF-8", c.comments.encoding.to_s
526 assert_equal "UTF-8", c.comments.encoding.to_s
506 assert_equal "UTF-8", c.committer.encoding.to_s
527 assert_equal "UTF-8", c.committer.encoding.to_s
507 end
528 end
508 end
529 end
509
530
510 def test_identifier
531 def test_identifier
511 c = Changeset.find_by_revision('1')
532 c = Changeset.find_by_revision('1')
512 assert_equal c.revision, c.identifier
533 assert_equal c.revision, c.identifier
513 end
534 end
514 end
535 end
@@ -1,370 +1,370
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class RepositoryTest < ActiveSupport::TestCase
20 class RepositoryTest < ActiveSupport::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :trackers,
22 :trackers,
23 :projects_trackers,
23 :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :repositories,
25 :repositories,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :issue_categories,
28 :issue_categories,
29 :changesets,
29 :changesets,
30 :changes,
30 :changes,
31 :users,
31 :users,
32 :members,
32 :members,
33 :member_roles,
33 :member_roles,
34 :roles,
34 :roles,
35 :enumerations
35 :enumerations
36
36
37 include Redmine::I18n
37 include Redmine::I18n
38
38
39 def setup
39 def setup
40 @repository = Project.find(1).repository
40 @repository = Project.find(1).repository
41 end
41 end
42
42
43 def test_blank_log_encoding_error_message
43 def test_blank_log_encoding_error_message
44 set_language_if_valid 'en'
44 set_language_if_valid 'en'
45 repo = Repository::Bazaar.new(
45 repo = Repository::Bazaar.new(
46 :project => Project.find(3),
46 :project => Project.find(3),
47 :url => "/test",
47 :url => "/test",
48 :log_encoding => ''
48 :log_encoding => ''
49 )
49 )
50 assert !repo.save
50 assert !repo.save
51 assert_include "Commit messages encoding can't be blank",
51 assert_include "Commit messages encoding can't be blank",
52 repo.errors.full_messages
52 repo.errors.full_messages
53 end
53 end
54
54
55 def test_blank_log_encoding_error_message_fr
55 def test_blank_log_encoding_error_message_fr
56 set_language_if_valid 'fr'
56 set_language_if_valid 'fr'
57 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)"
57 str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)"
58 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
58 str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
59 repo = Repository::Bazaar.new(
59 repo = Repository::Bazaar.new(
60 :project => Project.find(3),
60 :project => Project.find(3),
61 :url => "/test"
61 :url => "/test"
62 )
62 )
63 assert !repo.save
63 assert !repo.save
64 assert_include str, repo.errors.full_messages
64 assert_include str, repo.errors.full_messages
65 end
65 end
66
66
67 def test_create
67 def test_create
68 repository = Repository::Subversion.new(:project => Project.find(3))
68 repository = Repository::Subversion.new(:project => Project.find(3))
69 assert !repository.save
69 assert !repository.save
70
70
71 repository.url = "svn://localhost"
71 repository.url = "svn://localhost"
72 assert repository.save
72 assert repository.save
73 repository.reload
73 repository.reload
74
74
75 project = Project.find(3)
75 project = Project.find(3)
76 assert_equal repository, project.repository
76 assert_equal repository, project.repository
77 end
77 end
78
78
79 def test_first_repository_should_be_set_as_default
79 def test_first_repository_should_be_set_as_default
80 repository1 = Repository::Subversion.new(
80 repository1 = Repository::Subversion.new(
81 :project => Project.find(3),
81 :project => Project.find(3),
82 :identifier => 'svn1',
82 :identifier => 'svn1',
83 :url => 'file:///svn1'
83 :url => 'file:///svn1'
84 )
84 )
85 assert repository1.save
85 assert repository1.save
86 assert repository1.is_default?
86 assert repository1.is_default?
87
87
88 repository2 = Repository::Subversion.new(
88 repository2 = Repository::Subversion.new(
89 :project => Project.find(3),
89 :project => Project.find(3),
90 :identifier => 'svn2',
90 :identifier => 'svn2',
91 :url => 'file:///svn2'
91 :url => 'file:///svn2'
92 )
92 )
93 assert repository2.save
93 assert repository2.save
94 assert !repository2.is_default?
94 assert !repository2.is_default?
95
95
96 assert_equal repository1, Project.find(3).repository
96 assert_equal repository1, Project.find(3).repository
97 assert_equal [repository1, repository2], Project.find(3).repositories.sort
97 assert_equal [repository1, repository2], Project.find(3).repositories.sort
98 end
98 end
99
99
100 def test_identifier_should_accept_letters_digits_dashes_and_underscores
100 def test_identifier_should_accept_letters_digits_dashes_and_underscores
101 r = Repository::Subversion.new(
101 r = Repository::Subversion.new(
102 :project_id => 3,
102 :project_id => 3,
103 :identifier => 'svn-123_45',
103 :identifier => 'svn-123_45',
104 :url => 'file:///svn'
104 :url => 'file:///svn'
105 )
105 )
106 assert r.save
106 assert r.save
107 end
107 end
108
108
109 def test_identifier_should_not_be_frozen_for_a_new_repository
109 def test_identifier_should_not_be_frozen_for_a_new_repository
110 assert_equal false, Repository.new.identifier_frozen?
110 assert_equal false, Repository.new.identifier_frozen?
111 end
111 end
112
112
113 def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier
113 def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier
114 Repository.update_all(["identifier = ''"], "id = 10")
114 Repository.update_all(["identifier = ''"], "id = 10")
115
115
116 assert_equal false, Repository.find(10).identifier_frozen?
116 assert_equal false, Repository.find(10).identifier_frozen?
117 end
117 end
118
118
119 def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
119 def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
120 Repository.update_all(["identifier = 'abc123'"], "id = 10")
120 Repository.update_all(["identifier = 'abc123'"], "id = 10")
121
121
122 assert_equal true, Repository.find(10).identifier_frozen?
122 assert_equal true, Repository.find(10).identifier_frozen?
123 end
123 end
124
124
125 def test_identifier_should_not_accept_change_if_frozen
125 def test_identifier_should_not_accept_change_if_frozen
126 r = Repository.new(:identifier => 'foo')
126 r = Repository.new(:identifier => 'foo')
127 r.stubs(:identifier_frozen?).returns(true)
127 r.stubs(:identifier_frozen?).returns(true)
128
128
129 r.identifier = 'bar'
129 r.identifier = 'bar'
130 assert_equal 'foo', r.identifier
130 assert_equal 'foo', r.identifier
131 end
131 end
132
132
133 def test_identifier_should_accept_change_if_not_frozen
133 def test_identifier_should_accept_change_if_not_frozen
134 r = Repository.new(:identifier => 'foo')
134 r = Repository.new(:identifier => 'foo')
135 r.stubs(:identifier_frozen?).returns(false)
135 r.stubs(:identifier_frozen?).returns(false)
136
136
137 r.identifier = 'bar'
137 r.identifier = 'bar'
138 assert_equal 'bar', r.identifier
138 assert_equal 'bar', r.identifier
139 end
139 end
140
140
141 def test_destroy
141 def test_destroy
142 repository = Repository.find(10)
142 repository = Repository.find(10)
143 changesets = repository.changesets.count
143 changesets = repository.changesets.count
144 changes = repository.filechanges.count
144 changes = repository.filechanges.count
145
145
146 assert_difference 'Changeset.count', -changesets do
146 assert_difference 'Changeset.count', -changesets do
147 assert_difference 'Change.count', -changes do
147 assert_difference 'Change.count', -changes do
148 Repository.find(10).destroy
148 Repository.find(10).destroy
149 end
149 end
150 end
150 end
151 end
151 end
152
152
153 def test_destroy_should_delete_parents_associations
153 def test_destroy_should_delete_parents_associations
154 changeset = Changeset.find(102)
154 changeset = Changeset.find(102)
155 changeset.parents = Changeset.find_all_by_id([100, 101])
155 changeset.parents = Changeset.find_all_by_id([100, 101])
156
156
157 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
157 assert_difference 'Changeset.connection.select_all("select * from changeset_parents").size', -2 do
158 Repository.find(10).destroy
158 Repository.find(10).destroy
159 end
159 end
160 end
160 end
161
161
162 def test_destroy_should_delete_issues_associations
162 def test_destroy_should_delete_issues_associations
163 changeset = Changeset.find(102)
163 changeset = Changeset.find(102)
164 changeset.issues = Issue.find_all_by_id([1, 2])
164 changeset.issues = Issue.find_all_by_id([1, 2])
165
165
166 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do
166 assert_difference 'Changeset.connection.select_all("select * from changesets_issues").size', -2 do
167 Repository.find(10).destroy
167 Repository.find(10).destroy
168 end
168 end
169 end
169 end
170
170
171 def test_should_not_create_with_disabled_scm
171 def test_should_not_create_with_disabled_scm
172 # disable Subversion
172 # disable Subversion
173 with_settings :enabled_scm => ['Darcs', 'Git'] do
173 with_settings :enabled_scm => ['Darcs', 'Git'] do
174 repository = Repository::Subversion.new(
174 repository = Repository::Subversion.new(
175 :project => Project.find(3), :url => "svn://localhost")
175 :project => Project.find(3), :url => "svn://localhost")
176 assert !repository.save
176 assert !repository.save
177 assert_include I18n.translate('activerecord.errors.messages.invalid'),
177 assert_include I18n.translate('activerecord.errors.messages.invalid'),
178 repository.errors[:type]
178 repository.errors[:type]
179 end
179 end
180 end
180 end
181
181
182 def test_scan_changesets_for_issue_ids
182 def test_scan_changesets_for_issue_ids
183 Setting.default_language = 'en'
183 Setting.default_language = 'en'
184
184
185 Setting.commit_ref_keywords = 'refs , references, IssueID'
185 Setting.commit_ref_keywords = 'refs , references, IssueID'
186 Setting.commit_update_keywords = {
186 Setting.commit_update_keywords = [
187 'fixes , closes' => {'status_id' => IssueStatus.where(:is_closed => true).first.id, 'done_ratio' => '90'}
187 {'keywords' => 'fixes , closes', 'status_id' => IssueStatus.where(:is_closed => true).first.id, 'done_ratio' => '90'}
188 }
188 ]
189 Setting.default_language = 'en'
189 Setting.default_language = 'en'
190 ActionMailer::Base.deliveries.clear
190 ActionMailer::Base.deliveries.clear
191
191
192 # make sure issue 1 is not already closed
192 # make sure issue 1 is not already closed
193 fixed_issue = Issue.find(1)
193 fixed_issue = Issue.find(1)
194 assert !fixed_issue.status.is_closed?
194 assert !fixed_issue.status.is_closed?
195 old_status = fixed_issue.status
195 old_status = fixed_issue.status
196
196
197 with_settings :notified_events => %w(issue_added issue_updated) do
197 with_settings :notified_events => %w(issue_added issue_updated) do
198 Repository.scan_changesets_for_issue_ids
198 Repository.scan_changesets_for_issue_ids
199 end
199 end
200 assert_equal [101, 102], Issue.find(3).changeset_ids
200 assert_equal [101, 102], Issue.find(3).changeset_ids
201
201
202 # fixed issues
202 # fixed issues
203 fixed_issue.reload
203 fixed_issue.reload
204 assert fixed_issue.status.is_closed?
204 assert fixed_issue.status.is_closed?
205 assert_equal 90, fixed_issue.done_ratio
205 assert_equal 90, fixed_issue.done_ratio
206 assert_equal [101], fixed_issue.changeset_ids
206 assert_equal [101], fixed_issue.changeset_ids
207
207
208 # issue change
208 # issue change
209 journal = fixed_issue.journals.reorder('created_on desc').first
209 journal = fixed_issue.journals.reorder('created_on desc').first
210 assert_equal User.find_by_login('dlopper'), journal.user
210 assert_equal User.find_by_login('dlopper'), journal.user
211 assert_equal 'Applied in changeset r2.', journal.notes
211 assert_equal 'Applied in changeset r2.', journal.notes
212
212
213 # 2 email notifications
213 # 2 email notifications
214 assert_equal 2, ActionMailer::Base.deliveries.size
214 assert_equal 2, ActionMailer::Base.deliveries.size
215 mail = ActionMailer::Base.deliveries.first
215 mail = ActionMailer::Base.deliveries.first
216 assert_not_nil mail
216 assert_not_nil mail
217 assert mail.subject.starts_with?(
217 assert mail.subject.starts_with?(
218 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
218 "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
219 assert_mail_body_match(
219 assert_mail_body_match(
220 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
220 "Status changed from #{old_status} to #{fixed_issue.status}", mail)
221
221
222 # ignoring commits referencing an issue of another project
222 # ignoring commits referencing an issue of another project
223 assert_equal [], Issue.find(4).changesets
223 assert_equal [], Issue.find(4).changesets
224 end
224 end
225
225
226 def test_for_changeset_comments_strip
226 def test_for_changeset_comments_strip
227 repository = Repository::Mercurial.create(
227 repository = Repository::Mercurial.create(
228 :project => Project.find( 4 ),
228 :project => Project.find( 4 ),
229 :url => '/foo/bar/baz' )
229 :url => '/foo/bar/baz' )
230 comment = <<-COMMENT
230 comment = <<-COMMENT
231 This is a loooooooooooooooooooooooooooong comment
231 This is a loooooooooooooooooooooooooooong comment
232
232
233
233
234 COMMENT
234 COMMENT
235 changeset = Changeset.new(
235 changeset = Changeset.new(
236 :comments => comment, :commit_date => Time.now,
236 :comments => comment, :commit_date => Time.now,
237 :revision => 0, :scmid => 'f39b7922fb3c',
237 :revision => 0, :scmid => 'f39b7922fb3c',
238 :committer => 'foo <foo@example.com>',
238 :committer => 'foo <foo@example.com>',
239 :committed_on => Time.now, :repository => repository )
239 :committed_on => Time.now, :repository => repository )
240 assert( changeset.save )
240 assert( changeset.save )
241 assert_not_equal( comment, changeset.comments )
241 assert_not_equal( comment, changeset.comments )
242 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
242 assert_equal( 'This is a loooooooooooooooooooooooooooong comment',
243 changeset.comments )
243 changeset.comments )
244 end
244 end
245
245
246 def test_for_urls_strip_cvs
246 def test_for_urls_strip_cvs
247 repository = Repository::Cvs.create(
247 repository = Repository::Cvs.create(
248 :project => Project.find(4),
248 :project => Project.find(4),
249 :url => ' :pserver:login:password@host:/path/to/the/repository',
249 :url => ' :pserver:login:password@host:/path/to/the/repository',
250 :root_url => 'foo ',
250 :root_url => 'foo ',
251 :log_encoding => 'UTF-8')
251 :log_encoding => 'UTF-8')
252 assert repository.save
252 assert repository.save
253 repository.reload
253 repository.reload
254 assert_equal ':pserver:login:password@host:/path/to/the/repository',
254 assert_equal ':pserver:login:password@host:/path/to/the/repository',
255 repository.url
255 repository.url
256 assert_equal 'foo', repository.root_url
256 assert_equal 'foo', repository.root_url
257 end
257 end
258
258
259 def test_for_urls_strip_subversion
259 def test_for_urls_strip_subversion
260 repository = Repository::Subversion.create(
260 repository = Repository::Subversion.create(
261 :project => Project.find(4),
261 :project => Project.find(4),
262 :url => ' file:///dummy ')
262 :url => ' file:///dummy ')
263 assert repository.save
263 assert repository.save
264 repository.reload
264 repository.reload
265 assert_equal 'file:///dummy', repository.url
265 assert_equal 'file:///dummy', repository.url
266 end
266 end
267
267
268 def test_for_urls_strip_git
268 def test_for_urls_strip_git
269 repository = Repository::Git.create(
269 repository = Repository::Git.create(
270 :project => Project.find(4),
270 :project => Project.find(4),
271 :url => ' c:\dummy ')
271 :url => ' c:\dummy ')
272 assert repository.save
272 assert repository.save
273 repository.reload
273 repository.reload
274 assert_equal 'c:\dummy', repository.url
274 assert_equal 'c:\dummy', repository.url
275 end
275 end
276
276
277 def test_manual_user_mapping
277 def test_manual_user_mapping
278 assert_no_difference "Changeset.where('user_id <> 2').count" do
278 assert_no_difference "Changeset.where('user_id <> 2').count" do
279 c = Changeset.create!(
279 c = Changeset.create!(
280 :repository => @repository,
280 :repository => @repository,
281 :committer => 'foo',
281 :committer => 'foo',
282 :committed_on => Time.now,
282 :committed_on => Time.now,
283 :revision => 100,
283 :revision => 100,
284 :comments => 'Committed by foo.'
284 :comments => 'Committed by foo.'
285 )
285 )
286 assert_nil c.user
286 assert_nil c.user
287 @repository.committer_ids = {'foo' => '2'}
287 @repository.committer_ids = {'foo' => '2'}
288 assert_equal User.find(2), c.reload.user
288 assert_equal User.find(2), c.reload.user
289 # committer is now mapped
289 # committer is now mapped
290 c = Changeset.create!(
290 c = Changeset.create!(
291 :repository => @repository,
291 :repository => @repository,
292 :committer => 'foo',
292 :committer => 'foo',
293 :committed_on => Time.now,
293 :committed_on => Time.now,
294 :revision => 101,
294 :revision => 101,
295 :comments => 'Another commit by foo.'
295 :comments => 'Another commit by foo.'
296 )
296 )
297 assert_equal User.find(2), c.user
297 assert_equal User.find(2), c.user
298 end
298 end
299 end
299 end
300
300
301 def test_auto_user_mapping_by_username
301 def test_auto_user_mapping_by_username
302 c = Changeset.create!(
302 c = Changeset.create!(
303 :repository => @repository,
303 :repository => @repository,
304 :committer => 'jsmith',
304 :committer => 'jsmith',
305 :committed_on => Time.now,
305 :committed_on => Time.now,
306 :revision => 100,
306 :revision => 100,
307 :comments => 'Committed by john.'
307 :comments => 'Committed by john.'
308 )
308 )
309 assert_equal User.find(2), c.user
309 assert_equal User.find(2), c.user
310 end
310 end
311
311
312 def test_auto_user_mapping_by_email
312 def test_auto_user_mapping_by_email
313 c = Changeset.create!(
313 c = Changeset.create!(
314 :repository => @repository,
314 :repository => @repository,
315 :committer => 'john <jsmith@somenet.foo>',
315 :committer => 'john <jsmith@somenet.foo>',
316 :committed_on => Time.now,
316 :committed_on => Time.now,
317 :revision => 100,
317 :revision => 100,
318 :comments => 'Committed by john.'
318 :comments => 'Committed by john.'
319 )
319 )
320 assert_equal User.find(2), c.user
320 assert_equal User.find(2), c.user
321 end
321 end
322
322
323 def test_filesystem_avaialbe
323 def test_filesystem_avaialbe
324 klass = Repository::Filesystem
324 klass = Repository::Filesystem
325 assert klass.scm_adapter_class
325 assert klass.scm_adapter_class
326 assert_equal true, klass.scm_available
326 assert_equal true, klass.scm_available
327 end
327 end
328
328
329 def test_merge_extra_info
329 def test_merge_extra_info
330 repo = Repository::Subversion.new(:project => Project.find(3))
330 repo = Repository::Subversion.new(:project => Project.find(3))
331 assert !repo.save
331 assert !repo.save
332 repo.url = "svn://localhost"
332 repo.url = "svn://localhost"
333 assert repo.save
333 assert repo.save
334 repo.reload
334 repo.reload
335 project = Project.find(3)
335 project = Project.find(3)
336 assert_equal repo, project.repository
336 assert_equal repo, project.repository
337 assert_nil repo.extra_info
337 assert_nil repo.extra_info
338 h1 = {"test_1" => {"test_11" => "test_value_11"}}
338 h1 = {"test_1" => {"test_11" => "test_value_11"}}
339 repo.merge_extra_info(h1)
339 repo.merge_extra_info(h1)
340 assert_equal h1, repo.extra_info
340 assert_equal h1, repo.extra_info
341 h2 = {"test_2" => {
341 h2 = {"test_2" => {
342 "test_21" => "test_value_21",
342 "test_21" => "test_value_21",
343 "test_22" => "test_value_22",
343 "test_22" => "test_value_22",
344 }}
344 }}
345 repo.merge_extra_info(h2)
345 repo.merge_extra_info(h2)
346 assert_equal (h = {"test_11" => "test_value_11"}),
346 assert_equal (h = {"test_11" => "test_value_11"}),
347 repo.extra_info["test_1"]
347 repo.extra_info["test_1"]
348 assert_equal "test_value_21",
348 assert_equal "test_value_21",
349 repo.extra_info["test_2"]["test_21"]
349 repo.extra_info["test_2"]["test_21"]
350 h3 = {"test_2" => {
350 h3 = {"test_2" => {
351 "test_23" => "test_value_23",
351 "test_23" => "test_value_23",
352 "test_24" => "test_value_24",
352 "test_24" => "test_value_24",
353 }}
353 }}
354 repo.merge_extra_info(h3)
354 repo.merge_extra_info(h3)
355 assert_equal (h = {"test_11" => "test_value_11"}),
355 assert_equal (h = {"test_11" => "test_value_11"}),
356 repo.extra_info["test_1"]
356 repo.extra_info["test_1"]
357 assert_nil repo.extra_info["test_2"]["test_21"]
357 assert_nil repo.extra_info["test_2"]["test_21"]
358 assert_equal "test_value_23",
358 assert_equal "test_value_23",
359 repo.extra_info["test_2"]["test_23"]
359 repo.extra_info["test_2"]["test_23"]
360 end
360 end
361
361
362 def test_sort_should_not_raise_an_error_with_nil_identifiers
362 def test_sort_should_not_raise_an_error_with_nil_identifiers
363 r1 = Repository.new
363 r1 = Repository.new
364 r2 = Repository.new
364 r2 = Repository.new
365
365
366 assert_nothing_raised do
366 assert_nothing_raised do
367 [r1, r2].sort
367 [r1, r2].sort
368 end
368 end
369 end
369 end
370 end
370 end
General Comments 0
You need to be logged in to leave comments. Login now