##// END OF EJS Templates
human_attribute_name accepts optional argument....
Jean-Philippe Lang -
r8166:5eed64b8488a
parent child
Show More
@@ -1,70 +1,70
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Group < Principal
19 19 has_and_belongs_to_many :users, :after_add => :user_added,
20 20 :after_remove => :user_removed
21 21
22 22 acts_as_customizable
23 23
24 24 validates_presence_of :lastname
25 25 validates_uniqueness_of :lastname, :case_sensitive => false
26 26 validates_length_of :lastname, :maximum => 30
27 27
28 28 before_destroy :remove_references_before_destroy
29 29
30 30 def to_s
31 31 lastname.to_s
32 32 end
33 33
34 34 alias :name :to_s
35 35
36 36 def user_added(user)
37 37 members.each do |member|
38 38 next if member.project.nil?
39 39 user_member = Member.find_by_project_id_and_user_id(member.project_id, user.id) || Member.new(:project_id => member.project_id, :user_id => user.id)
40 40 member.member_roles.each do |member_role|
41 41 user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
42 42 end
43 43 user_member.save!
44 44 end
45 45 end
46 46
47 47 def user_removed(user)
48 48 members.each do |member|
49 49 MemberRole.find(:all, :include => :member,
50 50 :conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
51 51 end
52 52 end
53 53
54 def self.human_attribute_name(attribute_key_name)
54 def self.human_attribute_name(attribute_key_name, *args)
55 55 attr_name = attribute_key_name
56 56 if attr_name == 'lastname'
57 57 attr_name = "name"
58 58 end
59 super(attr_name)
59 super(attr_name, *args)
60 60 end
61 61
62 62 private
63 63
64 64 # Removes references that are not handled by associations
65 65 def remove_references_before_destroy
66 66 return if self.id.nil?
67 67
68 68 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
69 69 end
70 70 end
@@ -1,325 +1,325
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ScmFetchError < Exception; end
19 19
20 20 class Repository < ActiveRecord::Base
21 21 include Redmine::Ciphering
22 22
23 23 belongs_to :project
24 24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 25 has_many :changes, :through => :changesets
26 26
27 27 serialize :extra_info
28 28
29 29 # Raw SQL to delete changesets and changes in the database
30 30 # has_many :changesets, :dependent => :destroy is too slow for big repositories
31 31 before_destroy :clear_changesets
32 32
33 33 validates_length_of :password, :maximum => 255, :allow_nil => true
34 34 # Checks if the SCM is enabled when creating a repository
35 35 validate :repo_create_validation, :on => :create
36 36
37 37 def repo_create_validation
38 38 unless Setting.enabled_scm.include?(self.class.name.demodulize)
39 39 errors.add(:type, :invalid)
40 40 end
41 41 end
42 42
43 def self.human_attribute_name(attribute_key_name)
43 def self.human_attribute_name(attribute_key_name, *args)
44 44 attr_name = attribute_key_name
45 45 if attr_name == "log_encoding"
46 46 attr_name = "commit_logs_encoding"
47 47 end
48 super(attr_name)
48 super(attr_name, *args)
49 49 end
50 50
51 51 # Removes leading and trailing whitespace
52 52 def url=(arg)
53 53 write_attribute(:url, arg ? arg.to_s.strip : nil)
54 54 end
55 55
56 56 # Removes leading and trailing whitespace
57 57 def root_url=(arg)
58 58 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
59 59 end
60 60
61 61 def password
62 62 read_ciphered_attribute(:password)
63 63 end
64 64
65 65 def password=(arg)
66 66 write_ciphered_attribute(:password, arg)
67 67 end
68 68
69 69 def scm_adapter
70 70 self.class.scm_adapter_class
71 71 end
72 72
73 73 def scm
74 74 @scm ||= self.scm_adapter.new(url, root_url,
75 75 login, password, path_encoding)
76 76 update_attribute(:root_url, @scm.root_url) if root_url.blank?
77 77 @scm
78 78 end
79 79
80 80 def scm_name
81 81 self.class.scm_name
82 82 end
83 83
84 84 def merge_extra_info(arg)
85 85 h = extra_info || {}
86 86 return h if arg.nil?
87 87 h.merge!(arg)
88 88 write_attribute(:extra_info, h)
89 89 end
90 90
91 91 def report_last_commit
92 92 true
93 93 end
94 94
95 95 def supports_cat?
96 96 scm.supports_cat?
97 97 end
98 98
99 99 def supports_annotate?
100 100 scm.supports_annotate?
101 101 end
102 102
103 103 def supports_all_revisions?
104 104 true
105 105 end
106 106
107 107 def supports_directory_revisions?
108 108 false
109 109 end
110 110
111 111 def supports_revision_graph?
112 112 false
113 113 end
114 114
115 115 def entry(path=nil, identifier=nil)
116 116 scm.entry(path, identifier)
117 117 end
118 118
119 119 def entries(path=nil, identifier=nil)
120 120 scm.entries(path, identifier)
121 121 end
122 122
123 123 def branches
124 124 scm.branches
125 125 end
126 126
127 127 def tags
128 128 scm.tags
129 129 end
130 130
131 131 def default_branch
132 132 nil
133 133 end
134 134
135 135 def properties(path, identifier=nil)
136 136 scm.properties(path, identifier)
137 137 end
138 138
139 139 def cat(path, identifier=nil)
140 140 scm.cat(path, identifier)
141 141 end
142 142
143 143 def diff(path, rev, rev_to)
144 144 scm.diff(path, rev, rev_to)
145 145 end
146 146
147 147 def diff_format_revisions(cs, cs_to, sep=':')
148 148 text = ""
149 149 text << cs_to.format_identifier + sep if cs_to
150 150 text << cs.format_identifier if cs
151 151 text
152 152 end
153 153
154 154 # Returns a path relative to the url of the repository
155 155 def relative_path(path)
156 156 path
157 157 end
158 158
159 159 # Finds and returns a revision with a number or the beginning of a hash
160 160 def find_changeset_by_name(name)
161 161 return nil if name.blank?
162 162 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
163 163 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
164 164 end
165 165
166 166 def latest_changeset
167 167 @latest_changeset ||= changesets.find(:first)
168 168 end
169 169
170 170 # Returns the latest changesets for +path+
171 171 # Default behaviour is to search in cached changesets
172 172 def latest_changesets(path, rev, limit=10)
173 173 if path.blank?
174 174 changesets.find(
175 175 :all,
176 176 :include => :user,
177 177 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
178 178 :limit => limit)
179 179 else
180 180 changes.find(
181 181 :all,
182 182 :include => {:changeset => :user},
183 183 :conditions => ["path = ?", path.with_leading_slash],
184 184 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
185 185 :limit => limit
186 186 ).collect(&:changeset)
187 187 end
188 188 end
189 189
190 190 def scan_changesets_for_issue_ids
191 191 self.changesets.each(&:scan_comment_for_issue_ids)
192 192 end
193 193
194 194 # Returns an array of committers usernames and associated user_id
195 195 def committers
196 196 @committers ||= Changeset.connection.select_rows(
197 197 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
198 198 end
199 199
200 200 # Maps committers username to a user ids
201 201 def committer_ids=(h)
202 202 if h.is_a?(Hash)
203 203 committers.each do |committer, user_id|
204 204 new_user_id = h[committer]
205 205 if new_user_id && (new_user_id.to_i != user_id.to_i)
206 206 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
207 207 Changeset.update_all(
208 208 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
209 209 ["repository_id = ? AND committer = ?", id, committer])
210 210 end
211 211 end
212 212 @committers = nil
213 213 @found_committer_users = nil
214 214 true
215 215 else
216 216 false
217 217 end
218 218 end
219 219
220 220 # Returns the Redmine User corresponding to the given +committer+
221 221 # It will return nil if the committer is not yet mapped and if no User
222 222 # with the same username or email was found
223 223 def find_committer_user(committer)
224 224 unless committer.blank?
225 225 @found_committer_users ||= {}
226 226 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
227 227
228 228 user = nil
229 229 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
230 230 if c && c.user
231 231 user = c.user
232 232 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
233 233 username, email = $1.strip, $3
234 234 u = User.find_by_login(username)
235 235 u ||= User.find_by_mail(email) unless email.blank?
236 236 user = u
237 237 end
238 238 @found_committer_users[committer] = user
239 239 user
240 240 end
241 241 end
242 242
243 243 def repo_log_encoding
244 244 encoding = log_encoding.to_s.strip
245 245 encoding.blank? ? 'UTF-8' : encoding
246 246 end
247 247
248 248 # Fetches new changesets for all repositories of active projects
249 249 # Can be called periodically by an external script
250 250 # eg. ruby script/runner "Repository.fetch_changesets"
251 251 def self.fetch_changesets
252 252 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
253 253 if project.repository
254 254 begin
255 255 project.repository.fetch_changesets
256 256 rescue Redmine::Scm::Adapters::CommandFailed => e
257 257 logger.error "scm: error during fetching changesets: #{e.message}"
258 258 end
259 259 end
260 260 end
261 261 end
262 262
263 263 # scan changeset comments to find related and fixed issues for all repositories
264 264 def self.scan_changesets_for_issue_ids
265 265 find(:all).each(&:scan_changesets_for_issue_ids)
266 266 end
267 267
268 268 def self.scm_name
269 269 'Abstract'
270 270 end
271 271
272 272 def self.available_scm
273 273 subclasses.collect {|klass| [klass.scm_name, klass.name]}
274 274 end
275 275
276 276 def self.factory(klass_name, *args)
277 277 klass = "Repository::#{klass_name}".constantize
278 278 klass.new(*args)
279 279 rescue
280 280 nil
281 281 end
282 282
283 283 def self.scm_adapter_class
284 284 nil
285 285 end
286 286
287 287 def self.scm_command
288 288 ret = ""
289 289 begin
290 290 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
291 291 rescue Exception => e
292 292 logger.error "scm: error during get command: #{e.message}"
293 293 end
294 294 ret
295 295 end
296 296
297 297 def self.scm_version_string
298 298 ret = ""
299 299 begin
300 300 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
301 301 rescue Exception => e
302 302 logger.error "scm: error during get version string: #{e.message}"
303 303 end
304 304 ret
305 305 end
306 306
307 307 def self.scm_available
308 308 ret = false
309 309 begin
310 310 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
311 311 rescue Exception => e
312 312 logger.error "scm: error during get scm available: #{e.message}"
313 313 end
314 314 ret
315 315 end
316 316
317 317 private
318 318
319 319 def clear_changesets
320 320 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
321 321 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
322 322 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
323 323 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
324 324 end
325 325 end
@@ -1,104 +1,104
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/bazaar_adapter'
19 19
20 20 class Repository::Bazaar < Repository
21 21 attr_protected :root_url
22 22 validates_presence_of :url, :log_encoding
23 23
24 def self.human_attribute_name(attribute_key_name)
24 def self.human_attribute_name(attribute_key_name, *args)
25 25 attr_name = attribute_key_name
26 26 if attr_name == "url"
27 27 attr_name = "path_to_repository"
28 28 end
29 super(attr_name)
29 super(attr_name, *args)
30 30 end
31 31
32 32 def self.scm_adapter_class
33 33 Redmine::Scm::Adapters::BazaarAdapter
34 34 end
35 35
36 36 def self.scm_name
37 37 'Bazaar'
38 38 end
39 39
40 40 def entries(path=nil, identifier=nil)
41 41 entries = scm.entries(path, identifier)
42 42 if entries
43 43 entries.each do |e|
44 44 next if e.lastrev.revision.blank?
45 45 # Set the filesize unless browsing a specific revision
46 46 if identifier.nil? && e.is_file?
47 47 full_path = File.join(root_url, e.path)
48 48 e.size = File.stat(full_path).size if File.file?(full_path)
49 49 end
50 50 c = Change.find(
51 51 :first,
52 52 :include => :changeset,
53 53 :conditions => [
54 54 "#{Change.table_name}.revision = ? and #{Changeset.table_name}.repository_id = ?",
55 55 e.lastrev.revision,
56 56 id
57 57 ],
58 58 :order => "#{Changeset.table_name}.revision DESC")
59 59 if c
60 60 e.lastrev.identifier = c.changeset.revision
61 61 e.lastrev.name = c.changeset.revision
62 62 e.lastrev.author = c.changeset.committer
63 63 end
64 64 end
65 65 end
66 66 end
67 67
68 68 def fetch_changesets
69 69 scm_info = scm.info
70 70 if scm_info
71 71 # latest revision found in database
72 72 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
73 73 # latest revision in the repository
74 74 scm_revision = scm_info.lastrev.identifier.to_i
75 75 if db_revision < scm_revision
76 76 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
77 77 identifier_from = db_revision + 1
78 78 while (identifier_from <= scm_revision)
79 79 # loads changesets by batches of 200
80 80 identifier_to = [identifier_from + 199, scm_revision].min
81 81 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
82 82 transaction do
83 83 revisions.reverse_each do |revision|
84 84 changeset = Changeset.create(:repository => self,
85 85 :revision => revision.identifier,
86 86 :committer => revision.author,
87 87 :committed_on => revision.time,
88 88 :scmid => revision.scmid,
89 89 :comments => revision.message)
90 90
91 91 revision.paths.each do |change|
92 92 Change.create(:changeset => changeset,
93 93 :action => change[:action],
94 94 :path => change[:path],
95 95 :revision => change[:revision])
96 96 end
97 97 end
98 98 end unless revisions.nil?
99 99 identifier_from = identifier_to + 1
100 100 end
101 101 end
102 102 end
103 103 end
104 104 end
@@ -1,205 +1,205
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/cvs_adapter'
19 19 require 'digest/sha1'
20 20
21 21 class Repository::Cvs < Repository
22 22 validates_presence_of :url, :root_url, :log_encoding
23 23
24 def self.human_attribute_name(attribute_key_name)
24 def self.human_attribute_name(attribute_key_name, *args)
25 25 attr_name = attribute_key_name
26 26 if attr_name == "root_url"
27 27 attr_name = "cvsroot"
28 28 elsif attr_name == "url"
29 29 attr_name = "cvs_module"
30 30 end
31 super(attr_name)
31 super(attr_name, *args)
32 32 end
33 33
34 34 def self.scm_adapter_class
35 35 Redmine::Scm::Adapters::CvsAdapter
36 36 end
37 37
38 38 def self.scm_name
39 39 'CVS'
40 40 end
41 41
42 42 def entry(path=nil, identifier=nil)
43 43 rev = identifier.nil? ? nil : changesets.find_by_revision(identifier)
44 44 scm.entry(path, rev.nil? ? nil : rev.committed_on)
45 45 end
46 46
47 47 def entries(path=nil, identifier=nil)
48 48 rev = nil
49 49 if ! identifier.nil?
50 50 rev = changesets.find_by_revision(identifier)
51 51 return nil if rev.nil?
52 52 end
53 53 entries = scm.entries(path, rev.nil? ? nil : rev.committed_on)
54 54 if entries
55 55 entries.each() do |entry|
56 56 if ( ! entry.lastrev.nil? ) && ( ! entry.lastrev.revision.nil? )
57 57 change=changes.find_by_revision_and_path(
58 58 entry.lastrev.revision,
59 59 scm.with_leading_slash(entry.path) )
60 60 if change
61 61 entry.lastrev.identifier = change.changeset.revision
62 62 entry.lastrev.revision = change.changeset.revision
63 63 entry.lastrev.author = change.changeset.committer
64 64 # entry.lastrev.branch = change.branch
65 65 end
66 66 end
67 67 end
68 68 end
69 69 entries
70 70 end
71 71
72 72 def cat(path, identifier=nil)
73 73 rev = nil
74 74 if ! identifier.nil?
75 75 rev = changesets.find_by_revision(identifier)
76 76 return nil if rev.nil?
77 77 end
78 78 scm.cat(path, rev.nil? ? nil : rev.committed_on)
79 79 end
80 80
81 81 def annotate(path, identifier=nil)
82 82 rev = nil
83 83 if ! identifier.nil?
84 84 rev = changesets.find_by_revision(identifier)
85 85 return nil if rev.nil?
86 86 end
87 87 scm.annotate(path, rev.nil? ? nil : rev.committed_on)
88 88 end
89 89
90 90 def diff(path, rev, rev_to)
91 91 # convert rev to revision. CVS can't handle changesets here
92 92 diff=[]
93 93 changeset_from = changesets.find_by_revision(rev)
94 94 if rev_to.to_i > 0
95 95 changeset_to = changesets.find_by_revision(rev_to)
96 96 end
97 97 changeset_from.changes.each() do |change_from|
98 98 revision_from = nil
99 99 revision_to = nil
100 100 if path.nil? || (change_from.path.starts_with? scm.with_leading_slash(path))
101 101 revision_from = change_from.revision
102 102 end
103 103 if revision_from
104 104 if changeset_to
105 105 changeset_to.changes.each() do |change_to|
106 106 revision_to = change_to.revision if change_to.path == change_from.path
107 107 end
108 108 end
109 109 unless revision_to
110 110 revision_to = scm.get_previous_revision(revision_from)
111 111 end
112 112 file_diff = scm.diff(change_from.path, revision_from, revision_to)
113 113 diff = diff + file_diff unless file_diff.nil?
114 114 end
115 115 end
116 116 return diff
117 117 end
118 118
119 119 def fetch_changesets
120 120 # some nifty bits to introduce a commit-id with cvs
121 121 # natively cvs doesn't provide any kind of changesets,
122 122 # there is only a revision per file.
123 123 # we now take a guess using the author, the commitlog and the commit-date.
124 124
125 125 # last one is the next step to take. the commit-date is not equal for all
126 126 # commits in one changeset. cvs update the commit-date when the *,v file was touched. so
127 127 # we use a small delta here, to merge all changes belonging to _one_ changeset
128 128 time_delta = 10.seconds
129 129 fetch_since = latest_changeset ? latest_changeset.committed_on : nil
130 130 transaction do
131 131 tmp_rev_num = 1
132 132 scm.revisions('', fetch_since, nil, :log_encoding => repo_log_encoding) do |revision|
133 133 # only add the change to the database, if it doen't exists. the cvs log
134 134 # is not exclusive at all.
135 135 tmp_time = revision.time.clone
136 136 unless changes.find_by_path_and_revision(
137 137 scm.with_leading_slash(revision.paths[0][:path]),
138 138 revision.paths[0][:revision]
139 139 )
140 140 cmt = Changeset.normalize_comments(revision.message, repo_log_encoding)
141 141 author_utf8 = Changeset.to_utf8(revision.author, repo_log_encoding)
142 142 cs = changesets.find(
143 143 :first,
144 144 :conditions => {
145 145 :committed_on => tmp_time - time_delta .. tmp_time + time_delta,
146 146 :committer => author_utf8,
147 147 :comments => cmt
148 148 }
149 149 )
150 150 # create a new changeset....
151 151 unless cs
152 152 # we use a temporaray revision number here (just for inserting)
153 153 # later on, we calculate a continous positive number
154 154 tmp_time2 = tmp_time.clone.gmtime
155 155 branch = revision.paths[0][:branch]
156 156 scmid = branch + "-" + tmp_time2.strftime("%Y%m%d-%H%M%S")
157 157 cs = Changeset.create(:repository => self,
158 158 :revision => "tmp#{tmp_rev_num}",
159 159 :scmid => scmid,
160 160 :committer => revision.author,
161 161 :committed_on => tmp_time,
162 162 :comments => revision.message)
163 163 tmp_rev_num += 1
164 164 end
165 165 # convert CVS-File-States to internal Action-abbrevations
166 166 # default action is (M)odified
167 167 action = "M"
168 168 if revision.paths[0][:action] == "Exp" && revision.paths[0][:revision] == "1.1"
169 169 action = "A" # add-action always at first revision (= 1.1)
170 170 elsif revision.paths[0][:action] == "dead"
171 171 action = "D" # dead-state is similar to Delete
172 172 end
173 173 Change.create(
174 174 :changeset => cs,
175 175 :action => action,
176 176 :path => scm.with_leading_slash(revision.paths[0][:path]),
177 177 :revision => revision.paths[0][:revision],
178 178 :branch => revision.paths[0][:branch]
179 179 )
180 180 end
181 181 end
182 182
183 183 # Renumber new changesets in chronological order
184 184 changesets.find(
185 185 :all,
186 186 :order => 'committed_on ASC, id ASC',
187 187 :conditions => "revision LIKE 'tmp%'"
188 188 ).each do |changeset|
189 189 changeset.update_attribute :revision, next_revision_number
190 190 end
191 191 end # transaction
192 192 @current_revision_number = nil
193 193 end
194 194
195 195 private
196 196
197 197 # Returns the next revision number to assign to a CVS changeset
198 198 def next_revision_number
199 199 # Need to retrieve existing revision numbers to sort them as integers
200 200 sql = "SELECT revision FROM #{Changeset.table_name} "
201 201 sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'"
202 202 @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0)
203 203 @current_revision_number += 1
204 204 end
205 205 end
@@ -1,113 +1,113
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/darcs_adapter'
19 19
20 20 class Repository::Darcs < Repository
21 21 validates_presence_of :url, :log_encoding
22 22
23 def self.human_attribute_name(attribute_key_name)
23 def self.human_attribute_name(attribute_key_name, *args)
24 24 attr_name = attribute_key_name
25 25 if attr_name == "url"
26 26 attr_name = "path_to_repository"
27 27 end
28 super(attr_name)
28 super(attr_name, *args)
29 29 end
30 30
31 31 def self.scm_adapter_class
32 32 Redmine::Scm::Adapters::DarcsAdapter
33 33 end
34 34
35 35 def self.scm_name
36 36 'Darcs'
37 37 end
38 38
39 39 def supports_directory_revisions?
40 40 true
41 41 end
42 42
43 43 def entry(path=nil, identifier=nil)
44 44 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier)
45 45 scm.entry(path, patch.nil? ? nil : patch.scmid)
46 46 end
47 47
48 48 def entries(path=nil, identifier=nil)
49 49 patch = nil
50 50 if ! identifier.nil?
51 51 patch = changesets.find_by_revision(identifier)
52 52 return nil if patch.nil?
53 53 end
54 54 entries = scm.entries(path, patch.nil? ? nil : patch.scmid)
55 55 if entries
56 56 entries.each do |entry|
57 57 # Search the DB for the entry's last change
58 58 if entry.lastrev && !entry.lastrev.scmid.blank?
59 59 changeset = changesets.find_by_scmid(entry.lastrev.scmid)
60 60 end
61 61 if changeset
62 62 entry.lastrev.identifier = changeset.revision
63 63 entry.lastrev.name = changeset.revision
64 64 entry.lastrev.time = changeset.committed_on
65 65 entry.lastrev.author = changeset.committer
66 66 end
67 67 end
68 68 end
69 69 entries
70 70 end
71 71
72 72 def cat(path, identifier=nil)
73 73 patch = identifier.nil? ? nil : changesets.find_by_revision(identifier.to_s)
74 74 scm.cat(path, patch.nil? ? nil : patch.scmid)
75 75 end
76 76
77 77 def diff(path, rev, rev_to)
78 78 patch_from = changesets.find_by_revision(rev)
79 79 return nil if patch_from.nil?
80 80 patch_to = changesets.find_by_revision(rev_to) if rev_to
81 81 if path.blank?
82 82 path = patch_from.changes.collect{|change| change.path}.join(' ')
83 83 end
84 84 patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil) : nil
85 85 end
86 86
87 87 def fetch_changesets
88 88 scm_info = scm.info
89 89 if scm_info
90 90 db_last_id = latest_changeset ? latest_changeset.scmid : nil
91 91 next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1
92 92 # latest revision in the repository
93 93 scm_revision = scm_info.lastrev.scmid
94 94 unless changesets.find_by_scmid(scm_revision)
95 95 revisions = scm.revisions('', db_last_id, nil, :with_path => true)
96 96 transaction do
97 97 revisions.reverse_each do |revision|
98 98 changeset = Changeset.create(:repository => self,
99 99 :revision => next_rev,
100 100 :scmid => revision.scmid,
101 101 :committer => revision.author,
102 102 :committed_on => revision.time,
103 103 :comments => revision.message)
104 104 revision.paths.each do |change|
105 105 changeset.create_change(change)
106 106 end
107 107 next_rev += 1
108 108 end if revisions
109 109 end
110 110 end
111 111 end
112 112 end
113 113 end
@@ -1,54 +1,54
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # FileSystem adapter
5 5 # File written by Paul Rivier, at Demotera.
6 6 #
7 7 # This program is free software; you can redistribute it and/or
8 8 # modify it under the terms of the GNU General Public License
9 9 # as published by the Free Software Foundation; either version 2
10 10 # of the License, or (at your option) any later version.
11 11 #
12 12 # This program is distributed in the hope that it will be useful,
13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 15 # GNU General Public License for more details.
16 16 #
17 17 # You should have received a copy of the GNU General Public License
18 18 # along with this program; if not, write to the Free Software
19 19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 20
21 21 require 'redmine/scm/adapters/filesystem_adapter'
22 22
23 23 class Repository::Filesystem < Repository
24 24 attr_protected :root_url
25 25 validates_presence_of :url
26 26
27 def self.human_attribute_name(attribute_key_name)
27 def self.human_attribute_name(attribute_key_name, *args)
28 28 attr_name = attribute_key_name
29 29 if attr_name == "url"
30 30 attr_name = "root_directory"
31 31 end
32 super(attr_name)
32 super(attr_name, *args)
33 33 end
34 34
35 35 def self.scm_adapter_class
36 36 Redmine::Scm::Adapters::FilesystemAdapter
37 37 end
38 38
39 39 def self.scm_name
40 40 'Filesystem'
41 41 end
42 42
43 43 def supports_all_revisions?
44 44 false
45 45 end
46 46
47 47 def entries(path=nil, identifier=nil)
48 48 scm.entries(path, identifier)
49 49 end
50 50
51 51 def fetch_changesets
52 52 nil
53 53 end
54 54 end
@@ -1,205 +1,205
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require 'redmine/scm/adapters/git_adapter'
20 20
21 21 class Repository::Git < Repository
22 22 attr_protected :root_url
23 23 validates_presence_of :url
24 24
25 def self.human_attribute_name(attribute_key_name)
25 def self.human_attribute_name(attribute_key_name, *args)
26 26 attr_name = attribute_key_name
27 27 if attr_name == "url"
28 28 attr_name = "path_to_repository"
29 29 end
30 super(attr_name)
30 super(attr_name, *args)
31 31 end
32 32
33 33 def self.scm_adapter_class
34 34 Redmine::Scm::Adapters::GitAdapter
35 35 end
36 36
37 37 def self.scm_name
38 38 'Git'
39 39 end
40 40
41 41 def report_last_commit
42 42 extra_report_last_commit
43 43 end
44 44
45 45 def extra_report_last_commit
46 46 return false if extra_info.nil?
47 47 v = extra_info["extra_report_last_commit"]
48 48 return false if v.nil?
49 49 v.to_s != '0'
50 50 end
51 51
52 52 def supports_directory_revisions?
53 53 true
54 54 end
55 55
56 56 def supports_revision_graph?
57 57 true
58 58 end
59 59
60 60 def repo_log_encoding
61 61 'UTF-8'
62 62 end
63 63
64 64 # Returns the identifier for the given git changeset
65 65 def self.changeset_identifier(changeset)
66 66 changeset.scmid
67 67 end
68 68
69 69 # Returns the readable identifier for the given git changeset
70 70 def self.format_changeset_identifier(changeset)
71 71 changeset.revision[0, 8]
72 72 end
73 73
74 74 def branches
75 75 scm.branches
76 76 end
77 77
78 78 def tags
79 79 scm.tags
80 80 end
81 81
82 82 def default_branch
83 83 scm.default_branch
84 84 rescue Exception => e
85 85 logger.error "git: error during get default branch: #{e.message}"
86 86 nil
87 87 end
88 88
89 89 def find_changeset_by_name(name)
90 90 return nil if name.nil? || name.empty?
91 91 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
92 92 return e if e
93 93 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
94 94 end
95 95
96 96 def entries(path=nil, identifier=nil)
97 97 scm.entries(path,
98 98 identifier,
99 99 options = {:report_last_commit => extra_report_last_commit})
100 100 end
101 101
102 102 # With SCMs that have a sequential commit numbering,
103 103 # such as Subversion and Mercurial,
104 104 # Redmine is able to be clever and only fetch changesets
105 105 # going forward from the most recent one it knows about.
106 106 #
107 107 # However, Git does not have a sequential commit numbering.
108 108 #
109 109 # In order to fetch only new adding revisions,
110 110 # Redmine needs to parse revisions per branch.
111 111 # Branch "last_scmid" is for this requirement.
112 112 #
113 113 # In Git and Mercurial, revisions are not in date order.
114 114 # Redmine Mercurial fixed issues.
115 115 # * Redmine Takes Too Long On Large Mercurial Repository
116 116 # http://www.redmine.org/issues/3449
117 117 # * Sorting for changesets might go wrong on Mercurial repos
118 118 # http://www.redmine.org/issues/3567
119 119 #
120 120 # Database revision column is text, so Redmine can not sort by revision.
121 121 # Mercurial has revision number, and revision number guarantees revision order.
122 122 # Redmine Mercurial model stored revisions ordered by database id to database.
123 123 # So, Redmine Mercurial model can use correct ordering revisions.
124 124 #
125 125 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
126 126 # to get limited revisions from old to new.
127 127 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
128 128 #
129 129 # The repository can still be fully reloaded by calling #clear_changesets
130 130 # before fetching changesets (eg. for offline resync)
131 131 def fetch_changesets
132 132 scm_brs = branches
133 133 return if scm_brs.nil? || scm_brs.empty?
134 134 h1 = extra_info || {}
135 135 h = h1.dup
136 136 h["branches"] ||= {}
137 137 h["db_consistent"] ||= {}
138 138 if changesets.count == 0
139 139 h["db_consistent"]["ordering"] = 1
140 140 merge_extra_info(h)
141 141 self.save
142 142 elsif ! h["db_consistent"].has_key?("ordering")
143 143 h["db_consistent"]["ordering"] = 0
144 144 merge_extra_info(h)
145 145 self.save
146 146 end
147 147 scm_brs.each do |br1|
148 148 br = br1.to_s
149 149 from_scmid = nil
150 150 from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br]
151 151 h["branches"][br] ||= {}
152 152 scm.revisions('', from_scmid, br, {:reverse => true}) do |rev|
153 153 db_rev = find_changeset_by_name(rev.revision)
154 154 transaction do
155 155 if db_rev.nil?
156 156 db_saved_rev = save_revision(rev)
157 157 parents = {}
158 158 parents[db_saved_rev] = rev.parents unless rev.parents.nil?
159 159 parents.each do |ch, chparents|
160 160 ch.parents = chparents.collect{|rp| find_changeset_by_name(rp)}.compact
161 161 end
162 162 end
163 163 h["branches"][br]["last_scmid"] = rev.scmid
164 164 merge_extra_info(h)
165 165 self.save
166 166 end
167 167 end
168 168 end
169 169 end
170 170
171 171 def save_revision(rev)
172 172 changeset = Changeset.new(
173 173 :repository => self,
174 174 :revision => rev.identifier,
175 175 :scmid => rev.scmid,
176 176 :committer => rev.author,
177 177 :committed_on => rev.time,
178 178 :comments => rev.message
179 179 )
180 180 if changeset.save
181 181 rev.paths.each do |file|
182 182 Change.create(
183 183 :changeset => changeset,
184 184 :action => file[:action],
185 185 :path => file[:path])
186 186 end
187 187 end
188 188 changeset
189 189 end
190 190 private :save_revision
191 191
192 192 def latest_changesets(path,rev,limit=10)
193 193 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
194 194 return [] if revisions.nil? || revisions.empty?
195 195
196 196 changesets.find(
197 197 :all,
198 198 :conditions => [
199 199 "scmid IN (?)",
200 200 revisions.map!{|c| c.scmid}
201 201 ],
202 202 :order => 'committed_on DESC'
203 203 )
204 204 end
205 205 end
@@ -1,158 +1,158
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/mercurial_adapter'
19 19
20 20 class Repository::Mercurial < Repository
21 21 # sort changesets by revision number
22 22 has_many :changesets,
23 23 :order => "#{Changeset.table_name}.id DESC",
24 24 :foreign_key => 'repository_id'
25 25
26 26 attr_protected :root_url
27 27 validates_presence_of :url
28 28
29 29 # number of changesets to fetch at once
30 30 FETCH_AT_ONCE = 100
31 31
32 def self.human_attribute_name(attribute_key_name)
32 def self.human_attribute_name(attribute_key_name, *args)
33 33 attr_name = attribute_key_name
34 34 if attr_name == "url"
35 35 attr_name = "path_to_repository"
36 36 end
37 super(attr_name)
37 super(attr_name, *args)
38 38 end
39 39
40 40 def self.scm_adapter_class
41 41 Redmine::Scm::Adapters::MercurialAdapter
42 42 end
43 43
44 44 def self.scm_name
45 45 'Mercurial'
46 46 end
47 47
48 48 def supports_directory_revisions?
49 49 true
50 50 end
51 51
52 52 def supports_revision_graph?
53 53 true
54 54 end
55 55
56 56 def repo_log_encoding
57 57 'UTF-8'
58 58 end
59 59
60 60 # Returns the readable identifier for the given mercurial changeset
61 61 def self.format_changeset_identifier(changeset)
62 62 "#{changeset.revision}:#{changeset.scmid}"
63 63 end
64 64
65 65 # Returns the identifier for the given Mercurial changeset
66 66 def self.changeset_identifier(changeset)
67 67 changeset.scmid
68 68 end
69 69
70 70 def diff_format_revisions(cs, cs_to, sep=':')
71 71 super(cs, cs_to, ' ')
72 72 end
73 73
74 74 # Finds and returns a revision with a number or the beginning of a hash
75 75 def find_changeset_by_name(name)
76 76 return nil if name.nil? || name.empty?
77 77 if /[^\d]/ =~ name or name.to_s.size > 8
78 78 e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
79 79 else
80 80 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
81 81 end
82 82 return e if e
83 83 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
84 84 end
85 85
86 86 # Returns the latest changesets for +path+; sorted by revision number
87 87 #
88 88 # Because :order => 'id DESC' is defined at 'has_many',
89 89 # there is no need to set 'order'.
90 90 # But, MySQL test fails.
91 91 # Sqlite3 and PostgreSQL pass.
92 92 # Is this MySQL bug?
93 93 def latest_changesets(path, rev, limit=10)
94 94 changesets.find(:all,
95 95 :include => :user,
96 96 :conditions => latest_changesets_cond(path, rev, limit),
97 97 :limit => limit,
98 98 :order => "#{Changeset.table_name}.id DESC")
99 99 end
100 100
101 101 def latest_changesets_cond(path, rev, limit)
102 102 cond, args = [], []
103 103 if scm.branchmap.member? rev
104 104 # Mercurial named branch is *stable* in each revision.
105 105 # So, named branch can be stored in database.
106 106 # Mercurial provides *bookmark* which is equivalent with git branch.
107 107 # But, bookmark is not implemented.
108 108 cond << "#{Changeset.table_name}.scmid IN (?)"
109 109 # Revisions in root directory and sub directory are not equal.
110 110 # So, in order to get correct limit, we need to get all revisions.
111 111 # But, it is very heavy.
112 112 # Mercurial does not treat direcotry.
113 113 # So, "hg log DIR" is very heavy.
114 114 branch_limit = path.blank? ? limit : ( limit * 5 )
115 115 args << scm.nodes_in_branch(rev, :limit => branch_limit)
116 116 elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
117 117 cond << "#{Changeset.table_name}.id <= ?"
118 118 args << last.id
119 119 end
120 120 unless path.blank?
121 121 cond << "EXISTS (SELECT * FROM #{Change.table_name}
122 122 WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
123 123 AND (#{Change.table_name}.path = ?
124 124 OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
125 125 args << path.with_leading_slash
126 126 args << "#{path.with_leading_slash.gsub(%r{[%_\\]}) { |s| "\\#{s}" }}/%" << '\\'
127 127 end
128 128 [cond.join(' AND '), *args] unless cond.empty?
129 129 end
130 130 private :latest_changesets_cond
131 131
132 132 def fetch_changesets
133 133 return if scm.info.nil?
134 134 scm_rev = scm.info.lastrev.revision.to_i
135 135 db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
136 136 return unless db_rev < scm_rev # already up-to-date
137 137
138 138 logger.debug "Fetching changesets for repository #{url}" if logger
139 139 (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
140 140 transaction do
141 141 scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
142 142 cs = Changeset.create(:repository => self,
143 143 :revision => re.revision,
144 144 :scmid => re.scmid,
145 145 :committer => re.author,
146 146 :committed_on => re.time,
147 147 :comments => re.message)
148 148 re.paths.each { |e| cs.create_change(e) }
149 149 parents = {}
150 150 parents[cs] = re.parents unless re.parents.nil?
151 151 parents.each do |ch, chparents|
152 152 ch.parents = chparents.collect{|rp| find_changeset_by_name(rp)}.compact
153 153 end
154 154 end
155 155 end
156 156 end
157 157 end
158 158 end
@@ -1,118 +1,118
1 1 # Patches active_support/core_ext/load_error.rb to support 1.9.3 LoadError message
2 2 if RUBY_VERSION >= '1.9.3'
3 3 MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1]
4 4 end
5 5
6 6 require 'active_record'
7 7
8 8 module ActiveRecord
9 9 class Base
10 10 include Redmine::I18n
11 11
12 12 # Translate attribute names for validation errors display
13 def self.human_attribute_name(attr)
13 def self.human_attribute_name(attr, *args)
14 14 l("field_#{attr.to_s.gsub(/_id$/, '')}", :default => attr)
15 15 end
16 16 end
17 17 end
18 18
19 19 module ActiveRecord
20 20 class Errors
21 21 def full_messages(options = {})
22 22 full_messages = []
23 23
24 24 @errors.each_key do |attr|
25 25 @errors[attr].each do |message|
26 26 next unless message
27 27
28 28 if attr == "base"
29 29 full_messages << message
30 30 elsif attr == "custom_values"
31 31 # Replace the generic "custom values is invalid"
32 32 # with the errors on custom values
33 33 @base.custom_values.each do |value|
34 34 value.errors.each do |attr, msg|
35 35 full_messages << value.custom_field.name + ' ' + msg
36 36 end
37 37 end
38 38 else
39 39 attr_name = @base.class.human_attribute_name(attr)
40 40 full_messages << attr_name + ' ' + message.to_s
41 41 end
42 42 end
43 43 end
44 44 full_messages
45 45 end
46 46 end
47 47 end
48 48
49 49 module ActionView
50 50 module Helpers
51 51 module DateHelper
52 52 # distance_of_time_in_words breaks when difference is greater than 30 years
53 53 def distance_of_date_in_words(from_date, to_date = 0, options = {})
54 54 from_date = from_date.to_date if from_date.respond_to?(:to_date)
55 55 to_date = to_date.to_date if to_date.respond_to?(:to_date)
56 56 distance_in_days = (to_date - from_date).abs
57 57
58 58 I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
59 59 case distance_in_days
60 60 when 0..60 then locale.t :x_days, :count => distance_in_days.round
61 61 when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round
62 62 else locale.t :over_x_years, :count => (distance_in_days / 365).floor
63 63 end
64 64 end
65 65 end
66 66 end
67 67 end
68 68 end
69 69
70 70 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
71 71
72 72 module AsynchronousMailer
73 73 # Adds :async_smtp and :async_sendmail delivery methods
74 74 # to perform email deliveries asynchronously
75 75 %w(smtp sendmail).each do |type|
76 76 define_method("perform_delivery_async_#{type}") do |mail|
77 77 Thread.start do
78 78 send "perform_delivery_#{type}", mail
79 79 end
80 80 end
81 81 end
82 82
83 83 # Adds a delivery method that writes emails in tmp/emails for testing purpose
84 84 def perform_delivery_tmp_file(mail)
85 85 dest_dir = File.join(Rails.root, 'tmp', 'emails')
86 86 Dir.mkdir(dest_dir) unless File.directory?(dest_dir)
87 87 File.open(File.join(dest_dir, mail.message_id.gsub(/[<>]/, '') + '.eml'), 'wb') {|f| f.write(mail.encoded) }
88 88 end
89 89 end
90 90
91 91 ActionMailer::Base.send :include, AsynchronousMailer
92 92
93 93 module TMail
94 94 # TMail::Unquoter.convert_to_with_fallback_on_iso_8859_1 introduced in TMail 1.2.7
95 95 # triggers a test failure in test_add_issue_with_japanese_keywords(MailHandlerTest)
96 96 class Unquoter
97 97 class << self
98 98 alias_method :convert_to, :convert_to_without_fallback_on_iso_8859_1
99 99 end
100 100 end
101 101
102 102 # Patch for TMail 1.2.7. See http://www.redmine.org/issues/8751
103 103 class Encoder
104 104 def puts_meta(str)
105 105 add_text str
106 106 end
107 107 end
108 108 end
109 109
110 110 module ActionController
111 111 module MimeResponds
112 112 class Responder
113 113 def api(&block)
114 114 any(:xml, :json, &block)
115 115 end
116 116 end
117 117 end
118 118 end
General Comments 0
You need to be logged in to leave comments. Login now