##// END OF EJS Templates
scm: git: back out r5673 (#7146)....
Toshi MARUYAMA -
r5657:38d5925d70d7
parent child
Show More
@@ -1,366 +1,374
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class GitAdapter < AbstractAdapter
23 class GitAdapter < AbstractAdapter
24
24
25 # Git executable name
25 # Git executable name
26 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
26 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
27
27
28 # raised if scm command exited with error, e.g. unknown revision.
28 # raised if scm command exited with error, e.g. unknown revision.
29 class ScmCommandAborted < CommandFailed; end
29 class ScmCommandAborted < CommandFailed; end
30
30
31 class << self
31 class << self
32 def client_command
32 def client_command
33 @@bin ||= GIT_BIN
33 @@bin ||= GIT_BIN
34 end
34 end
35
35
36 def sq_bin
36 def sq_bin
37 @@sq_bin ||= shell_quote(GIT_BIN)
37 @@sq_bin ||= shell_quote(GIT_BIN)
38 end
38 end
39
39
40 def client_version
40 def client_version
41 @@client_version ||= (scm_command_version || [])
41 @@client_version ||= (scm_command_version || [])
42 end
42 end
43
43
44 def client_available
44 def client_available
45 !client_version.empty?
45 !client_version.empty?
46 end
46 end
47
47
48 def scm_command_version
48 def scm_command_version
49 scm_version = scm_version_from_command_line.dup
49 scm_version = scm_version_from_command_line.dup
50 if scm_version.respond_to?(:force_encoding)
50 if scm_version.respond_to?(:force_encoding)
51 scm_version.force_encoding('ASCII-8BIT')
51 scm_version.force_encoding('ASCII-8BIT')
52 end
52 end
53 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
53 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
54 m[2].scan(%r{\d+}).collect(&:to_i)
54 m[2].scan(%r{\d+}).collect(&:to_i)
55 end
55 end
56 end
56 end
57
57
58 def scm_version_from_command_line
58 def scm_version_from_command_line
59 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
59 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
60 end
60 end
61 end
61 end
62
62
63 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
63 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
64 super
64 super
65 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
65 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
66 end
66 end
67
67
68 def info
68 def info
69 begin
69 begin
70 Info.new(:root_url => url, :lastrev => lastrev('',nil))
70 Info.new(:root_url => url, :lastrev => lastrev('',nil))
71 rescue
71 rescue
72 nil
72 nil
73 end
73 end
74 end
74 end
75
75
76 def branches
76 def branches
77 return @branches if @branches
77 return @branches if @branches
78 @branches = []
78 @branches = []
79 cmd_args = %w|branch --no-color|
79 cmd_args = %w|branch --no-color|
80 scm_cmd(*cmd_args) do |io|
80 scm_cmd(*cmd_args) do |io|
81 io.each_line do |line|
81 io.each_line do |line|
82 @branches << line.match('\s*\*?\s*(.*)$')[1]
82 @branches << line.match('\s*\*?\s*(.*)$')[1]
83 end
83 end
84 end
84 end
85 @branches.sort!
85 @branches.sort!
86 rescue ScmCommandAborted
86 rescue ScmCommandAborted
87 nil
87 nil
88 end
88 end
89
89
90 def tags
90 def tags
91 return @tags if @tags
91 return @tags if @tags
92 cmd_args = %w|tag|
92 cmd_args = %w|tag|
93 scm_cmd(*cmd_args) do |io|
93 scm_cmd(*cmd_args) do |io|
94 @tags = io.readlines.sort!.map{|t| t.strip}
94 @tags = io.readlines.sort!.map{|t| t.strip}
95 end
95 end
96 rescue ScmCommandAborted
96 rescue ScmCommandAborted
97 nil
97 nil
98 end
98 end
99
99
100 def default_branch
100 def default_branch
101 bras = self.branches
101 bras = self.branches
102 return nil if bras.nil?
102 return nil if bras.nil?
103 bras.include?('master') ? 'master' : bras.first
103 bras.include?('master') ? 'master' : bras.first
104 end
104 end
105
105
106 def entry(path=nil, identifier=nil)
106 def entry(path=nil, identifier=nil)
107 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
107 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
108 search_path = parts[0..-2].join('/')
108 search_path = parts[0..-2].join('/')
109 search_name = parts[-1]
109 search_name = parts[-1]
110 if search_path.blank? && search_name.blank?
110 if search_path.blank? && search_name.blank?
111 # Root entry
111 # Root entry
112 Entry.new(:path => '', :kind => 'dir')
112 Entry.new(:path => '', :kind => 'dir')
113 else
113 else
114 # Search for the entry in the parent directory
114 # Search for the entry in the parent directory
115 es = entries(search_path, identifier,
115 es = entries(search_path, identifier,
116 options = {:report_last_commit => false})
116 options = {:report_last_commit => false})
117 es ? es.detect {|e| e.name == search_name} : nil
117 es ? es.detect {|e| e.name == search_name} : nil
118 end
118 end
119 end
119 end
120
120
121 def entries(path=nil, identifier=nil, options={})
121 def entries(path=nil, identifier=nil, options={})
122 path ||= ''
122 path ||= ''
123 p = scm_iconv(@path_encoding, 'UTF-8', path)
123 p = scm_iconv(@path_encoding, 'UTF-8', path)
124 entries = Entries.new
124 entries = Entries.new
125 cmd_args = %w|ls-tree -l|
125 cmd_args = %w|ls-tree -l|
126 cmd_args << "HEAD:#{p}" if identifier.nil?
126 cmd_args << "HEAD:#{p}" if identifier.nil?
127 cmd_args << "#{identifier}:#{p}" if identifier
127 cmd_args << "#{identifier}:#{p}" if identifier
128 scm_cmd(*cmd_args) do |io|
128 scm_cmd(*cmd_args) do |io|
129 io.each_line do |line|
129 io.each_line do |line|
130 e = line.chomp.to_s
130 e = line.chomp.to_s
131 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
131 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
132 type = $1
132 type = $1
133 sha = $2
133 sha = $2
134 size = $3
134 size = $3
135 name = $4
135 name = $4
136 if name.respond_to?(:force_encoding)
136 if name.respond_to?(:force_encoding)
137 name.force_encoding(@path_encoding)
137 name.force_encoding(@path_encoding)
138 end
138 end
139 full_path = p.empty? ? name : "#{p}/#{name}"
139 full_path = p.empty? ? name : "#{p}/#{name}"
140 n = scm_iconv('UTF-8', @path_encoding, name)
140 n = scm_iconv('UTF-8', @path_encoding, name)
141 full_p = scm_iconv('UTF-8', @path_encoding, full_path)
141 full_p = scm_iconv('UTF-8', @path_encoding, full_path)
142 entries << Entry.new({:name => n,
142 entries << Entry.new({:name => n,
143 :path => full_p,
143 :path => full_p,
144 :kind => (type == "tree") ? 'dir' : 'file',
144 :kind => (type == "tree") ? 'dir' : 'file',
145 :size => (type == "tree") ? nil : size,
145 :size => (type == "tree") ? nil : size,
146 :lastrev => options[:report_last_commit] ?
146 :lastrev => options[:report_last_commit] ?
147 lastrev(full_path, identifier) : Revision.new
147 lastrev(full_path, identifier) : Revision.new
148 }) unless entries.detect{|entry| entry.name == name}
148 }) unless entries.detect{|entry| entry.name == name}
149 end
149 end
150 end
150 end
151 end
151 end
152 entries.sort_by_name
152 entries.sort_by_name
153 rescue ScmCommandAborted
153 rescue ScmCommandAborted
154 nil
154 nil
155 end
155 end
156
156
157 def lastrev(path, rev)
157 def lastrev(path, rev)
158 return nil if path.nil?
158 return nil if path.nil?
159 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
159 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
160 cmd_args << rev if rev
160 cmd_args << rev if rev
161 cmd_args << "--" << path unless path.empty?
161 cmd_args << "--" << path unless path.empty?
162 lines = []
162 lines = []
163 scm_cmd(*cmd_args) { |io| lines = io.readlines }
163 scm_cmd(*cmd_args) { |io| lines = io.readlines }
164 begin
164 begin
165 id = lines[0].split[1]
165 id = lines[0].split[1]
166 author = lines[1].match('Author:\s+(.*)$')[1]
166 author = lines[1].match('Author:\s+(.*)$')[1]
167 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
167 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
168
168
169 Revision.new({
169 Revision.new({
170 :identifier => id,
170 :identifier => id,
171 :scmid => id,
171 :scmid => id,
172 :author => author,
172 :author => author,
173 :time => time,
173 :time => time,
174 :message => nil,
174 :message => nil,
175 :paths => nil
175 :paths => nil
176 })
176 })
177 rescue NoMethodError => e
177 rescue NoMethodError => e
178 logger.error("The revision '#{path}' has a wrong format")
178 logger.error("The revision '#{path}' has a wrong format")
179 return nil
179 return nil
180 end
180 end
181 rescue ScmCommandAborted
181 rescue ScmCommandAborted
182 nil
182 nil
183 end
183 end
184
184
185 def revisions(path, identifier_from, identifier_to, options={})
185 def revisions(path, identifier_from, identifier_to, options={})
186 revs = Revisions.new
186 revs = Revisions.new
187 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller|
187 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller|
188 cmd_args << "--reverse" if options[:reverse]
188 cmd_args << "--reverse" if options[:reverse]
189 cmd_args << "--all" if options[:all]
189 cmd_args << "--all" if options[:all]
190 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
190 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
191 from_to = ""
191 from_to = ""
192 from_to << "#{identifier_from}.." if identifier_from
192 from_to << "#{identifier_from}.." if identifier_from
193 from_to << "#{identifier_to}" if identifier_to
193 from_to << "#{identifier_to}" if identifier_to
194 cmd_args << from_to if !from_to.empty?
194 cmd_args << from_to if !from_to.empty?
195 cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since]
195 cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since]
196 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
196 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
197
197
198 scm_cmd *cmd_args do |io|
198 scm_cmd *cmd_args do |io|
199 files=[]
199 files=[]
200 changeset = {}
200 changeset = {}
201 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
201 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
202
202
203 io.each_line do |line|
203 io.each_line do |line|
204 if line =~ /^commit ([0-9a-f]{40})$/
204 if line =~ /^commit ([0-9a-f]{40})$/
205 key = "commit"
205 key = "commit"
206 value = $1
206 value = $1
207 if (parsing_descr == 1 || parsing_descr == 2)
207 if (parsing_descr == 1 || parsing_descr == 2)
208 parsing_descr = 0
208 parsing_descr = 0
209 revision = Revision.new({
209 revision = Revision.new({
210 :identifier => changeset[:commit],
210 :identifier => changeset[:commit],
211 :scmid => changeset[:commit],
211 :scmid => changeset[:commit],
212 :author => changeset[:author],
212 :author => changeset[:author],
213 :time => Time.parse(changeset[:date]),
213 :time => Time.parse(changeset[:date]),
214 :message => changeset[:description],
214 :message => changeset[:description],
215 :paths => files
215 :paths => files
216 })
216 })
217 revs << revision
217 if block_given?
218 yield revision
219 else
220 revs << revision
221 end
218 changeset = {}
222 changeset = {}
219 files = []
223 files = []
220 end
224 end
221 changeset[:commit] = $1
225 changeset[:commit] = $1
222 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
226 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
223 key = $1
227 key = $1
224 value = $2
228 value = $2
225 if key == "Author"
229 if key == "Author"
226 changeset[:author] = value
230 changeset[:author] = value
227 elsif key == "CommitDate"
231 elsif key == "CommitDate"
228 changeset[:date] = value
232 changeset[:date] = value
229 end
233 end
230 elsif (parsing_descr == 0) && line.chomp.to_s == ""
234 elsif (parsing_descr == 0) && line.chomp.to_s == ""
231 parsing_descr = 1
235 parsing_descr = 1
232 changeset[:description] = ""
236 changeset[:description] = ""
233 elsif (parsing_descr == 1 || parsing_descr == 2) \
237 elsif (parsing_descr == 1 || parsing_descr == 2) \
234 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
238 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
235 parsing_descr = 2
239 parsing_descr = 2
236 fileaction = $1
240 fileaction = $1
237 filepath = $2
241 filepath = $2
238 p = scm_iconv('UTF-8', @path_encoding, filepath)
242 p = scm_iconv('UTF-8', @path_encoding, filepath)
239 files << {:action => fileaction, :path => p}
243 files << {:action => fileaction, :path => p}
240 elsif (parsing_descr == 1 || parsing_descr == 2) \
244 elsif (parsing_descr == 1 || parsing_descr == 2) \
241 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
245 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
242 parsing_descr = 2
246 parsing_descr = 2
243 fileaction = $1
247 fileaction = $1
244 filepath = $3
248 filepath = $3
245 p = scm_iconv('UTF-8', @path_encoding, filepath)
249 p = scm_iconv('UTF-8', @path_encoding, filepath)
246 files << {:action => fileaction, :path => p}
250 files << {:action => fileaction, :path => p}
247 elsif (parsing_descr == 1) && line.chomp.to_s == ""
251 elsif (parsing_descr == 1) && line.chomp.to_s == ""
248 parsing_descr = 2
252 parsing_descr = 2
249 elsif (parsing_descr == 1)
253 elsif (parsing_descr == 1)
250 changeset[:description] << line[4..-1]
254 changeset[:description] << line[4..-1]
251 end
255 end
252 end
256 end
253
257
254 if changeset[:commit]
258 if changeset[:commit]
255 revision = Revision.new({
259 revision = Revision.new({
256 :identifier => changeset[:commit],
260 :identifier => changeset[:commit],
257 :scmid => changeset[:commit],
261 :scmid => changeset[:commit],
258 :author => changeset[:author],
262 :author => changeset[:author],
259 :time => Time.parse(changeset[:date]),
263 :time => Time.parse(changeset[:date]),
260 :message => changeset[:description],
264 :message => changeset[:description],
261 :paths => files
265 :paths => files
262 })
266 })
263 revs << revision
267 if block_given?
268 yield revision
269 else
270 revs << revision
271 end
264 end
272 end
265 end
273 end
266 revs
274 revs
267 rescue ScmCommandAborted => e
275 rescue ScmCommandAborted => e
268 logger.error("git log #{from_to.to_s} error: #{e.message}")
276 logger.error("git log #{from_to.to_s} error: #{e.message}")
269 revs
277 revs
270 end
278 end
271
279
272 def diff(path, identifier_from, identifier_to=nil)
280 def diff(path, identifier_from, identifier_to=nil)
273 path ||= ''
281 path ||= ''
274 cmd_args = []
282 cmd_args = []
275 if identifier_to
283 if identifier_to
276 cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
284 cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
277 else
285 else
278 cmd_args << "show" << "--no-color" << identifier_from
286 cmd_args << "show" << "--no-color" << identifier_from
279 end
287 end
280 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
288 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
281 diff = []
289 diff = []
282 scm_cmd *cmd_args do |io|
290 scm_cmd *cmd_args do |io|
283 io.each_line do |line|
291 io.each_line do |line|
284 diff << line
292 diff << line
285 end
293 end
286 end
294 end
287 diff
295 diff
288 rescue ScmCommandAborted
296 rescue ScmCommandAborted
289 nil
297 nil
290 end
298 end
291
299
292 def annotate(path, identifier=nil)
300 def annotate(path, identifier=nil)
293 identifier = 'HEAD' if identifier.blank?
301 identifier = 'HEAD' if identifier.blank?
294 cmd_args = %w|blame|
302 cmd_args = %w|blame|
295 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
303 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
296 blame = Annotate.new
304 blame = Annotate.new
297 content = nil
305 content = nil
298 scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
306 scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
299 # git annotates binary files
307 # git annotates binary files
300 return nil if content.is_binary_data?
308 return nil if content.is_binary_data?
301 identifier = ''
309 identifier = ''
302 # git shows commit author on the first occurrence only
310 # git shows commit author on the first occurrence only
303 authors_by_commit = {}
311 authors_by_commit = {}
304 content.split("\n").each do |line|
312 content.split("\n").each do |line|
305 if line =~ /^([0-9a-f]{39,40})\s.*/
313 if line =~ /^([0-9a-f]{39,40})\s.*/
306 identifier = $1
314 identifier = $1
307 elsif line =~ /^author (.+)/
315 elsif line =~ /^author (.+)/
308 authors_by_commit[identifier] = $1.strip
316 authors_by_commit[identifier] = $1.strip
309 elsif line =~ /^\t(.*)/
317 elsif line =~ /^\t(.*)/
310 blame.add_line($1, Revision.new(
318 blame.add_line($1, Revision.new(
311 :identifier => identifier,
319 :identifier => identifier,
312 :revision => identifier,
320 :revision => identifier,
313 :scmid => identifier,
321 :scmid => identifier,
314 :author => authors_by_commit[identifier]
322 :author => authors_by_commit[identifier]
315 ))
323 ))
316 identifier = ''
324 identifier = ''
317 author = ''
325 author = ''
318 end
326 end
319 end
327 end
320 blame
328 blame
321 rescue ScmCommandAborted
329 rescue ScmCommandAborted
322 nil
330 nil
323 end
331 end
324
332
325 def cat(path, identifier=nil)
333 def cat(path, identifier=nil)
326 if identifier.nil?
334 if identifier.nil?
327 identifier = 'HEAD'
335 identifier = 'HEAD'
328 end
336 end
329 cmd_args = %w|show --no-color|
337 cmd_args = %w|show --no-color|
330 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
338 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
331 cat = nil
339 cat = nil
332 scm_cmd(*cmd_args) do |io|
340 scm_cmd(*cmd_args) do |io|
333 io.binmode
341 io.binmode
334 cat = io.read
342 cat = io.read
335 end
343 end
336 cat
344 cat
337 rescue ScmCommandAborted
345 rescue ScmCommandAborted
338 nil
346 nil
339 end
347 end
340
348
341 class Revision < Redmine::Scm::Adapters::Revision
349 class Revision < Redmine::Scm::Adapters::Revision
342 # Returns the readable identifier
350 # Returns the readable identifier
343 def format_identifier
351 def format_identifier
344 identifier[0,8]
352 identifier[0,8]
345 end
353 end
346 end
354 end
347
355
348 def scm_cmd(*args, &block)
356 def scm_cmd(*args, &block)
349 repo_path = root_url || url
357 repo_path = root_url || url
350 full_args = [GIT_BIN, '--git-dir', repo_path]
358 full_args = [GIT_BIN, '--git-dir', repo_path]
351 if self.class.client_version_above?([1, 7, 2])
359 if self.class.client_version_above?([1, 7, 2])
352 full_args << '-c' << 'core.quotepath=false'
360 full_args << '-c' << 'core.quotepath=false'
353 full_args << '-c' << 'log.decorate=no'
361 full_args << '-c' << 'log.decorate=no'
354 end
362 end
355 full_args += args
363 full_args += args
356 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
364 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
357 if $? && $?.exitstatus != 0
365 if $? && $?.exitstatus != 0
358 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
366 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
359 end
367 end
360 ret
368 ret
361 end
369 end
362 private :scm_cmd
370 private :scm_cmd
363 end
371 end
364 end
372 end
365 end
373 end
366 end
374 end
General Comments 0
You need to be logged in to leave comments. Login now