comparison lib/redmine/scm/adapters/.svn/text-base/git_adapter.rb.svn-base @ 245:051f544170fe

* Update to SVN trunk revision 4993
author Chris Cannam
date Thu, 03 Mar 2011 11:42:28 +0000
parents 0579821a129a
children cbce1fd3b1b7
comparison
equal deleted inserted replaced
244:8972b600f4fb 245:051f544170fe
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
25 SCM_GIT_REPORT_LAST_COMMIT = true
26
24 # Git executable name 27 # Git executable name
25 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git" 28 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
29
30 # raised if scm command exited with error, e.g. unknown revision.
31 class ScmCommandAborted < CommandFailed; end
32
33 class << self
34 def client_command
35 @@bin ||= GIT_BIN
36 end
37
38 def sq_bin
39 @@sq_bin ||= shell_quote(GIT_BIN)
40 end
41
42 def client_version
43 @@client_version ||= (scm_command_version || [])
44 end
45
46 def client_available
47 !client_version.empty?
48 end
49
50 def scm_command_version
51 scm_version = scm_version_from_command_line.dup
52 if scm_version.respond_to?(:force_encoding)
53 scm_version.force_encoding('ASCII-8BIT')
54 end
55 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
56 m[2].scan(%r{\d+}).collect(&:to_i)
57 end
58 end
59
60 def scm_version_from_command_line
61 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
62 end
63 end
64
65 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
66 super
67 @flag_report_last_commit = SCM_GIT_REPORT_LAST_COMMIT
68 end
26 69
27 def info 70 def info
28 begin 71 begin
29 Info.new(:root_url => url, :lastrev => lastrev('',nil)) 72 Info.new(:root_url => url, :lastrev => lastrev('',nil))
30 rescue 73 rescue
33 end 76 end
34 77
35 def branches 78 def branches
36 return @branches if @branches 79 return @branches if @branches
37 @branches = [] 80 @branches = []
38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch --no-color" 81 cmd = "#{self.class.sq_bin} --git-dir #{target('')} branch --no-color"
39 shellout(cmd) do |io| 82 shellout(cmd) do |io|
40 io.each_line do |line| 83 io.each_line do |line|
41 @branches << line.match('\s*\*?\s*(.*)$')[1] 84 @branches << line.match('\s*\*?\s*(.*)$')[1]
42 end 85 end
43 end 86 end
44 @branches.sort! 87 @branches.sort!
45 end 88 end
46 89
47 def tags 90 def tags
48 return @tags if @tags 91 return @tags if @tags
49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag" 92 cmd = "#{self.class.sq_bin} --git-dir #{target('')} tag"
50 shellout(cmd) do |io| 93 shellout(cmd) do |io|
51 @tags = io.readlines.sort!.map{|t| t.strip} 94 @tags = io.readlines.sort!.map{|t| t.strip}
52 end 95 end
53 end 96 end
54 97
55 def default_branch 98 def default_branch
56 branches.include?('master') ? 'master' : branches.first 99 branches.include?('master') ? 'master' : branches.first
57 end 100 end
58 101
59 def entries(path=nil, identifier=nil) 102 def entries(path=nil, identifier=nil)
60 path ||= '' 103 path ||= ''
61 entries = Entries.new 104 entries = Entries.new
62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l " 105 cmd = "#{self.class.sq_bin} --git-dir #{target('')} ls-tree -l "
63 cmd << shell_quote("HEAD:" + path) if identifier.nil? 106 cmd << shell_quote("HEAD:" + path) if identifier.nil?
64 cmd << shell_quote(identifier + ":" + path) if identifier 107 cmd << shell_quote(identifier + ":" + path) if identifier
65 shellout(cmd) do |io| 108 shellout(cmd) do |io|
66 io.each_line do |line| 109 io.each_line do |line|
67 e = line.chomp.to_s 110 e = line.chomp.to_s
73 full_path = path.empty? ? name : "#{path}/#{name}" 116 full_path = path.empty? ? name : "#{path}/#{name}"
74 entries << Entry.new({:name => name, 117 entries << Entry.new({:name => name,
75 :path => full_path, 118 :path => full_path,
76 :kind => (type == "tree") ? 'dir' : 'file', 119 :kind => (type == "tree") ? 'dir' : 'file',
77 :size => (type == "tree") ? nil : size, 120 :size => (type == "tree") ? nil : size,
78 :lastrev => lastrev(full_path,identifier) 121 :lastrev => @flag_report_last_commit ? lastrev(full_path,identifier) : Revision.new
79 }) unless entries.detect{|entry| entry.name == name} 122 }) unless entries.detect{|entry| entry.name == name}
80 end 123 end
81 end 124 end
82 end 125 end
83 return nil if $? && $?.exitstatus != 0 126 return nil if $? && $?.exitstatus != 0
84 entries.sort_by_name 127 entries.sort_by_name
85 end 128 end
86 129
87 def lastrev(path,rev) 130 def lastrev(path, rev)
88 return nil if path.nil? 131 return nil if path.nil?
89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --date=iso --pretty=fuller --no-merges -n 1 " 132 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
90 cmd << " #{shell_quote rev} " if rev 133 cmd_args << rev if rev
91 cmd << "-- #{shell_quote path} " unless path.empty? 134 cmd_args << "--" << path unless path.empty?
92 lines = [] 135 lines = []
93 shellout(cmd) { |io| lines = io.readlines } 136 scm_cmd(*cmd_args) { |io| lines = io.readlines }
94 return nil if $? && $?.exitstatus != 0
95 begin 137 begin
96 id = lines[0].split[1] 138 id = lines[0].split[1]
97 author = lines[1].match('Author:\s+(.*)$')[1] 139 author = lines[1].match('Author:\s+(.*)$')[1]
98 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1]).localtime 140 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
99 141
100 Revision.new({ 142 Revision.new({
101 :identifier => id, 143 :identifier => id,
102 :scmid => id, 144 :scmid => id,
103 :author => author, 145 :author => author,
104 :time => time, 146 :time => time,
105 :message => nil, 147 :message => nil,
106 :paths => nil 148 :paths => nil
107 }) 149 })
108 rescue NoMethodError => e 150 rescue NoMethodError => e
109 logger.error("The revision '#{path}' has a wrong format") 151 logger.error("The revision '#{path}' has a wrong format")
110 return nil 152 return nil
111 end 153 end
154 rescue ScmCommandAborted
155 nil
112 end 156 end
113 157
114 def revisions(path, identifier_from, identifier_to, options={}) 158 def revisions(path, identifier_from, identifier_to, options={})
115 revisions = Revisions.new 159 revisions = Revisions.new
116 160 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller|
117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --no-color --raw --date=iso --pretty=fuller " 161 cmd_args << "--reverse" if options[:reverse]
118 cmd << " --reverse " if options[:reverse] 162 cmd_args << "--all" if options[:all]
119 cmd << " --all " if options[:all] 163 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
120 cmd << " -n #{options[:limit].to_i} " if options[:limit] 164 from_to = ""
121 cmd << "#{shell_quote(identifier_from + '..')}" if identifier_from 165 from_to << "#{identifier_from}.." if identifier_from
122 cmd << "#{shell_quote identifier_to}" if identifier_to 166 from_to << "#{identifier_to}" if identifier_to
123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since] 167 cmd_args << from_to if !from_to.empty?
124 cmd << " -- #{shell_quote path}" if path && !path.empty? 168 cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since]
125 169 cmd_args << "--" << path if path && !path.empty?
126 shellout(cmd) do |io| 170
171 scm_cmd *cmd_args do |io|
127 files=[] 172 files=[]
128 changeset = {} 173 changeset = {}
129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files 174 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
130 revno = 1 175 revno = 1
131 176
198 else 243 else
199 revisions << revision 244 revisions << revision
200 end 245 end
201 end 246 end
202 end 247 end
203 248 revisions
204 return nil if $? && $?.exitstatus != 0 249 rescue ScmCommandAborted
205 revisions 250 revisions
206 end 251 end
207 252
208 def diff(path, identifier_from, identifier_to=nil) 253 def diff(path, identifier_from, identifier_to=nil)
209 path ||= '' 254 path ||= ''
210 255
211 if identifier_to 256 if identifier_to
212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}" 257 cmd = "#{self.class.sq_bin} --git-dir #{target('')} diff --no-color #{shell_quote identifier_to} #{shell_quote identifier_from}"
213 else 258 else
214 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}" 259 cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote identifier_from}"
215 end 260 end
216 261
217 cmd << " -- #{shell_quote path}" unless path.empty? 262 cmd << " -- #{shell_quote path}" unless path.empty?
218 diff = [] 263 diff = []
219 shellout(cmd) do |io| 264 shellout(cmd) do |io|
225 diff 270 diff
226 end 271 end
227 272
228 def annotate(path, identifier=nil) 273 def annotate(path, identifier=nil)
229 identifier = 'HEAD' if identifier.blank? 274 identifier = 'HEAD' if identifier.blank?
230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}" 275 cmd = "#{self.class.sq_bin} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
231 blame = Annotate.new 276 blame = Annotate.new
232 content = nil 277 content = nil
233 shellout(cmd) { |io| io.binmode; content = io.read } 278 shellout(cmd) { |io| io.binmode; content = io.read }
234 return nil if $? && $?.exitstatus != 0 279 return nil if $? && $?.exitstatus != 0
235 # git annotates binary files 280 # git annotates binary files
248 author = '' 293 author = ''
249 end 294 end
250 end 295 end
251 blame 296 blame
252 end 297 end
253 298
254 def cat(path, identifier=nil) 299 def cat(path, identifier=nil)
255 if identifier.nil? 300 if identifier.nil?
256 identifier = 'HEAD' 301 identifier = 'HEAD'
257 end 302 end
258 cmd = "#{GIT_BIN} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}" 303 cmd = "#{self.class.sq_bin} --git-dir #{target('')} show --no-color #{shell_quote(identifier + ':' + path)}"
259 cat = nil 304 cat = nil
260 shellout(cmd) do |io| 305 shellout(cmd) do |io|
261 io.binmode 306 io.binmode
262 cat = io.read 307 cat = io.read
263 end 308 end
269 # Returns the readable identifier 314 # Returns the readable identifier
270 def format_identifier 315 def format_identifier
271 identifier[0,8] 316 identifier[0,8]
272 end 317 end
273 end 318 end
319
320 def scm_cmd(*args, &block)
321 repo_path = root_url || url
322 full_args = [GIT_BIN, '--git-dir', repo_path]
323 full_args += args
324 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
325 if $? && $?.exitstatus != 0
326 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
327 end
328 ret
329 end
330 private :scm_cmd
274 end 331 end
275 end 332 end
276 end 333 end
277 end 334 end