Mercurial > hg > soundsoftware-site
comparison lib/redmine/scm/adapters/git_adapter.rb @ 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 |