##// END OF EJS Templates
Wraps changeset creation inside a single transation....
Jean-Philippe Lang -
r3355:103698b371ee
parent child
Show More
@@ -1,331 +1,333
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 require 'cgi'
18 require 'cgi'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class CommandFailed < StandardError #:nodoc:
23 class CommandFailed < StandardError #:nodoc:
24 end
24 end
25
25
26 class AbstractAdapter #:nodoc:
26 class AbstractAdapter #:nodoc:
27 class << self
27 class << self
28 # Returns the version of the scm client
28 # Returns the version of the scm client
29 # Eg: [1, 5, 0] or [] if unknown
29 # Eg: [1, 5, 0] or [] if unknown
30 def client_version
30 def client_version
31 []
31 []
32 end
32 end
33
33
34 # Returns the version string of the scm client
34 # Returns the version string of the scm client
35 # Eg: '1.5.0' or 'Unknown version' if unknown
35 # Eg: '1.5.0' or 'Unknown version' if unknown
36 def client_version_string
36 def client_version_string
37 v = client_version || 'Unknown version'
37 v = client_version || 'Unknown version'
38 v.is_a?(Array) ? v.join('.') : v.to_s
38 v.is_a?(Array) ? v.join('.') : v.to_s
39 end
39 end
40
40
41 # Returns true if the current client version is above
41 # Returns true if the current client version is above
42 # or equals the given one
42 # or equals the given one
43 # If option is :unknown is set to true, it will return
43 # If option is :unknown is set to true, it will return
44 # true if the client version is unknown
44 # true if the client version is unknown
45 def client_version_above?(v, options={})
45 def client_version_above?(v, options={})
46 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
46 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
47 end
47 end
48 end
48 end
49
49
50 def initialize(url, root_url=nil, login=nil, password=nil)
50 def initialize(url, root_url=nil, login=nil, password=nil)
51 @url = url
51 @url = url
52 @login = login if login && !login.empty?
52 @login = login if login && !login.empty?
53 @password = (password || "") if @login
53 @password = (password || "") if @login
54 @root_url = root_url.blank? ? retrieve_root_url : root_url
54 @root_url = root_url.blank? ? retrieve_root_url : root_url
55 end
55 end
56
56
57 def adapter_name
57 def adapter_name
58 'Abstract'
58 'Abstract'
59 end
59 end
60
60
61 def supports_cat?
61 def supports_cat?
62 true
62 true
63 end
63 end
64
64
65 def supports_annotate?
65 def supports_annotate?
66 respond_to?('annotate')
66 respond_to?('annotate')
67 end
67 end
68
68
69 def root_url
69 def root_url
70 @root_url
70 @root_url
71 end
71 end
72
72
73 def url
73 def url
74 @url
74 @url
75 end
75 end
76
76
77 # get info about the svn repository
77 # get info about the svn repository
78 def info
78 def info
79 return nil
79 return nil
80 end
80 end
81
81
82 # Returns the entry identified by path and revision identifier
82 # Returns the entry identified by path and revision identifier
83 # or nil if entry doesn't exist in the repository
83 # or nil if entry doesn't exist in the repository
84 def entry(path=nil, identifier=nil)
84 def entry(path=nil, identifier=nil)
85 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
85 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
86 search_path = parts[0..-2].join('/')
86 search_path = parts[0..-2].join('/')
87 search_name = parts[-1]
87 search_name = parts[-1]
88 if search_path.blank? && search_name.blank?
88 if search_path.blank? && search_name.blank?
89 # Root entry
89 # Root entry
90 Entry.new(:path => '', :kind => 'dir')
90 Entry.new(:path => '', :kind => 'dir')
91 else
91 else
92 # Search for the entry in the parent directory
92 # Search for the entry in the parent directory
93 es = entries(search_path, identifier)
93 es = entries(search_path, identifier)
94 es ? es.detect {|e| e.name == search_name} : nil
94 es ? es.detect {|e| e.name == search_name} : nil
95 end
95 end
96 end
96 end
97
97
98 # Returns an Entries collection
98 # Returns an Entries collection
99 # or nil if the given path doesn't exist in the repository
99 # or nil if the given path doesn't exist in the repository
100 def entries(path=nil, identifier=nil)
100 def entries(path=nil, identifier=nil)
101 return nil
101 return nil
102 end
102 end
103
103
104 def branches
104 def branches
105 return nil
105 return nil
106 end
106 end
107
107
108 def tags
108 def tags
109 return nil
109 return nil
110 end
110 end
111
111
112 def default_branch
112 def default_branch
113 return nil
113 return nil
114 end
114 end
115
115
116 def properties(path, identifier=nil)
116 def properties(path, identifier=nil)
117 return nil
117 return nil
118 end
118 end
119
119
120 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
120 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
121 return nil
121 return nil
122 end
122 end
123
123
124 def diff(path, identifier_from, identifier_to=nil)
124 def diff(path, identifier_from, identifier_to=nil)
125 return nil
125 return nil
126 end
126 end
127
127
128 def cat(path, identifier=nil)
128 def cat(path, identifier=nil)
129 return nil
129 return nil
130 end
130 end
131
131
132 def with_leading_slash(path)
132 def with_leading_slash(path)
133 path ||= ''
133 path ||= ''
134 (path[0,1]!="/") ? "/#{path}" : path
134 (path[0,1]!="/") ? "/#{path}" : path
135 end
135 end
136
136
137 def with_trailling_slash(path)
137 def with_trailling_slash(path)
138 path ||= ''
138 path ||= ''
139 (path[-1,1] == "/") ? path : "#{path}/"
139 (path[-1,1] == "/") ? path : "#{path}/"
140 end
140 end
141
141
142 def without_leading_slash(path)
142 def without_leading_slash(path)
143 path ||= ''
143 path ||= ''
144 path.gsub(%r{^/+}, '')
144 path.gsub(%r{^/+}, '')
145 end
145 end
146
146
147 def without_trailling_slash(path)
147 def without_trailling_slash(path)
148 path ||= ''
148 path ||= ''
149 (path[-1,1] == "/") ? path[0..-2] : path
149 (path[-1,1] == "/") ? path[0..-2] : path
150 end
150 end
151
151
152 def shell_quote(str)
152 def shell_quote(str)
153 if Redmine::Platform.mswin?
153 if Redmine::Platform.mswin?
154 '"' + str.gsub(/"/, '\\"') + '"'
154 '"' + str.gsub(/"/, '\\"') + '"'
155 else
155 else
156 "'" + str.gsub(/'/, "'\"'\"'") + "'"
156 "'" + str.gsub(/'/, "'\"'\"'") + "'"
157 end
157 end
158 end
158 end
159
159
160 private
160 private
161 def retrieve_root_url
161 def retrieve_root_url
162 info = self.info
162 info = self.info
163 info ? info.root_url : nil
163 info ? info.root_url : nil
164 end
164 end
165
165
166 def target(path)
166 def target(path)
167 path ||= ''
167 path ||= ''
168 base = path.match(/^\//) ? root_url : url
168 base = path.match(/^\//) ? root_url : url
169 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
169 shell_quote("#{base}/#{path}".gsub(/[?<>\*]/, ''))
170 end
170 end
171
171
172 def logger
172 def logger
173 self.class.logger
173 self.class.logger
174 end
174 end
175
175
176 def shellout(cmd, &block)
176 def shellout(cmd, &block)
177 self.class.shellout(cmd, &block)
177 self.class.shellout(cmd, &block)
178 end
178 end
179
179
180 def self.logger
180 def self.logger
181 RAILS_DEFAULT_LOGGER
181 RAILS_DEFAULT_LOGGER
182 end
182 end
183
183
184 def self.shellout(cmd, &block)
184 def self.shellout(cmd, &block)
185 logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug?
185 logger.debug "Shelling out: #{strip_credential(cmd)}" if logger && logger.debug?
186 if Rails.env == 'development'
186 if Rails.env == 'development'
187 # Capture stderr when running in dev environment
187 # Capture stderr when running in dev environment
188 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
188 cmd = "#{cmd} 2>>#{RAILS_ROOT}/log/scm.stderr.log"
189 end
189 end
190 begin
190 begin
191 IO.popen(cmd, "r+") do |io|
191 IO.popen(cmd, "r+") do |io|
192 io.close_write
192 io.close_write
193 block.call(io) if block_given?
193 block.call(io) if block_given?
194 end
194 end
195 rescue Errno::ENOENT => e
195 rescue Errno::ENOENT => e
196 msg = strip_credential(e.message)
196 msg = strip_credential(e.message)
197 # The command failed, log it and re-raise
197 # The command failed, log it and re-raise
198 logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}")
198 logger.error("SCM command failed, make sure that your SCM binary (eg. svn) is in PATH (#{ENV['PATH']}): #{strip_credential(cmd)}\n with: #{msg}")
199 raise CommandFailed.new(msg)
199 raise CommandFailed.new(msg)
200 end
200 end
201 end
201 end
202
202
203 # Hides username/password in a given command
203 # Hides username/password in a given command
204 def self.strip_credential(cmd)
204 def self.strip_credential(cmd)
205 q = (Redmine::Platform.mswin? ? '"' : "'")
205 q = (Redmine::Platform.mswin? ? '"' : "'")
206 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
206 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
207 end
207 end
208
208
209 def strip_credential(cmd)
209 def strip_credential(cmd)
210 self.class.strip_credential(cmd)
210 self.class.strip_credential(cmd)
211 end
211 end
212 end
212 end
213
213
214 class Entries < Array
214 class Entries < Array
215 def sort_by_name
215 def sort_by_name
216 sort {|x,y|
216 sort {|x,y|
217 if x.kind == y.kind
217 if x.kind == y.kind
218 x.name.to_s <=> y.name.to_s
218 x.name.to_s <=> y.name.to_s
219 else
219 else
220 x.kind <=> y.kind
220 x.kind <=> y.kind
221 end
221 end
222 }
222 }
223 end
223 end
224
224
225 def revisions
225 def revisions
226 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
226 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
227 end
227 end
228 end
228 end
229
229
230 class Info
230 class Info
231 attr_accessor :root_url, :lastrev
231 attr_accessor :root_url, :lastrev
232 def initialize(attributes={})
232 def initialize(attributes={})
233 self.root_url = attributes[:root_url] if attributes[:root_url]
233 self.root_url = attributes[:root_url] if attributes[:root_url]
234 self.lastrev = attributes[:lastrev]
234 self.lastrev = attributes[:lastrev]
235 end
235 end
236 end
236 end
237
237
238 class Entry
238 class Entry
239 attr_accessor :name, :path, :kind, :size, :lastrev
239 attr_accessor :name, :path, :kind, :size, :lastrev
240 def initialize(attributes={})
240 def initialize(attributes={})
241 self.name = attributes[:name] if attributes[:name]
241 self.name = attributes[:name] if attributes[:name]
242 self.path = attributes[:path] if attributes[:path]
242 self.path = attributes[:path] if attributes[:path]
243 self.kind = attributes[:kind] if attributes[:kind]
243 self.kind = attributes[:kind] if attributes[:kind]
244 self.size = attributes[:size].to_i if attributes[:size]
244 self.size = attributes[:size].to_i if attributes[:size]
245 self.lastrev = attributes[:lastrev]
245 self.lastrev = attributes[:lastrev]
246 end
246 end
247
247
248 def is_file?
248 def is_file?
249 'file' == self.kind
249 'file' == self.kind
250 end
250 end
251
251
252 def is_dir?
252 def is_dir?
253 'dir' == self.kind
253 'dir' == self.kind
254 end
254 end
255
255
256 def is_text?
256 def is_text?
257 Redmine::MimeType.is_type?('text', name)
257 Redmine::MimeType.is_type?('text', name)
258 end
258 end
259 end
259 end
260
260
261 class Revisions < Array
261 class Revisions < Array
262 def latest
262 def latest
263 sort {|x,y|
263 sort {|x,y|
264 unless x.time.nil? or y.time.nil?
264 unless x.time.nil? or y.time.nil?
265 x.time <=> y.time
265 x.time <=> y.time
266 else
266 else
267 0
267 0
268 end
268 end
269 }.last
269 }.last
270 end
270 end
271 end
271 end
272
272
273 class Revision
273 class Revision
274 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
274 attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
275
275
276 def initialize(attributes={})
276 def initialize(attributes={})
277 self.identifier = attributes[:identifier]
277 self.identifier = attributes[:identifier]
278 self.scmid = attributes[:scmid]
278 self.scmid = attributes[:scmid]
279 self.name = attributes[:name] || self.identifier
279 self.name = attributes[:name] || self.identifier
280 self.author = attributes[:author]
280 self.author = attributes[:author]
281 self.time = attributes[:time]
281 self.time = attributes[:time]
282 self.message = attributes[:message] || ""
282 self.message = attributes[:message] || ""
283 self.paths = attributes[:paths]
283 self.paths = attributes[:paths]
284 self.revision = attributes[:revision]
284 self.revision = attributes[:revision]
285 self.branch = attributes[:branch]
285 self.branch = attributes[:branch]
286 end
286 end
287
287
288 def save(repo)
288 def save(repo)
289 if repo.changesets.find_by_scmid(scmid.to_s).nil?
289 Changeset.transaction do
290 changeset = Changeset.create!(
290 changeset = Changeset.new(
291 :repository => repo,
291 :repository => repo,
292 :revision => identifier,
292 :revision => identifier,
293 :scmid => scmid,
293 :scmid => scmid,
294 :committer => author,
294 :committer => author,
295 :committed_on => time,
295 :committed_on => time,
296 :comments => message)
296 :comments => message)
297
297
298 paths.each do |file|
298 if changeset.save
299 Change.create!(
299 paths.each do |file|
300 :changeset => changeset,
300 Change.create(
301 :action => file[:action],
301 :changeset => changeset,
302 :path => file[:path])
302 :action => file[:action],
303 end
303 :path => file[:path])
304 end
305 end
304 end
306 end
305 end
307 end
306 end
308 end
307
309
308 class Annotate
310 class Annotate
309 attr_reader :lines, :revisions
311 attr_reader :lines, :revisions
310
312
311 def initialize
313 def initialize
312 @lines = []
314 @lines = []
313 @revisions = []
315 @revisions = []
314 end
316 end
315
317
316 def add_line(line, revision)
318 def add_line(line, revision)
317 @lines << line
319 @lines << line
318 @revisions << revision
320 @revisions << revision
319 end
321 end
320
322
321 def content
323 def content
322 content = lines.join("\n")
324 content = lines.join("\n")
323 end
325 end
324
326
325 def empty?
327 def empty?
326 lines.empty?
328 lines.empty?
327 end
329 end
328 end
330 end
329 end
331 end
330 end
332 end
331 end
333 end
General Comments 0
You need to be logged in to leave comments. Login now