##// END OF EJS Templates
scm: git: use stdin instead of command line in "git log" (#10470)...
Toshi MARUYAMA -
r9148:3e11f9abfe9d
parent child
Show More
@@ -1,382 +1,382
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 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
28 28 # raised if scm command exited with error, e.g. unknown revision.
29 29 class ScmCommandAborted < CommandFailed; end
30 30
31 31 class << self
32 32 def client_command
33 33 ""
34 34 end
35 35
36 36 def shell_quote_command
37 37 if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java'
38 38 client_command
39 39 else
40 40 shell_quote(client_command)
41 41 end
42 42 end
43 43
44 44 # Returns the version of the scm client
45 45 # Eg: [1, 5, 0] or [] if unknown
46 46 def client_version
47 47 []
48 48 end
49 49
50 50 # Returns the version string of the scm client
51 51 # Eg: '1.5.0' or 'Unknown version' if unknown
52 52 def client_version_string
53 53 v = client_version || 'Unknown version'
54 54 v.is_a?(Array) ? v.join('.') : v.to_s
55 55 end
56 56
57 57 # Returns true if the current client version is above
58 58 # or equals the given one
59 59 # If option is :unknown is set to true, it will return
60 60 # true if the client version is unknown
61 61 def client_version_above?(v, options={})
62 62 ((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown])
63 63 end
64 64
65 65 def client_available
66 66 true
67 67 end
68 68
69 69 def shell_quote(str)
70 70 if Redmine::Platform.mswin?
71 71 '"' + str.gsub(/"/, '\\"') + '"'
72 72 else
73 73 "'" + str.gsub(/'/, "'\"'\"'") + "'"
74 74 end
75 75 end
76 76 end
77 77
78 78 def initialize(url, root_url=nil, login=nil, password=nil,
79 79 path_encoding=nil)
80 80 @url = url
81 81 @login = login if login && !login.empty?
82 82 @password = (password || "") if @login
83 83 @root_url = root_url.blank? ? retrieve_root_url : root_url
84 84 end
85 85
86 86 def adapter_name
87 87 'Abstract'
88 88 end
89 89
90 90 def supports_cat?
91 91 true
92 92 end
93 93
94 94 def supports_annotate?
95 95 respond_to?('annotate')
96 96 end
97 97
98 98 def root_url
99 99 @root_url
100 100 end
101 101
102 102 def url
103 103 @url
104 104 end
105 105
106 106 def path_encoding
107 107 nil
108 108 end
109 109
110 110 # get info about the svn repository
111 111 def info
112 112 return nil
113 113 end
114 114
115 115 # Returns the entry identified by path and revision identifier
116 116 # or nil if entry doesn't exist in the repository
117 117 def entry(path=nil, identifier=nil)
118 118 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
119 119 search_path = parts[0..-2].join('/')
120 120 search_name = parts[-1]
121 121 if search_path.blank? && search_name.blank?
122 122 # Root entry
123 123 Entry.new(:path => '', :kind => 'dir')
124 124 else
125 125 # Search for the entry in the parent directory
126 126 es = entries(search_path, identifier)
127 127 es ? es.detect {|e| e.name == search_name} : nil
128 128 end
129 129 end
130 130
131 131 # Returns an Entries collection
132 132 # or nil if the given path doesn't exist in the repository
133 133 def entries(path=nil, identifier=nil, options={})
134 134 return nil
135 135 end
136 136
137 137 def branches
138 138 return nil
139 139 end
140 140
141 141 def tags
142 142 return nil
143 143 end
144 144
145 145 def default_branch
146 146 return nil
147 147 end
148 148
149 149 def properties(path, identifier=nil)
150 150 return nil
151 151 end
152 152
153 153 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
154 154 return nil
155 155 end
156 156
157 157 def diff(path, identifier_from, identifier_to=nil)
158 158 return nil
159 159 end
160 160
161 161 def cat(path, identifier=nil)
162 162 return nil
163 163 end
164 164
165 165 def with_leading_slash(path)
166 166 path ||= ''
167 167 (path[0,1]!="/") ? "/#{path}" : path
168 168 end
169 169
170 170 def with_trailling_slash(path)
171 171 path ||= ''
172 172 (path[-1,1] == "/") ? path : "#{path}/"
173 173 end
174 174
175 175 def without_leading_slash(path)
176 176 path ||= ''
177 177 path.gsub(%r{^/+}, '')
178 178 end
179 179
180 180 def without_trailling_slash(path)
181 181 path ||= ''
182 182 (path[-1,1] == "/") ? path[0..-2] : path
183 183 end
184 184
185 185 def shell_quote(str)
186 186 self.class.shell_quote(str)
187 187 end
188 188
189 189 private
190 190 def retrieve_root_url
191 191 info = self.info
192 192 info ? info.root_url : nil
193 193 end
194 194
195 195 def target(path, sq=true)
196 196 path ||= ''
197 197 base = path.match(/^\//) ? root_url : url
198 198 str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
199 199 if sq
200 200 str = shell_quote(str)
201 201 end
202 202 str
203 203 end
204 204
205 205 def logger
206 206 self.class.logger
207 207 end
208 208
209 def shellout(cmd, &block)
210 self.class.shellout(cmd, &block)
209 def shellout(cmd, options = {}, &block)
210 self.class.shellout(cmd, options, &block)
211 211 end
212 212
213 213 def self.logger
214 214 Rails.logger
215 215 end
216 216
217 def self.shellout(cmd, &block)
217 def self.shellout(cmd, options = {}, &block)
218 218 if logger && logger.debug?
219 219 logger.debug "Shelling out: #{strip_credential(cmd)}"
220 220 end
221 221 if Rails.env == 'development'
222 222 # Capture stderr when running in dev environment
223 223 cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
224 224 end
225 225 begin
226 226 mode = "r+"
227 227 IO.popen(cmd, mode) do |io|
228 228 io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
229 io.close_write
229 io.close_write unless options[:write_stdin]
230 230 block.call(io) if block_given?
231 231 end
232 232 ## If scm command does not exist,
233 233 ## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException
234 234 ## in production environment.
235 235 # rescue Errno::ENOENT => e
236 236 rescue Exception => e
237 237 msg = strip_credential(e.message)
238 238 # The command failed, log it and re-raise
239 239 logmsg = "SCM command failed, "
240 240 logmsg += "make sure that your SCM command (e.g. svn) is "
241 241 logmsg += "in PATH (#{ENV['PATH']})\n"
242 242 logmsg += "You can configure your scm commands in config/configuration.yml.\n"
243 243 logmsg += "#{strip_credential(cmd)}\n"
244 244 logmsg += "with: #{msg}"
245 245 logger.error(logmsg)
246 246 raise CommandFailed.new(msg)
247 247 end
248 248 end
249 249
250 250 # Hides username/password in a given command
251 251 def self.strip_credential(cmd)
252 252 q = (Redmine::Platform.mswin? ? '"' : "'")
253 253 cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
254 254 end
255 255
256 256 def strip_credential(cmd)
257 257 self.class.strip_credential(cmd)
258 258 end
259 259
260 260 def scm_iconv(to, from, str)
261 261 return nil if str.nil?
262 262 return str if to == from
263 263 begin
264 264 Iconv.conv(to, from, str)
265 265 rescue Iconv::Failure => err
266 266 logger.error("failed to convert from #{from} to #{to}. #{err}")
267 267 nil
268 268 end
269 269 end
270 270 end
271 271
272 272 class Entries < Array
273 273 def sort_by_name
274 274 sort {|x,y|
275 275 if x.kind == y.kind
276 276 x.name.to_s <=> y.name.to_s
277 277 else
278 278 x.kind <=> y.kind
279 279 end
280 280 }
281 281 end
282 282
283 283 def revisions
284 284 revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
285 285 end
286 286 end
287 287
288 288 class Info
289 289 attr_accessor :root_url, :lastrev
290 290 def initialize(attributes={})
291 291 self.root_url = attributes[:root_url] if attributes[:root_url]
292 292 self.lastrev = attributes[:lastrev]
293 293 end
294 294 end
295 295
296 296 class Entry
297 297 attr_accessor :name, :path, :kind, :size, :lastrev
298 298 def initialize(attributes={})
299 299 self.name = attributes[:name] if attributes[:name]
300 300 self.path = attributes[:path] if attributes[:path]
301 301 self.kind = attributes[:kind] if attributes[:kind]
302 302 self.size = attributes[:size].to_i if attributes[:size]
303 303 self.lastrev = attributes[:lastrev]
304 304 end
305 305
306 306 def is_file?
307 307 'file' == self.kind
308 308 end
309 309
310 310 def is_dir?
311 311 'dir' == self.kind
312 312 end
313 313
314 314 def is_text?
315 315 Redmine::MimeType.is_type?('text', name)
316 316 end
317 317 end
318 318
319 319 class Revisions < Array
320 320 def latest
321 321 sort {|x,y|
322 322 unless x.time.nil? or y.time.nil?
323 323 x.time <=> y.time
324 324 else
325 325 0
326 326 end
327 327 }.last
328 328 end
329 329 end
330 330
331 331 class Revision
332 332 attr_accessor :scmid, :name, :author, :time, :message,
333 333 :paths, :revision, :branch, :identifier,
334 334 :parents
335 335
336 336 def initialize(attributes={})
337 337 self.identifier = attributes[:identifier]
338 338 self.scmid = attributes[:scmid]
339 339 self.name = attributes[:name] || self.identifier
340 340 self.author = attributes[:author]
341 341 self.time = attributes[:time]
342 342 self.message = attributes[:message] || ""
343 343 self.paths = attributes[:paths]
344 344 self.revision = attributes[:revision]
345 345 self.branch = attributes[:branch]
346 346 self.parents = attributes[:parents]
347 347 end
348 348
349 349 # Returns the readable identifier.
350 350 def format_identifier
351 351 self.identifier.to_s
352 352 end
353 353 end
354 354
355 355 class Annotate
356 356 attr_reader :lines, :revisions
357 357
358 358 def initialize
359 359 @lines = []
360 360 @revisions = []
361 361 end
362 362
363 363 def add_line(line, revision)
364 364 @lines << line
365 365 @revisions << revision
366 366 end
367 367
368 368 def content
369 369 content = lines.join("\n")
370 370 end
371 371
372 372 def empty?
373 373 lines.empty?
374 374 end
375 375 end
376 376
377 377 class Branch < String
378 378 attr_accessor :revision, :scmid
379 379 end
380 380 end
381 381 end
382 382 end
@@ -1,407 +1,412
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 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class GitAdapter < AbstractAdapter
24 24
25 25 # Git executable name
26 26 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
27 27
28 28 class GitBranch < Branch
29 29 attr_accessor :is_default
30 30 end
31 31
32 32 class << self
33 33 def client_command
34 34 @@bin ||= GIT_BIN
35 35 end
36 36
37 37 def sq_bin
38 38 @@sq_bin ||= shell_quote_command
39 39 end
40 40
41 41 def client_version
42 42 @@client_version ||= (scm_command_version || [])
43 43 end
44 44
45 45 def client_available
46 46 !client_version.empty?
47 47 end
48 48
49 49 def scm_command_version
50 50 scm_version = scm_version_from_command_line.dup
51 51 if scm_version.respond_to?(:force_encoding)
52 52 scm_version.force_encoding('ASCII-8BIT')
53 53 end
54 54 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
55 55 m[2].scan(%r{\d+}).collect(&:to_i)
56 56 end
57 57 end
58 58
59 59 def scm_version_from_command_line
60 60 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
61 61 end
62 62 end
63 63
64 64 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
65 65 super
66 66 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
67 67 end
68 68
69 69 def path_encoding
70 70 @path_encoding
71 71 end
72 72
73 73 def info
74 74 begin
75 75 Info.new(:root_url => url, :lastrev => lastrev('',nil))
76 76 rescue
77 77 nil
78 78 end
79 79 end
80 80
81 81 def branches
82 82 return @branches if @branches
83 83 @branches = []
84 84 cmd_args = %w|branch --no-color --verbose --no-abbrev|
85 85 git_cmd(cmd_args) do |io|
86 86 io.each_line do |line|
87 87 branch_rev = line.match('\s*(\*?)\s*(.*?)\s*([0-9a-f]{40}).*$')
88 88 bran = GitBranch.new(branch_rev[2])
89 89 bran.revision = branch_rev[3]
90 90 bran.scmid = branch_rev[3]
91 91 bran.is_default = ( branch_rev[1] == '*' )
92 92 @branches << bran
93 93 end
94 94 end
95 95 @branches.sort!
96 96 rescue ScmCommandAborted
97 97 nil
98 98 end
99 99
100 100 def tags
101 101 return @tags if @tags
102 102 cmd_args = %w|tag|
103 103 git_cmd(cmd_args) do |io|
104 104 @tags = io.readlines.sort!.map{|t| t.strip}
105 105 end
106 106 rescue ScmCommandAborted
107 107 nil
108 108 end
109 109
110 110 def default_branch
111 111 bras = self.branches
112 112 return nil if bras.nil?
113 113 default_bras = bras.select{|x| x.is_default == true}
114 114 return default_bras.first.to_s if ! default_bras.empty?
115 115 master_bras = bras.select{|x| x.to_s == 'master'}
116 116 master_bras.empty? ? bras.first.to_s : 'master'
117 117 end
118 118
119 119 def entry(path=nil, identifier=nil)
120 120 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
121 121 search_path = parts[0..-2].join('/')
122 122 search_name = parts[-1]
123 123 if search_path.blank? && search_name.blank?
124 124 # Root entry
125 125 Entry.new(:path => '', :kind => 'dir')
126 126 else
127 127 # Search for the entry in the parent directory
128 128 es = entries(search_path, identifier,
129 129 options = {:report_last_commit => false})
130 130 es ? es.detect {|e| e.name == search_name} : nil
131 131 end
132 132 end
133 133
134 134 def entries(path=nil, identifier=nil, options={})
135 135 path ||= ''
136 136 p = scm_iconv(@path_encoding, 'UTF-8', path)
137 137 entries = Entries.new
138 138 cmd_args = %w|ls-tree -l|
139 139 cmd_args << "HEAD:#{p}" if identifier.nil?
140 140 cmd_args << "#{identifier}:#{p}" if identifier
141 141 git_cmd(cmd_args) do |io|
142 142 io.each_line do |line|
143 143 e = line.chomp.to_s
144 144 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
145 145 type = $1
146 146 sha = $2
147 147 size = $3
148 148 name = $4
149 149 if name.respond_to?(:force_encoding)
150 150 name.force_encoding(@path_encoding)
151 151 end
152 152 full_path = p.empty? ? name : "#{p}/#{name}"
153 153 n = scm_iconv('UTF-8', @path_encoding, name)
154 154 full_p = scm_iconv('UTF-8', @path_encoding, full_path)
155 155 entries << Entry.new({:name => n,
156 156 :path => full_p,
157 157 :kind => (type == "tree") ? 'dir' : 'file',
158 158 :size => (type == "tree") ? nil : size,
159 159 :lastrev => options[:report_last_commit] ?
160 160 lastrev(full_path, identifier) : Revision.new
161 161 }) unless entries.detect{|entry| entry.name == name}
162 162 end
163 163 end
164 164 end
165 165 entries.sort_by_name
166 166 rescue ScmCommandAborted
167 167 nil
168 168 end
169 169
170 170 def lastrev(path, rev)
171 171 return nil if path.nil?
172 172 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
173 173 cmd_args << rev if rev
174 174 cmd_args << "--" << path unless path.empty?
175 175 lines = []
176 176 git_cmd(cmd_args) { |io| lines = io.readlines }
177 177 begin
178 178 id = lines[0].split[1]
179 179 author = lines[1].match('Author:\s+(.*)$')[1]
180 180 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
181 181
182 182 Revision.new({
183 183 :identifier => id,
184 184 :scmid => id,
185 185 :author => author,
186 186 :time => time,
187 187 :message => nil,
188 188 :paths => nil
189 189 })
190 190 rescue NoMethodError => e
191 191 logger.error("The revision '#{path}' has a wrong format")
192 192 return nil
193 193 end
194 194 rescue ScmCommandAborted
195 195 nil
196 196 end
197 197
198 198 def revisions(path, identifier_from, identifier_to, options={})
199 199 revs = Revisions.new
200 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents|
200 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents --stdin|
201 201 cmd_args << "--reverse" if options[:reverse]
202 202 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
203 from_to = ""
203 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
204 revisions = []
204 205 if identifier_from || identifier_to
205 from_to << "#{identifier_from}.." if identifier_from
206 from_to << "#{identifier_to}" if identifier_to
207 cmd_args << from_to if !from_to.empty?
206 revisions << ""
207 revisions[0] << "#{identifier_from}.." if identifier_from
208 revisions[0] << "#{identifier_to}" if identifier_to
208 209 else
209 cmd_args += options[:includes] unless options[:includes].blank?
210 unless options[:includes].blank?
211 revisions += options[:includes]
212 end
210 213 unless options[:excludes].blank?
211 cmd_args << "--not"
212 cmd_args += options[:excludes]
214 revisions += options[:excludes].map{|r| "^#{r}"}
213 215 end
214 216 end
215 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
216 217
217 git_cmd(cmd_args) do |io|
218 git_cmd(cmd_args, {:write_stdin => true}) do |io|
219 io.binmode
220 io.puts(revisions.join("\n"))
221 io.close_write
218 222 files=[]
219 223 changeset = {}
220 224 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
221 225
222 226 io.each_line do |line|
223 227 if line =~ /^commit ([0-9a-f]{40})(( [0-9a-f]{40})*)$/
224 228 key = "commit"
225 229 value = $1
226 230 parents_str = $2
227 231 if (parsing_descr == 1 || parsing_descr == 2)
228 232 parsing_descr = 0
229 233 revision = Revision.new({
230 234 :identifier => changeset[:commit],
231 235 :scmid => changeset[:commit],
232 236 :author => changeset[:author],
233 237 :time => Time.parse(changeset[:date]),
234 238 :message => changeset[:description],
235 239 :paths => files,
236 240 :parents => changeset[:parents]
237 241 })
238 242 if block_given?
239 243 yield revision
240 244 else
241 245 revs << revision
242 246 end
243 247 changeset = {}
244 248 files = []
245 249 end
246 250 changeset[:commit] = $1
247 251 unless parents_str.nil? or parents_str == ""
248 252 changeset[:parents] = parents_str.strip.split(' ')
249 253 end
250 254 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
251 255 key = $1
252 256 value = $2
253 257 if key == "Author"
254 258 changeset[:author] = value
255 259 elsif key == "CommitDate"
256 260 changeset[:date] = value
257 261 end
258 262 elsif (parsing_descr == 0) && line.chomp.to_s == ""
259 263 parsing_descr = 1
260 264 changeset[:description] = ""
261 265 elsif (parsing_descr == 1 || parsing_descr == 2) \
262 266 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
263 267 parsing_descr = 2
264 268 fileaction = $1
265 269 filepath = $2
266 270 p = scm_iconv('UTF-8', @path_encoding, filepath)
267 271 files << {:action => fileaction, :path => p}
268 272 elsif (parsing_descr == 1 || parsing_descr == 2) \
269 273 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
270 274 parsing_descr = 2
271 275 fileaction = $1
272 276 filepath = $3
273 277 p = scm_iconv('UTF-8', @path_encoding, filepath)
274 278 files << {:action => fileaction, :path => p}
275 279 elsif (parsing_descr == 1) && line.chomp.to_s == ""
276 280 parsing_descr = 2
277 281 elsif (parsing_descr == 1)
278 282 changeset[:description] << line[4..-1]
279 283 end
280 284 end
281 285
282 286 if changeset[:commit]
283 287 revision = Revision.new({
284 288 :identifier => changeset[:commit],
285 289 :scmid => changeset[:commit],
286 290 :author => changeset[:author],
287 291 :time => Time.parse(changeset[:date]),
288 292 :message => changeset[:description],
289 293 :paths => files,
290 294 :parents => changeset[:parents]
291 295 })
292 296 if block_given?
293 297 yield revision
294 298 else
295 299 revs << revision
296 300 end
297 301 end
298 302 end
299 303 revs
300 304 rescue ScmCommandAborted => e
301 305 err_msg = "git log error: #{e.message}"
302 306 logger.error(err_msg)
303 307 if block_given?
304 308 raise CommandFailed, err_msg
305 309 else
306 310 revs
307 311 end
308 312 end
309 313
310 314 def diff(path, identifier_from, identifier_to=nil)
311 315 path ||= ''
312 316 cmd_args = []
313 317 if identifier_to
314 318 cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
315 319 else
316 320 cmd_args << "show" << "--no-color" << identifier_from
317 321 end
318 322 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
319 323 diff = []
320 324 git_cmd(cmd_args) do |io|
321 325 io.each_line do |line|
322 326 diff << line
323 327 end
324 328 end
325 329 diff
326 330 rescue ScmCommandAborted
327 331 nil
328 332 end
329 333
330 334 def annotate(path, identifier=nil)
331 335 identifier = 'HEAD' if identifier.blank?
332 336 cmd_args = %w|blame|
333 337 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
334 338 blame = Annotate.new
335 339 content = nil
336 340 git_cmd(cmd_args) { |io| io.binmode; content = io.read }
337 341 # git annotates binary files
338 342 return nil if content.is_binary_data?
339 343 identifier = ''
340 344 # git shows commit author on the first occurrence only
341 345 authors_by_commit = {}
342 346 content.split("\n").each do |line|
343 347 if line =~ /^([0-9a-f]{39,40})\s.*/
344 348 identifier = $1
345 349 elsif line =~ /^author (.+)/
346 350 authors_by_commit[identifier] = $1.strip
347 351 elsif line =~ /^\t(.*)/
348 352 blame.add_line($1, Revision.new(
349 353 :identifier => identifier,
350 354 :revision => identifier,
351 355 :scmid => identifier,
352 356 :author => authors_by_commit[identifier]
353 357 ))
354 358 identifier = ''
355 359 author = ''
356 360 end
357 361 end
358 362 blame
359 363 rescue ScmCommandAborted
360 364 nil
361 365 end
362 366
363 367 def cat(path, identifier=nil)
364 368 if identifier.nil?
365 369 identifier = 'HEAD'
366 370 end
367 371 cmd_args = %w|show --no-color|
368 372 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
369 373 cat = nil
370 374 git_cmd(cmd_args) do |io|
371 375 io.binmode
372 376 cat = io.read
373 377 end
374 378 cat
375 379 rescue ScmCommandAborted
376 380 nil
377 381 end
378 382
379 383 class Revision < Redmine::Scm::Adapters::Revision
380 384 # Returns the readable identifier
381 385 def format_identifier
382 386 identifier[0,8]
383 387 end
384 388 end
385 389
386 def git_cmd(args, &block)
390 def git_cmd(args, options = {}, &block)
387 391 repo_path = root_url || url
388 392 full_args = ['--git-dir', repo_path]
389 393 if self.class.client_version_above?([1, 7, 2])
390 394 full_args << '-c' << 'core.quotepath=false'
391 395 full_args << '-c' << 'log.decorate=no'
392 396 end
393 397 full_args += args
394 398 ret = shellout(
395 399 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
400 options,
396 401 &block
397 402 )
398 403 if $? && $?.exitstatus != 0
399 404 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
400 405 end
401 406 ret
402 407 end
403 408 private :git_cmd
404 409 end
405 410 end
406 411 end
407 412 end
General Comments 0
You need to be logged in to leave comments. Login now