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