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