##// END OF EJS Templates
scm: catch exception of getting command and version in model (#4273)....
Toshi MARUYAMA -
r4762:a092f5e0b9af
parent child
Show More
@@ -1,247 +1,265
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 begin
187 begin
188 project.repository.fetch_changesets
188 project.repository.fetch_changesets
189 rescue Redmine::Scm::Adapters::CommandFailed => e
189 rescue Redmine::Scm::Adapters::CommandFailed => e
190 logger.error "Repository: error during fetching changesets: #{e.message}"
190 logger.error "scm: error during fetching changesets: #{e.message}"
191 end
191 end
192 end
192 end
193 end
193 end
194 end
194 end
195
195
196 # 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
197 def self.scan_changesets_for_issue_ids
197 def self.scan_changesets_for_issue_ids
198 find(:all).each(&:scan_changesets_for_issue_ids)
198 find(:all).each(&:scan_changesets_for_issue_ids)
199 end
199 end
200
200
201 def self.scm_name
201 def self.scm_name
202 'Abstract'
202 'Abstract'
203 end
203 end
204
204
205 def self.available_scm
205 def self.available_scm
206 subclasses.collect {|klass| [klass.scm_name, klass.name]}
206 subclasses.collect {|klass| [klass.scm_name, klass.name]}
207 end
207 end
208
208
209 def self.factory(klass_name, *args)
209 def self.factory(klass_name, *args)
210 klass = "Repository::#{klass_name}".constantize
210 klass = "Repository::#{klass_name}".constantize
211 klass.new(*args)
211 klass.new(*args)
212 rescue
212 rescue
213 nil
213 nil
214 end
214 end
215
215
216 def self.scm_adapter_class
216 def self.scm_adapter_class
217 nil
217 nil
218 end
218 end
219
219
220 def self.scm_command
220 def self.scm_command
221 self.scm_adapter_class.nil? ? "" : self.scm_adapter_class.client_command
221 ret = ""
222 begin
223 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
224 rescue Redmine::Scm::Adapters::CommandFailed => e
225 logger.error "scm: error during get command: #{e.message}"
226 end
227 ret
222 end
228 end
223
229
224 def self.scm_version_string
230 def self.scm_version_string
225 self.scm_adapter_class.nil? ? "" : self.scm_adapter_class.client_version_string
231 ret = ""
232 begin
233 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
234 rescue Redmine::Scm::Adapters::CommandFailed => e
235 logger.error "scm: error during get version string: #{e.message}"
236 end
237 ret
226 end
238 end
227
239
228 def self.scm_available
240 def self.scm_available
229 self.scm_adapter_class.nil? ? false : self.scm_adapter_class.client_available
241 ret = false
242 begin
243 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
244 rescue Redmine::Scm::Adapters::CommandFailed => e
245 logger.error "scm: error during get scm available: #{e.message}"
246 end
247 ret
230 end
248 end
231
249
232 private
250 private
233
251
234 def before_save
252 def before_save
235 # Strips url and root_url
253 # Strips url and root_url
236 url.strip!
254 url.strip!
237 root_url.strip!
255 root_url.strip!
238 true
256 true
239 end
257 end
240
258
241 def clear_changesets
259 def clear_changesets
242 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
260 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
243 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
261 connection.delete("DELETE FROM #{ch} WHERE #{ch}.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})")
262 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
245 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
263 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
246 end
264 end
247 end
265 end
General Comments 0
You need to be logged in to leave comments. Login now