##// END OF EJS Templates
scm: catch CommandFailed during bulk Repository.fetch_changesets (#4455)....
Toshi MARUYAMA -
r4704:60d80653ba4b
parent child
Show More
@@ -1,243 +1,247
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Repository < ActiveRecord::Base
18 class Repository < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
20 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 has_many :changes, :through => :changesets
21 has_many :changes, :through => :changesets
22
22
23 # Raw SQL to delete changesets and changes in the database
23 # Raw SQL to delete changesets and changes in the database
24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
25 before_destroy :clear_changesets
25 before_destroy :clear_changesets
26
26
27 # Checks if the SCM is enabled when creating a repository
27 # Checks if the SCM is enabled when creating a repository
28 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
28 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
29
29
30 # Removes leading and trailing whitespace
30 # Removes leading and trailing whitespace
31 def url=(arg)
31 def url=(arg)
32 write_attribute(:url, arg ? arg.to_s.strip : nil)
32 write_attribute(:url, arg ? arg.to_s.strip : nil)
33 end
33 end
34
34
35 # Removes leading and trailing whitespace
35 # Removes leading and trailing whitespace
36 def root_url=(arg)
36 def root_url=(arg)
37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
38 end
38 end
39
39
40 def scm_adapter
40 def scm_adapter
41 self.class.scm_adapter_class
41 self.class.scm_adapter_class
42 end
42 end
43
43
44 def scm
44 def scm
45 @scm ||= self.scm_adapter.new url, root_url, login, password
45 @scm ||= self.scm_adapter.new url, root_url, login, password
46 update_attribute(:root_url, @scm.root_url) if root_url.blank?
46 update_attribute(:root_url, @scm.root_url) if root_url.blank?
47 @scm
47 @scm
48 end
48 end
49
49
50 def scm_name
50 def scm_name
51 self.class.scm_name
51 self.class.scm_name
52 end
52 end
53
53
54 def supports_cat?
54 def supports_cat?
55 scm.supports_cat?
55 scm.supports_cat?
56 end
56 end
57
57
58 def supports_annotate?
58 def supports_annotate?
59 scm.supports_annotate?
59 scm.supports_annotate?
60 end
60 end
61
61
62 def entry(path=nil, identifier=nil)
62 def entry(path=nil, identifier=nil)
63 scm.entry(path, identifier)
63 scm.entry(path, identifier)
64 end
64 end
65
65
66 def entries(path=nil, identifier=nil)
66 def entries(path=nil, identifier=nil)
67 scm.entries(path, identifier)
67 scm.entries(path, identifier)
68 end
68 end
69
69
70 def branches
70 def branches
71 scm.branches
71 scm.branches
72 end
72 end
73
73
74 def tags
74 def tags
75 scm.tags
75 scm.tags
76 end
76 end
77
77
78 def default_branch
78 def default_branch
79 scm.default_branch
79 scm.default_branch
80 end
80 end
81
81
82 def properties(path, identifier=nil)
82 def properties(path, identifier=nil)
83 scm.properties(path, identifier)
83 scm.properties(path, identifier)
84 end
84 end
85
85
86 def cat(path, identifier=nil)
86 def cat(path, identifier=nil)
87 scm.cat(path, identifier)
87 scm.cat(path, identifier)
88 end
88 end
89
89
90 def diff(path, rev, rev_to)
90 def diff(path, rev, rev_to)
91 scm.diff(path, rev, rev_to)
91 scm.diff(path, rev, rev_to)
92 end
92 end
93
93
94 def diff_format_revisions(cs, cs_to, sep=':')
94 def diff_format_revisions(cs, cs_to, sep=':')
95 text = ""
95 text = ""
96 text << cs_to.format_identifier + sep if cs_to
96 text << cs_to.format_identifier + sep if cs_to
97 text << cs.format_identifier if cs
97 text << cs.format_identifier if cs
98 text
98 text
99 end
99 end
100
100
101 # Returns a path relative to the url of the repository
101 # Returns a path relative to the url of the repository
102 def relative_path(path)
102 def relative_path(path)
103 path
103 path
104 end
104 end
105
105
106 # Finds and returns a revision with a number or the beginning of a hash
106 # Finds and returns a revision with a number or the beginning of a hash
107 def find_changeset_by_name(name)
107 def find_changeset_by_name(name)
108 return nil if name.blank?
108 return nil if name.blank?
109 changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
109 changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
110 end
110 end
111
111
112 def latest_changeset
112 def latest_changeset
113 @latest_changeset ||= changesets.find(:first)
113 @latest_changeset ||= changesets.find(:first)
114 end
114 end
115
115
116 # Returns the latest changesets for +path+
116 # Returns the latest changesets for +path+
117 # Default behaviour is to search in cached changesets
117 # Default behaviour is to search in cached changesets
118 def latest_changesets(path, rev, limit=10)
118 def latest_changesets(path, rev, limit=10)
119 if path.blank?
119 if path.blank?
120 changesets.find(:all, :include => :user,
120 changesets.find(:all, :include => :user,
121 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
121 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
122 :limit => limit)
122 :limit => limit)
123 else
123 else
124 changes.find(:all, :include => {:changeset => :user},
124 changes.find(:all, :include => {:changeset => :user},
125 :conditions => ["path = ?", path.with_leading_slash],
125 :conditions => ["path = ?", path.with_leading_slash],
126 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
126 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
127 :limit => limit).collect(&:changeset)
127 :limit => limit).collect(&:changeset)
128 end
128 end
129 end
129 end
130
130
131 def scan_changesets_for_issue_ids
131 def scan_changesets_for_issue_ids
132 self.changesets.each(&:scan_comment_for_issue_ids)
132 self.changesets.each(&:scan_comment_for_issue_ids)
133 end
133 end
134
134
135 # Returns an array of committers usernames and associated user_id
135 # Returns an array of committers usernames and associated user_id
136 def committers
136 def committers
137 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
137 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
138 end
138 end
139
139
140 # Maps committers username to a user ids
140 # Maps committers username to a user ids
141 def committer_ids=(h)
141 def committer_ids=(h)
142 if h.is_a?(Hash)
142 if h.is_a?(Hash)
143 committers.each do |committer, user_id|
143 committers.each do |committer, user_id|
144 new_user_id = h[committer]
144 new_user_id = h[committer]
145 if new_user_id && (new_user_id.to_i != user_id.to_i)
145 if new_user_id && (new_user_id.to_i != user_id.to_i)
146 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
146 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
147 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
147 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
148 end
148 end
149 end
149 end
150 @committers = nil
150 @committers = nil
151 @found_committer_users = nil
151 @found_committer_users = nil
152 true
152 true
153 else
153 else
154 false
154 false
155 end
155 end
156 end
156 end
157
157
158 # Returns the Redmine User corresponding to the given +committer+
158 # Returns the Redmine User corresponding to the given +committer+
159 # It will return nil if the committer is not yet mapped and if no User
159 # It will return nil if the committer is not yet mapped and if no User
160 # with the same username or email was found
160 # with the same username or email was found
161 def find_committer_user(committer)
161 def find_committer_user(committer)
162 unless committer.blank?
162 unless committer.blank?
163 @found_committer_users ||= {}
163 @found_committer_users ||= {}
164 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
164 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
165
165
166 user = nil
166 user = nil
167 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
167 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
168 if c && c.user
168 if c && c.user
169 user = c.user
169 user = c.user
170 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
170 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
171 username, email = $1.strip, $3
171 username, email = $1.strip, $3
172 u = User.find_by_login(username)
172 u = User.find_by_login(username)
173 u ||= User.find_by_mail(email) unless email.blank?
173 u ||= User.find_by_mail(email) unless email.blank?
174 user = u
174 user = u
175 end
175 end
176 @found_committer_users[committer] = user
176 @found_committer_users[committer] = user
177 user
177 user
178 end
178 end
179 end
179 end
180
180
181 # Fetches new changesets for all repositories of active projects
181 # Fetches new changesets for all repositories of active projects
182 # Can be called periodically by an external script
182 # Can be called periodically by an external script
183 # eg. ruby script/runner "Repository.fetch_changesets"
183 # eg. ruby script/runner "Repository.fetch_changesets"
184 def self.fetch_changesets
184 def self.fetch_changesets
185 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
185 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
186 if project.repository
186 if project.repository
187 project.repository.fetch_changesets
187 begin
188 project.repository.fetch_changesets
189 rescue Redmine::Scm::Adapters::CommandFailed => e
190 logger.error "Repository: error during fetching changesets: #{e.message}"
191 end
188 end
192 end
189 end
193 end
190 end
194 end
191
195
192 # scan changeset comments to find related and fixed issues for all repositories
196 # scan changeset comments to find related and fixed issues for all repositories
193 def self.scan_changesets_for_issue_ids
197 def self.scan_changesets_for_issue_ids
194 find(:all).each(&:scan_changesets_for_issue_ids)
198 find(:all).each(&:scan_changesets_for_issue_ids)
195 end
199 end
196
200
197 def self.scm_name
201 def self.scm_name
198 'Abstract'
202 'Abstract'
199 end
203 end
200
204
201 def self.available_scm
205 def self.available_scm
202 subclasses.collect {|klass| [klass.scm_name, klass.name]}
206 subclasses.collect {|klass| [klass.scm_name, klass.name]}
203 end
207 end
204
208
205 def self.factory(klass_name, *args)
209 def self.factory(klass_name, *args)
206 klass = "Repository::#{klass_name}".constantize
210 klass = "Repository::#{klass_name}".constantize
207 klass.new(*args)
211 klass.new(*args)
208 rescue
212 rescue
209 nil
213 nil
210 end
214 end
211
215
212 def self.scm_adapter_class
216 def self.scm_adapter_class
213 nil
217 nil
214 end
218 end
215
219
216 def self.scm_command
220 def self.scm_command
217 self.scm_adapter_class.nil? ? "" : self.scm_adapter_class.client_command
221 self.scm_adapter_class.nil? ? "" : self.scm_adapter_class.client_command
218 end
222 end
219
223
220 def self.scm_version_string
224 def self.scm_version_string
221 self.scm_adapter_class.nil? ? "" : self.scm_adapter_class.client_version_string
225 self.scm_adapter_class.nil? ? "" : self.scm_adapter_class.client_version_string
222 end
226 end
223
227
224 def self.scm_available
228 def self.scm_available
225 self.scm_adapter_class.nil? ? false : self.scm_adapter_class.client_available
229 self.scm_adapter_class.nil? ? false : self.scm_adapter_class.client_available
226 end
230 end
227
231
228 private
232 private
229
233
230 def before_save
234 def before_save
231 # Strips url and root_url
235 # Strips url and root_url
232 url.strip!
236 url.strip!
233 root_url.strip!
237 root_url.strip!
234 true
238 true
235 end
239 end
236
240
237 def clear_changesets
241 def clear_changesets
238 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
242 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
239 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
243 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
240 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
244 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
241 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
245 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
242 end
246 end
243 end
247 end
General Comments 0
You need to be logged in to leave comments. Login now