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