##// END OF EJS Templates
scm: return nil at model default_branch and override at git model (#8458, #6713)....
Toshi MARUYAMA -
r6010:5cfc42982b96
parent child
Show More
@@ -1,322 +1,322
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ScmFetchError < Exception; end
19 19
20 20 class Repository < ActiveRecord::Base
21 21 include Redmine::Ciphering
22 22
23 23 belongs_to :project
24 24 has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 25 has_many :changes, :through => :changesets
26 26
27 27 serialize :extra_info
28 28
29 29 # Raw SQL to delete changesets and changes in the database
30 30 # has_many :changesets, :dependent => :destroy is too slow for big repositories
31 31 before_destroy :clear_changesets
32 32
33 33 validates_length_of :password, :maximum => 255, :allow_nil => true
34 34 # Checks if the SCM is enabled when creating a repository
35 35 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
36 36
37 37 def self.human_attribute_name(attribute_key_name)
38 38 attr_name = attribute_key_name
39 39 if attr_name == "log_encoding"
40 40 attr_name = "commit_logs_encoding"
41 41 end
42 42 super(attr_name)
43 43 end
44 44
45 45 # Removes leading and trailing whitespace
46 46 def url=(arg)
47 47 write_attribute(:url, arg ? arg.to_s.strip : nil)
48 48 end
49 49
50 50 # Removes leading and trailing whitespace
51 51 def root_url=(arg)
52 52 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
53 53 end
54 54
55 55 def password
56 56 read_ciphered_attribute(:password)
57 57 end
58 58
59 59 def password=(arg)
60 60 write_ciphered_attribute(:password, arg)
61 61 end
62 62
63 63 def scm_adapter
64 64 self.class.scm_adapter_class
65 65 end
66 66
67 67 def scm
68 68 @scm ||= self.scm_adapter.new(url, root_url,
69 69 login, password, path_encoding)
70 70 update_attribute(:root_url, @scm.root_url) if root_url.blank?
71 71 @scm
72 72 end
73 73
74 74 def scm_name
75 75 self.class.scm_name
76 76 end
77 77
78 78 def merge_extra_info(arg)
79 79 h = extra_info || {}
80 80 return h if arg.nil?
81 81 h.merge!(arg)
82 82 write_attribute(:extra_info, h)
83 83 end
84 84
85 85 def report_last_commit
86 86 true
87 87 end
88 88
89 89 def supports_cat?
90 90 scm.supports_cat?
91 91 end
92 92
93 93 def supports_annotate?
94 94 scm.supports_annotate?
95 95 end
96 96
97 97 def supports_all_revisions?
98 98 true
99 99 end
100 100
101 101 def supports_directory_revisions?
102 102 false
103 103 end
104 104
105 105 def entry(path=nil, identifier=nil)
106 106 scm.entry(path, identifier)
107 107 end
108 108
109 109 def entries(path=nil, identifier=nil)
110 110 scm.entries(path, identifier)
111 111 end
112 112
113 113 def branches
114 114 scm.branches
115 115 end
116 116
117 117 def tags
118 118 scm.tags
119 119 end
120 120
121 121 def default_branch
122 scm.default_branch
122 nil
123 123 end
124 124
125 125 def properties(path, identifier=nil)
126 126 scm.properties(path, identifier)
127 127 end
128 128
129 129 def cat(path, identifier=nil)
130 130 scm.cat(path, identifier)
131 131 end
132 132
133 133 def diff(path, rev, rev_to)
134 134 scm.diff(path, rev, rev_to)
135 135 end
136 136
137 137 def diff_format_revisions(cs, cs_to, sep=':')
138 138 text = ""
139 139 text << cs_to.format_identifier + sep if cs_to
140 140 text << cs.format_identifier if cs
141 141 text
142 142 end
143 143
144 144 # Returns a path relative to the url of the repository
145 145 def relative_path(path)
146 146 path
147 147 end
148 148
149 149 # Finds and returns a revision with a number or the beginning of a hash
150 150 def find_changeset_by_name(name)
151 151 return nil if name.blank?
152 152 changesets.find(:first, :conditions => (name.match(/^\d*$/) ?
153 153 ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%']))
154 154 end
155 155
156 156 def latest_changeset
157 157 @latest_changeset ||= changesets.find(:first)
158 158 end
159 159
160 160 # Returns the latest changesets for +path+
161 161 # Default behaviour is to search in cached changesets
162 162 def latest_changesets(path, rev, limit=10)
163 163 if path.blank?
164 164 changesets.find(
165 165 :all,
166 166 :include => :user,
167 167 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
168 168 :limit => limit)
169 169 else
170 170 changes.find(
171 171 :all,
172 172 :include => {:changeset => :user},
173 173 :conditions => ["path = ?", path.with_leading_slash],
174 174 :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
175 175 :limit => limit
176 176 ).collect(&:changeset)
177 177 end
178 178 end
179 179
180 180 def scan_changesets_for_issue_ids
181 181 self.changesets.each(&:scan_comment_for_issue_ids)
182 182 end
183 183
184 184 # Returns an array of committers usernames and associated user_id
185 185 def committers
186 186 @committers ||= Changeset.connection.select_rows(
187 187 "SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
188 188 end
189 189
190 190 # Maps committers username to a user ids
191 191 def committer_ids=(h)
192 192 if h.is_a?(Hash)
193 193 committers.each do |committer, user_id|
194 194 new_user_id = h[committer]
195 195 if new_user_id && (new_user_id.to_i != user_id.to_i)
196 196 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
197 197 Changeset.update_all(
198 198 "user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }",
199 199 ["repository_id = ? AND committer = ?", id, committer])
200 200 end
201 201 end
202 202 @committers = nil
203 203 @found_committer_users = nil
204 204 true
205 205 else
206 206 false
207 207 end
208 208 end
209 209
210 210 # Returns the Redmine User corresponding to the given +committer+
211 211 # It will return nil if the committer is not yet mapped and if no User
212 212 # with the same username or email was found
213 213 def find_committer_user(committer)
214 214 unless committer.blank?
215 215 @found_committer_users ||= {}
216 216 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
217 217
218 218 user = nil
219 219 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
220 220 if c && c.user
221 221 user = c.user
222 222 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
223 223 username, email = $1.strip, $3
224 224 u = User.find_by_login(username)
225 225 u ||= User.find_by_mail(email) unless email.blank?
226 226 user = u
227 227 end
228 228 @found_committer_users[committer] = user
229 229 user
230 230 end
231 231 end
232 232
233 233 def repo_log_encoding
234 234 encoding = log_encoding.to_s.strip
235 235 encoding.blank? ? 'UTF-8' : encoding
236 236 end
237 237
238 238 # Fetches new changesets for all repositories of active projects
239 239 # Can be called periodically by an external script
240 240 # eg. ruby script/runner "Repository.fetch_changesets"
241 241 def self.fetch_changesets
242 242 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
243 243 if project.repository
244 244 begin
245 245 project.repository.fetch_changesets
246 246 rescue Redmine::Scm::Adapters::CommandFailed => e
247 247 logger.error "scm: error during fetching changesets: #{e.message}"
248 248 end
249 249 end
250 250 end
251 251 end
252 252
253 253 # scan changeset comments to find related and fixed issues for all repositories
254 254 def self.scan_changesets_for_issue_ids
255 255 find(:all).each(&:scan_changesets_for_issue_ids)
256 256 end
257 257
258 258 def self.scm_name
259 259 'Abstract'
260 260 end
261 261
262 262 def self.available_scm
263 263 subclasses.collect {|klass| [klass.scm_name, klass.name]}
264 264 end
265 265
266 266 def self.factory(klass_name, *args)
267 267 klass = "Repository::#{klass_name}".constantize
268 268 klass.new(*args)
269 269 rescue
270 270 nil
271 271 end
272 272
273 273 def self.scm_adapter_class
274 274 nil
275 275 end
276 276
277 277 def self.scm_command
278 278 ret = ""
279 279 begin
280 280 ret = self.scm_adapter_class.client_command if self.scm_adapter_class
281 281 rescue Exception => e
282 282 logger.error "scm: error during get command: #{e.message}"
283 283 end
284 284 ret
285 285 end
286 286
287 287 def self.scm_version_string
288 288 ret = ""
289 289 begin
290 290 ret = self.scm_adapter_class.client_version_string if self.scm_adapter_class
291 291 rescue Exception => e
292 292 logger.error "scm: error during get version string: #{e.message}"
293 293 end
294 294 ret
295 295 end
296 296
297 297 def self.scm_available
298 298 ret = false
299 299 begin
300 300 ret = self.scm_adapter_class.client_available if self.scm_adapter_class
301 301 rescue Exception => e
302 302 logger.error "scm: error during get scm available: #{e.message}"
303 303 end
304 304 ret
305 305 end
306 306
307 307 private
308 308
309 309 def before_save
310 310 # Strips url and root_url
311 311 url.strip!
312 312 root_url.strip!
313 313 true
314 314 end
315 315
316 316 def clear_changesets
317 317 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
318 318 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
319 319 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
320 320 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
321 321 end
322 322 end
@@ -1,176 +1,180
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 4 #
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; either version 2
8 8 # of the License, or (at your option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 18
19 19 require 'redmine/scm/adapters/git_adapter'
20 20
21 21 class Repository::Git < Repository
22 22 attr_protected :root_url
23 23 validates_presence_of :url
24 24
25 25 def self.human_attribute_name(attribute_key_name)
26 26 attr_name = attribute_key_name
27 27 if attr_name == "url"
28 28 attr_name = "path_to_repository"
29 29 end
30 30 super(attr_name)
31 31 end
32 32
33 33 def self.scm_adapter_class
34 34 Redmine::Scm::Adapters::GitAdapter
35 35 end
36 36
37 37 def self.scm_name
38 38 'Git'
39 39 end
40 40
41 41 def report_last_commit
42 42 extra_report_last_commit
43 43 end
44 44
45 45 def extra_report_last_commit
46 46 return false if extra_info.nil?
47 47 v = extra_info["extra_report_last_commit"]
48 48 return false if v.nil?
49 49 v.to_s != '0'
50 50 end
51 51
52 52 def supports_directory_revisions?
53 53 true
54 54 end
55 55
56 56 def repo_log_encoding
57 57 'UTF-8'
58 58 end
59 59
60 60 # Returns the identifier for the given git changeset
61 61 def self.changeset_identifier(changeset)
62 62 changeset.scmid
63 63 end
64 64
65 65 # Returns the readable identifier for the given git changeset
66 66 def self.format_changeset_identifier(changeset)
67 67 changeset.revision[0, 8]
68 68 end
69 69
70 70 def branches
71 71 scm.branches
72 72 end
73 73
74 74 def tags
75 75 scm.tags
76 76 end
77 77
78 def default_branch
79 scm.default_branch
80 end
81
78 82 def find_changeset_by_name(name)
79 83 return nil if name.nil? || name.empty?
80 84 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
81 85 return e if e
82 86 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
83 87 end
84 88
85 89 def entries(path=nil, identifier=nil)
86 90 scm.entries(path,
87 91 identifier,
88 92 options = {:report_last_commit => extra_report_last_commit})
89 93 end
90 94
91 95 # In Git and Mercurial, revisions are not in date order.
92 96 # Redmine Mercurial fixed issues.
93 97 # * Redmine Takes Too Long On Large Mercurial Repository
94 98 # http://www.redmine.org/issues/3449
95 99 # * Sorting for changesets might go wrong on Mercurial repos
96 100 # http://www.redmine.org/issues/3567
97 101 #
98 102 # Database revision column is text, so Redmine can not sort by revision.
99 103 # Mercurial has revision number, and revision number guarantees revision order.
100 104 # Redmine Mercurial model stored revisions ordered by database id to database.
101 105 # So, Redmine Mercurial model can use correct ordering revisions.
102 106 #
103 107 # Redmine Mercurial adapter uses "hg log -r 0:tip --limit 10"
104 108 # to get limited revisions from old to new.
105 109 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
106 110 #
107 111 # The repository can still be fully reloaded by calling #clear_changesets
108 112 # before fetching changesets (eg. for offline resync)
109 113 def fetch_changesets
110 114 scm_brs = branches
111 115 return if scm_brs.nil? || scm_brs.empty?
112 116 h1 = extra_info || {}
113 117 h = h1.dup
114 118 h["branches"] ||= {}
115 119 h["db_consistent"] ||= {}
116 120 if changesets.count == 0
117 121 h["db_consistent"]["ordering"] = 1
118 122 merge_extra_info(h)
119 123 self.save
120 124 elsif ! h["db_consistent"].has_key?("ordering")
121 125 h["db_consistent"]["ordering"] = 0
122 126 merge_extra_info(h)
123 127 self.save
124 128 end
125 129 scm_brs.each do |br|
126 130 from_scmid = nil
127 131 from_scmid = h["branches"][br]["last_scmid"] if h["branches"][br]
128 132 h["branches"][br] ||= {}
129 133 scm.revisions('', from_scmid, br, {:reverse => true}) do |rev|
130 134 db_rev = find_changeset_by_name(rev.revision)
131 135 transaction do
132 136 if db_rev.nil?
133 137 save_revision(rev)
134 138 end
135 139 h["branches"][br]["last_scmid"] = rev.scmid
136 140 merge_extra_info(h)
137 141 self.save
138 142 end
139 143 end
140 144 end
141 145 end
142 146
143 147 def save_revision(rev)
144 148 changeset = Changeset.new(
145 149 :repository => self,
146 150 :revision => rev.identifier,
147 151 :scmid => rev.scmid,
148 152 :committer => rev.author,
149 153 :committed_on => rev.time,
150 154 :comments => rev.message
151 155 )
152 156 if changeset.save
153 157 rev.paths.each do |file|
154 158 Change.create(
155 159 :changeset => changeset,
156 160 :action => file[:action],
157 161 :path => file[:path])
158 162 end
159 163 end
160 164 end
161 165 private :save_revision
162 166
163 167 def latest_changesets(path,rev,limit=10)
164 168 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
165 169 return [] if revisions.nil? || revisions.empty?
166 170
167 171 changesets.find(
168 172 :all,
169 173 :conditions => [
170 174 "scmid IN (?)",
171 175 revisions.map!{|c| c.scmid}
172 176 ],
173 177 :order => 'committed_on DESC'
174 178 )
175 179 end
176 180 end
General Comments 0
You need to be logged in to leave comments. Login now