##// END OF EJS Templates
scm: git: move saving changesets from adapter to model (#3396)....
Toshi MARUYAMA -
r4839:d7aa303a5ca0
parent child
Show More
@@ -1,98 +1,119
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 # Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/git_adapter'
19 19
20 20 class Repository::Git < Repository
21 21 attr_protected :root_url
22 22 validates_presence_of :url
23 23
24 24 def self.scm_adapter_class
25 25 Redmine::Scm::Adapters::GitAdapter
26 26 end
27 27
28 28 def self.scm_name
29 29 'Git'
30 30 end
31 31
32 32 # Returns the identifier for the given git changeset
33 33 def self.changeset_identifier(changeset)
34 34 changeset.scmid
35 35 end
36 36
37 37 # Returns the readable identifier for the given git changeset
38 38 def self.format_changeset_identifier(changeset)
39 39 changeset.revision[0, 8]
40 40 end
41 41
42 42 def branches
43 43 scm.branches
44 44 end
45 45
46 46 def tags
47 47 scm.tags
48 48 end
49 49
50 50 def find_changeset_by_name(name)
51 51 return nil if name.nil? || name.empty?
52 52 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
53 53 return e if e
54 54 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
55 55 end
56 56
57 57 # With SCM's that have a sequential commit numbering, redmine is able to be
58 58 # clever and only fetch changesets going forward from the most recent one
59 59 # it knows about. However, with git, you never know if people have merged
60 60 # commits into the middle of the repository history, so we should parse
61 61 # the entire log. Since it's way too slow for large repositories, we only
62 62 # parse 1 week before the last known commit.
63 63 # The repository can still be fully reloaded by calling #clear_changesets
64 64 # before fetching changesets (eg. for offline resync)
65 65 def fetch_changesets
66 66 c = changesets.find(:first, :order => 'committed_on DESC')
67 67 since = (c ? c.committed_on - 7.days : nil)
68 68
69 69 revisions = scm.revisions('', nil, nil, :all => true, :since => since)
70 70 return if revisions.nil? || revisions.empty?
71 71
72 72 recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
73 73
74 74 # Clean out revisions that are no longer in git
75 75 recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
76 76
77 77 # Subtract revisions that redmine already knows about
78 78 recent_revisions = recent_changesets.map{|c| c.scmid}
79 79 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
80 80
81 81 # Save the remaining ones to the database
82 revisions.each{|r| r.save(self)} unless revisions.nil?
82 unless revisions.nil?
83 revisions.each do |rev|
84 transaction do
85 changeset = Changeset.new(
86 :repository => self,
87 :revision => rev.identifier,
88 :scmid => rev.scmid,
89 :committer => rev.author,
90 :committed_on => rev.time,
91 :comments => rev.message)
92
93 if changeset.save
94 rev.paths.each do |file|
95 Change.create(
96 :changeset => changeset,
97 :action => file[:action],
98 :path => file[:path])
99 end
100 end
101 end
102 end
103 end
83 104 end
84 105
85 106 def latest_changesets(path,rev,limit=10)
86 107 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
87 108 return [] if revisions.nil? || revisions.empty?
88 109
89 110 changesets.find(
90 111 :all,
91 112 :conditions => [
92 113 "scmid IN (?)",
93 114 revisions.map!{|c| c.scmid}
94 115 ],
95 116 :order => 'committed_on DESC'
96 117 )
97 118 end
98 119 end
@@ -1,373 +1,352
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'cgi'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class CommandFailed < StandardError #:nodoc:
24 24 end
25 25
26 26 class AbstractAdapter #:nodoc:
27 27 class << self
28 28 def client_command
29 29 ""
30 30 end
31 31
32 32 # Returns the version of the scm client
33 33 # Eg: [1, 5, 0] or [] if unknown
34 34 def client_version
35 35 []
36 36 end
37 37
38 38 # Returns the version string of the scm client
39 39 # Eg: '1.5.0' or 'Unknown version' if unknown
40 40 def client_version_string
41 41 v = client_version || 'Unknown version'
42 42 v.is_a?(Array) ? v.join('.') : v.to_s
43 43 end
44 44
45 45 # Returns true if the current client version is above
46 46 # or equals the given one
47 47 # If option is :unknown is set to true, it will return
48 48 # true if the client version is unknown
49 49 def client_version_above?(v, options={})
50 50 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
51 51 end
52 52
53 53 def client_available
54 54 true
55 55 end
56 56
57 57 def shell_quote(str)
58 58 if Redmine::Platform.mswin?
59 59 '"' + str.gsub(/"/, '\\"') + '"'
60 60 else
61 61 "'" + str.gsub(/'/, "'\"'\"'") + "'"
62 62 end
63 63 end
64 64 end
65 65
66 66 def initialize(url, root_url=nil, login=nil, password=nil,
67 67 path_encoding=nil)
68 68 @url = url
69 69 @login = login if login && !login.empty?
70 70 @password = (password || "") if @login
71 71 @root_url = root_url.blank? ? retrieve_root_url : root_url
72 72 end
73 73
74 74 def adapter_name
75 75 'Abstract'
76 76 end
77 77
78 78 def supports_cat?
79 79 true
80 80 end
81 81
82 82 def supports_annotate?
83 83 respond_to?('annotate')
84 84 end
85 85
86 86 def root_url
87 87 @root_url
88 88 end
89 89
90 90 def url
91 91 @url
92 92 end
93 93
94 94 # get info about the svn repository
95 95 def info
96 96 return nil
97 97 end
98 98
99 99 # Returns the entry identified by path and revision identifier
100 100 # or nil if entry doesn't exist in the repository
101 101 def entry(path=nil, identifier=nil)
102 102 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
103 103 search_path = parts[0..-2].join('/')
104 104 search_name = parts[-1]
105 105 if search_path.blank? && search_name.blank?
106 106 # Root entry
107 107 Entry.new(:path => '', :kind => 'dir')
108 108 else
109 109 # Search for the entry in the parent directory
110 110 es = entries(search_path, identifier)
111 111 es ? es.detect {|e| e.name == search_name} : nil
112 112 end
113 113 end
114 114
115 115 # Returns an Entries collection
116 116 # or nil if the given path doesn't exist in the repository
117 117 def entries(path=nil, identifier=nil)
118 118 return nil
119 119 end
120 120
121 121 def branches
122 122 return nil
123 123 end
124 124
125 125 def tags
126 126 return nil
127 127 end
128 128
129 129 def default_branch
130 130 return nil
131 131 end
132 132
133 133 def properties(path, identifier=nil)
134 134 return nil
135 135 end
136 136
137 137 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
138 138 return nil
139 139 end
140 140
141 141 def diff(path, identifier_from, identifier_to=nil)
142 142 return nil
143 143 end
144 144
145 145 def cat(path, identifier=nil)
146 146 return nil
147 147 end
148 148
149 149 def with_leading_slash(path)
150 150 path ||= ''
151 151 (path[0,1]!="/") ? "/#{path}" : path
152 152 end
153 153
154 154 def with_trailling_slash(path)
155 155 path ||= ''
156 156 (path[-1,1] == "/") ? path : "#{path}/"
157 157 end
158 158
159 159 def without_leading_slash(path)
160 160 path ||= ''
161 161 path.gsub(%r{^/+}, '')
162 162 end
163 163
164 164 def without_trailling_slash(path)
165 165 path ||= ''
166 166 (path[-1,1] == "/") ? path[0..-2] : path
167 167 end
168 168
169 169 def shell_quote(str)
170 170 self.class.shell_quote(str)
171 171 end
172 172
173 173 private
174 174 def retrieve_root_url
175 175 info = self.info
176 176 info ? info.root_url : nil
177 177 end
178 178
179 179 def target(path)
180 180 path ||= ''
181 181 base = path.match(/^\//) ? root_url : url
182 182 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
183 183 end
184 184
185 185 def logger
186 186 self.class.logger
187 187 end
188 188
189 189 def shellout(cmd, &block)
190 190 self.class.shellout(cmd, &block)
191 191 end
192 192
193 193 def self.logger
194 194 RAILS_DEFAULT_LOGGER
195 195 end
196 196
197 197 def self.shellout(cmd, &block)
198 198 logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug?
199 199 if Rails.env == 'development'
200 200 # Capture stderr when running in dev environment
201 201 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
202 202 end
203 203 begin
204 204 if RUBY_VERSION < '1.9'
205 205 mode = "r+"
206 206 else
207 207 mode = "r+:ASCII-8BIT"
208 208 end
209 209 IO.popen(cmd, mode) do |io|
210 210 io.close_write
211 211 block.call(io) if block_given?
212 212 end
213 213 rescue Errno::ENOENT => e
214 214 msg = strip_credential(e.message)
215 215 # The command failed, log it and re-raise
216 216 logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}")
217 217 raise CommandFailed.new(msg)
218 218 end
219 219 end
220 220
221 221 # Hides username/password in a given command
222 222 def self.strip_credential(cmd)
223 223 q = (Redmine::Platform.mswin? ? '"' : "'")
224 224 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
225 225 end
226 226
227 227 def strip_credential(cmd)
228 228 self.class.strip_credential(cmd)
229 229 end
230 230
231 231 def scm_iconv(to, from, str)
232 232 return nil if str.nil?
233 233 return str if to == from
234 234 begin
235 235 Iconv.conv(to, from, str)
236 236 rescue Iconv::Failure => err
237 237 logger.error("failed to convert from #{from} to #{to}. #{err}")
238 238 nil
239 239 end
240 240 end
241 241 end
242 242
243 243 class Entries < Array
244 244 def sort_by_name
245 245 sort {|x,y|
246 246 if x.kind == y.kind
247 247 x.name.to_s <=> y.name.to_s
248 248 else
249 249 x.kind <=> y.kind
250 250 end
251 251 }
252 252 end
253 253
254 254 def revisions
255 255 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
256 256 end
257 257 end
258 258
259 259 class Info
260 260 attr_accessor :root_url, :lastrev
261 261 def initialize(attributes={})
262 262 self.root_url = attributes[:root_url] if attributes[:root_url]
263 263 self.lastrev = attributes[:lastrev]
264 264 end
265 265 end
266 266
267 267 class Entry
268 268 attr_accessor :name, :path, :kind, :size, :lastrev
269 269 def initialize(attributes={})
270 270 self.name = attributes[:name] if attributes[:name]
271 271 self.path = attributes[:path] if attributes[:path]
272 272 self.kind = attributes[:kind] if attributes[:kind]
273 273 self.size = attributes[:size].to_i if attributes[:size]
274 274 self.lastrev = attributes[:lastrev]
275 275 end
276 276
277 277 def is_file?
278 278 'file' == self.kind
279 279 end
280 280
281 281 def is_dir?
282 282 'dir' == self.kind
283 283 end
284 284
285 285 def is_text?
286 286 Redmine::MimeType.is_type?('text', name)
287 287 end
288 288 end
289 289
290 290 class Revisions < Array
291 291 def latest
292 292 sort {|x,y|
293 293 unless x.time.nil? or y.time.nil?
294 294 x.time <=> y.time
295 295 else
296 296 0
297 297 end
298 298 }.last
299 299 end
300 300 end
301 301
302 302 class Revision
303 303 attr_accessor :scmid, :name, :author, :time, :message, :paths, :revision, :branch
304 304 attr_writer :identifier
305 305
306 306 def initialize(attributes={})
307 307 self.identifier = attributes[:identifier]
308 308 self.scmid = attributes[:scmid]
309 309 self.name = attributes[:name] || self.identifier
310 310 self.author = attributes[:author]
311 311 self.time = attributes[:time]
312 312 self.message = attributes[:message] || ""
313 313 self.paths = attributes[:paths]
314 314 self.revision = attributes[:revision]
315 315 self.branch = attributes[:branch]
316 316 end
317 317
318 318 # Returns the identifier of this revision; see also Changeset model
319 319 def identifier
320 320 (@identifier || revision).to_s
321 321 end
322 322
323 323 # Returns the readable identifier.
324 324 def format_identifier
325 325 identifier
326 326 end
327
328 def save(repo)
329 Changeset.transaction do
330 changeset = Changeset.new(
331 :repository => repo,
332 :revision => identifier,
333 :scmid => scmid,
334 :committer => author,
335 :committed_on => time,
336 :comments => message)
337
338 if changeset.save
339 paths.each do |file|
340 Change.create(
341 :changeset => changeset,
342 :action => file[:action],
343 :path => file[:path])
344 end
345 end
346 end
347 end
348 327 end
349 328
350 329 class Annotate
351 330 attr_reader :lines, :revisions
352 331
353 332 def initialize
354 333 @lines = []
355 334 @revisions = []
356 335 end
357 336
358 337 def add_line(line, revision)
359 338 @lines << line
360 339 @revisions << revision
361 340 end
362 341
363 342 def content
364 343 content = lines.join("\n")
365 344 end
366 345
367 346 def empty?
368 347 lines.empty?
369 348 end
370 349 end
371 350 end
372 351 end
373 352 end
General Comments 0
You need to be logged in to leave comments. Login now