comparison lib/redmine/scm/adapters/git_adapter.rb @ 1115:433d4f72a19b redmine-2.2

Update to Redmine SVN revision 11137 on 2.2-stable branch
author Chris Cannam
date Mon, 07 Jan 2013 12:01:42 +0000
parents cbb26bc654de
children 51d7f3e06556 622f24f53b42 261b3d9a4903
comparison
equal deleted inserted replaced
929:5f33065ddc4b 1115:433d4f72a19b
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang 2 # Copyright (C) 2006-2012 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.
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 class GitBranch < Branch
29 attr_accessor :is_default
30 end
31
28 class << self 32 class << self
29 def client_command 33 def client_command
30 @@bin ||= GIT_BIN 34 @@bin ||= GIT_BIN
31 end 35 end
32 36
76 80
77 def branches 81 def branches
78 return @branches if @branches 82 return @branches if @branches
79 @branches = [] 83 @branches = []
80 cmd_args = %w|branch --no-color --verbose --no-abbrev| 84 cmd_args = %w|branch --no-color --verbose --no-abbrev|
81 scm_cmd(*cmd_args) do |io| 85 git_cmd(cmd_args) do |io|
82 io.each_line do |line| 86 io.each_line do |line|
83 branch_rev = line.match('\s*\*?\s*(.*?)\s*([0-9a-f]{40}).*$') 87 branch_rev = line.match('\s*(\*?)\s*(.*?)\s*([0-9a-f]{40}).*$')
84 bran = Branch.new(branch_rev[1]) 88 bran = GitBranch.new(branch_rev[2])
85 bran.revision = branch_rev[2] 89 bran.revision = branch_rev[3]
86 bran.scmid = branch_rev[2] 90 bran.scmid = branch_rev[3]
91 bran.is_default = ( branch_rev[1] == '*' )
87 @branches << bran 92 @branches << bran
88 end 93 end
89 end 94 end
90 @branches.sort! 95 @branches.sort!
91 rescue ScmCommandAborted 96 rescue ScmCommandAborted
93 end 98 end
94 99
95 def tags 100 def tags
96 return @tags if @tags 101 return @tags if @tags
97 cmd_args = %w|tag| 102 cmd_args = %w|tag|
98 scm_cmd(*cmd_args) do |io| 103 git_cmd(cmd_args) do |io|
99 @tags = io.readlines.sort!.map{|t| t.strip} 104 @tags = io.readlines.sort!.map{|t| t.strip}
100 end 105 end
101 rescue ScmCommandAborted 106 rescue ScmCommandAborted
102 nil 107 nil
103 end 108 end
104 109
105 def default_branch 110 def default_branch
106 bras = self.branches 111 bras = self.branches
107 return nil if bras.nil? 112 return nil if bras.nil?
108 bras.include?('master') ? 'master' : bras.first 113 default_bras = bras.select{|x| x.is_default == true}
114 return default_bras.first.to_s if ! default_bras.empty?
115 master_bras = bras.select{|x| x.to_s == 'master'}
116 master_bras.empty? ? bras.first.to_s : 'master'
109 end 117 end
110 118
111 def entry(path=nil, identifier=nil) 119 def entry(path=nil, identifier=nil)
112 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?} 120 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
113 search_path = parts[0..-2].join('/') 121 search_path = parts[0..-2].join('/')
128 p = scm_iconv(@path_encoding, 'UTF-8', path) 136 p = scm_iconv(@path_encoding, 'UTF-8', path)
129 entries = Entries.new 137 entries = Entries.new
130 cmd_args = %w|ls-tree -l| 138 cmd_args = %w|ls-tree -l|
131 cmd_args << "HEAD:#{p}" if identifier.nil? 139 cmd_args << "HEAD:#{p}" if identifier.nil?
132 cmd_args << "#{identifier}:#{p}" if identifier 140 cmd_args << "#{identifier}:#{p}" if identifier
133 scm_cmd(*cmd_args) do |io| 141 git_cmd(cmd_args) do |io|
134 io.each_line do |line| 142 io.each_line do |line|
135 e = line.chomp.to_s 143 e = line.chomp.to_s
136 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/ 144 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
137 type = $1 145 type = $1
138 sha = $2 146 sha = $2
163 return nil if path.nil? 171 return nil if path.nil?
164 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1| 172 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
165 cmd_args << rev if rev 173 cmd_args << rev if rev
166 cmd_args << "--" << path unless path.empty? 174 cmd_args << "--" << path unless path.empty?
167 lines = [] 175 lines = []
168 scm_cmd(*cmd_args) { |io| lines = io.readlines } 176 git_cmd(cmd_args) { |io| lines = io.readlines }
169 begin 177 begin
170 id = lines[0].split[1] 178 id = lines[0].split[1]
171 author = lines[1].match('Author:\s+(.*)$')[1] 179 author = lines[1].match('Author:\s+(.*)$')[1]
172 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]) 180 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
173 181
187 nil 195 nil
188 end 196 end
189 197
190 def revisions(path, identifier_from, identifier_to, options={}) 198 def revisions(path, identifier_from, identifier_to, options={})
191 revs = Revisions.new 199 revs = Revisions.new
192 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|
193 cmd_args << "--reverse" if options[:reverse] 201 cmd_args << "--reverse" if options[:reverse]
194 cmd_args << "--all" if options[:all]
195 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit] 202 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
196 from_to = ""
197 from_to << "#{identifier_from}.." if identifier_from
198 from_to << "#{identifier_to}" if identifier_to
199 cmd_args << from_to if !from_to.empty?
200 cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since]
201 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty? 203 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
202 204 revisions = []
203 scm_cmd *cmd_args do |io| 205 if identifier_from || identifier_to
206 revisions << ""
207 revisions[0] << "#{identifier_from}.." if identifier_from
208 revisions[0] << "#{identifier_to}" if identifier_to
209 else
210 unless options[:includes].blank?
211 revisions += options[:includes]
212 end
213 unless options[:excludes].blank?
214 revisions += options[:excludes].map{|r| "^#{r}"}
215 end
216 end
217
218 git_cmd(cmd_args, {:write_stdin => true}) do |io|
219 io.binmode
220 io.puts(revisions.join("\n"))
221 io.close_write
204 files=[] 222 files=[]
205 changeset = {} 223 changeset = {}
206 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files 224 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
207 225
208 io.each_line do |line| 226 io.each_line do |line|
282 end 300 end
283 end 301 end
284 end 302 end
285 revs 303 revs
286 rescue ScmCommandAborted => e 304 rescue ScmCommandAborted => e
287 logger.error("git log #{from_to.to_s} error: #{e.message}") 305 err_msg = "git log error: #{e.message}"
288 revs 306 logger.error(err_msg)
307 if block_given?
308 raise CommandFailed, err_msg
309 else
310 revs
311 end
289 end 312 end
290 313
291 def diff(path, identifier_from, identifier_to=nil) 314 def diff(path, identifier_from, identifier_to=nil)
292 path ||= '' 315 path ||= ''
293 cmd_args = [] 316 cmd_args = []
296 else 319 else
297 cmd_args << "show" << "--no-color" << identifier_from 320 cmd_args << "show" << "--no-color" << identifier_from
298 end 321 end
299 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty? 322 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
300 diff = [] 323 diff = []
301 scm_cmd *cmd_args do |io| 324 git_cmd(cmd_args) do |io|
302 io.each_line do |line| 325 io.each_line do |line|
303 diff << line 326 diff << line
304 end 327 end
305 end 328 end
306 diff 329 diff
312 identifier = 'HEAD' if identifier.blank? 335 identifier = 'HEAD' if identifier.blank?
313 cmd_args = %w|blame| 336 cmd_args = %w|blame|
314 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path) 337 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
315 blame = Annotate.new 338 blame = Annotate.new
316 content = nil 339 content = nil
317 scm_cmd(*cmd_args) { |io| io.binmode; content = io.read } 340 git_cmd(cmd_args) { |io| io.binmode; content = io.read }
318 # git annotates binary files 341 # git annotates binary files
319 return nil if content.is_binary_data? 342 return nil if content.is_binary_data?
320 identifier = '' 343 identifier = ''
321 # git shows commit author on the first occurrence only 344 # git shows commit author on the first occurrence only
322 authors_by_commit = {} 345 authors_by_commit = {}
346 identifier = 'HEAD' 369 identifier = 'HEAD'
347 end 370 end
348 cmd_args = %w|show --no-color| 371 cmd_args = %w|show --no-color|
349 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}" 372 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
350 cat = nil 373 cat = nil
351 scm_cmd(*cmd_args) do |io| 374 git_cmd(cmd_args) do |io|
352 io.binmode 375 io.binmode
353 cat = io.read 376 cat = io.read
354 end 377 end
355 cat 378 cat
356 rescue ScmCommandAborted 379 rescue ScmCommandAborted
362 def format_identifier 385 def format_identifier
363 identifier[0,8] 386 identifier[0,8]
364 end 387 end
365 end 388 end
366 389
367 def scm_cmd(*args, &block) 390 def git_cmd(args, options = {}, &block)
368 repo_path = root_url || url 391 repo_path = root_url || url
369 full_args = ['--git-dir', repo_path] 392 full_args = ['--git-dir', repo_path]
370 if self.class.client_version_above?([1, 7, 2]) 393 if self.class.client_version_above?([1, 7, 2])
371 full_args << '-c' << 'core.quotepath=false' 394 full_args << '-c' << 'core.quotepath=false'
372 full_args << '-c' << 'log.decorate=no' 395 full_args << '-c' << 'log.decorate=no'
373 end 396 end
374 full_args += args 397 full_args += args
375 ret = shellout( 398 ret = shellout(
376 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '), 399 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
400 options,
377 &block 401 &block
378 ) 402 )
379 if $? && $?.exitstatus != 0 403 if $? && $?.exitstatus != 0
380 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" 404 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
381 end 405 end
382 ret 406 ret
383 end 407 end
384 private :scm_cmd 408 private :git_cmd
385 end 409 end
386 end 410 end
387 end 411 end
388 end 412 end