##// END OF EJS Templates
remove trailing white-spaces from lib/redmine/scm/adapters/git_adapter.rb...
Toshi MARUYAMA -
r13546:4dbdff1479eb
parent child
Show More
@@ -1,408 +1,408
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redmine/scm/adapters/abstract_adapter'
19 19
20 20 module Redmine
21 21 module Scm
22 22 module Adapters
23 23 class GitAdapter < AbstractAdapter
24 24
25 25 # Git executable name
26 26 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
27 27
28 class GitBranch < Branch
28 class GitBranch < Branch
29 29 attr_accessor :is_default
30 30 end
31 31
32 32 class << self
33 33 def client_command
34 34 @@bin ||= GIT_BIN
35 35 end
36 36
37 37 def sq_bin
38 38 @@sq_bin ||= shell_quote_command
39 39 end
40 40
41 41 def client_version
42 42 @@client_version ||= (scm_command_version || [])
43 43 end
44 44
45 45 def client_available
46 46 !client_version.empty?
47 47 end
48 48
49 49 def scm_command_version
50 50 scm_version = scm_version_from_command_line.dup.force_encoding('ASCII-8BIT')
51 51 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
52 52 m[2].scan(%r{\d+}).collect(&:to_i)
53 53 end
54 54 end
55 55
56 56 def scm_version_from_command_line
57 57 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
58 58 end
59 59 end
60 60
61 61 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
62 62 super
63 63 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
64 64 end
65 65
66 66 def path_encoding
67 67 @path_encoding
68 68 end
69 69
70 70 def info
71 71 begin
72 72 Info.new(:root_url => url, :lastrev => lastrev('',nil))
73 73 rescue
74 74 nil
75 75 end
76 76 end
77 77
78 78 def branches
79 79 return @branches if !@branches.nil?
80 80 @branches = []
81 81 cmd_args = %w|branch --no-color --verbose --no-abbrev|
82 82 git_cmd(cmd_args) do |io|
83 83 io.each_line do |line|
84 84 branch_rev = line.match('\s*(\*?)\s*(.*?)\s*([0-9a-f]{40}).*$')
85 85 bran = GitBranch.new(branch_rev[2])
86 86 bran.revision = branch_rev[3]
87 87 bran.scmid = branch_rev[3]
88 88 bran.is_default = ( branch_rev[1] == '*' )
89 89 @branches << bran
90 90 end
91 91 end
92 92 @branches.sort!
93 93 rescue ScmCommandAborted
94 94 nil
95 95 end
96 96
97 97 def tags
98 98 return @tags if !@tags.nil?
99 99 @tags = []
100 100 cmd_args = %w|tag|
101 101 git_cmd(cmd_args) do |io|
102 102 @tags = io.readlines.sort!.map{|t| t.strip}
103 103 end
104 104 @tags
105 105 rescue ScmCommandAborted
106 106 nil
107 107 end
108 108
109 109 def default_branch
110 110 bras = self.branches
111 111 return nil if bras.nil?
112 112 default_bras = bras.select{|x| x.is_default == true}
113 113 return default_bras.first.to_s if ! default_bras.empty?
114 114 master_bras = bras.select{|x| x.to_s == 'master'}
115 master_bras.empty? ? bras.first.to_s : 'master'
115 master_bras.empty? ? bras.first.to_s : 'master'
116 116 end
117 117
118 118 def entry(path=nil, identifier=nil)
119 119 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
120 120 search_path = parts[0..-2].join('/')
121 121 search_name = parts[-1]
122 122 if search_path.blank? && search_name.blank?
123 123 # Root entry
124 124 Entry.new(:path => '', :kind => 'dir')
125 125 else
126 126 # Search for the entry in the parent directory
127 127 es = entries(search_path, identifier,
128 128 options = {:report_last_commit => false})
129 129 es ? es.detect {|e| e.name == search_name} : nil
130 130 end
131 131 end
132 132
133 133 def entries(path=nil, identifier=nil, options={})
134 134 path ||= ''
135 135 p = scm_iconv(@path_encoding, 'UTF-8', path)
136 136 entries = Entries.new
137 137 cmd_args = %w|ls-tree -l|
138 138 cmd_args << "HEAD:#{p}" if identifier.nil?
139 139 cmd_args << "#{identifier}:#{p}" if identifier
140 140 git_cmd(cmd_args) do |io|
141 141 io.each_line do |line|
142 142 e = line.chomp.to_s
143 143 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
144 144 type = $1
145 145 sha = $2
146 146 size = $3
147 147 name = $4.force_encoding(@path_encoding)
148 148 full_path = p.empty? ? name : "#{p}/#{name}"
149 149 n = scm_iconv('UTF-8', @path_encoding, name)
150 150 full_p = scm_iconv('UTF-8', @path_encoding, full_path)
151 151 entries << Entry.new({:name => n,
152 152 :path => full_p,
153 153 :kind => (type == "tree") ? 'dir' : 'file',
154 154 :size => (type == "tree") ? nil : size,
155 155 :lastrev => options[:report_last_commit] ?
156 156 lastrev(full_path, identifier) : Revision.new
157 157 }) unless entries.detect{|entry| entry.name == name}
158 158 end
159 159 end
160 160 end
161 161 entries.sort_by_name
162 162 rescue ScmCommandAborted
163 163 nil
164 164 end
165 165
166 166 def lastrev(path, rev)
167 167 return nil if path.nil?
168 168 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
169 169 cmd_args << rev if rev
170 170 cmd_args << "--" << path unless path.empty?
171 171 lines = []
172 172 git_cmd(cmd_args) { |io| lines = io.readlines }
173 173 begin
174 174 id = lines[0].split[1]
175 175 author = lines[1].match('Author:\s+(.*)$')[1]
176 176 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
177 177
178 178 Revision.new({
179 179 :identifier => id,
180 180 :scmid => id,
181 181 :author => author,
182 182 :time => time,
183 183 :message => nil,
184 184 :paths => nil
185 185 })
186 186 rescue NoMethodError => e
187 187 logger.error("The revision '#{path}' has a wrong format")
188 188 return nil
189 189 end
190 190 rescue ScmCommandAborted
191 191 nil
192 192 end
193 193
194 194 def revisions(path, identifier_from, identifier_to, options={})
195 195 revs = Revisions.new
196 196 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller --parents --stdin|
197 197 cmd_args << "--reverse" if options[:reverse]
198 198 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
199 199 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
200 200 revisions = []
201 201 if identifier_from || identifier_to
202 202 revisions << ""
203 203 revisions[0] << "#{identifier_from}.." if identifier_from
204 204 revisions[0] << "#{identifier_to}" if identifier_to
205 205 else
206 206 unless options[:includes].blank?
207 207 revisions += options[:includes]
208 208 end
209 209 unless options[:excludes].blank?
210 210 revisions += options[:excludes].map{|r| "^#{r}"}
211 211 end
212 212 end
213 213
214 214 git_cmd(cmd_args, {:write_stdin => true}) do |io|
215 215 io.binmode
216 216 io.puts(revisions.join("\n"))
217 217 io.close_write
218 218 files=[]
219 219 changeset = {}
220 220 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
221 221
222 222 io.each_line do |line|
223 223 if line =~ /^commit ([0-9a-f]{40})(( [0-9a-f]{40})*)$/
224 224 key = "commit"
225 225 value = $1
226 226 parents_str = $2
227 227 if (parsing_descr == 1 || parsing_descr == 2)
228 228 parsing_descr = 0
229 229 revision = Revision.new({
230 230 :identifier => changeset[:commit],
231 231 :scmid => changeset[:commit],
232 232 :author => changeset[:author],
233 233 :time => Time.parse(changeset[:date]),
234 234 :message => changeset[:description],
235 235 :paths => files,
236 236 :parents => changeset[:parents]
237 237 })
238 238 if block_given?
239 239 yield revision
240 240 else
241 241 revs << revision
242 242 end
243 243 changeset = {}
244 244 files = []
245 245 end
246 246 changeset[:commit] = $1
247 247 unless parents_str.nil? or parents_str == ""
248 248 changeset[:parents] = parents_str.strip.split(' ')
249 249 end
250 250 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
251 251 key = $1
252 252 value = $2
253 253 if key == "Author"
254 254 changeset[:author] = value
255 255 elsif key == "CommitDate"
256 256 changeset[:date] = value
257 257 end
258 258 elsif (parsing_descr == 0) && line.chomp.to_s == ""
259 259 parsing_descr = 1
260 260 changeset[:description] = ""
261 261 elsif (parsing_descr == 1 || parsing_descr == 2) \
262 262 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
263 263 parsing_descr = 2
264 264 fileaction = $1
265 265 filepath = $2
266 266 p = scm_iconv('UTF-8', @path_encoding, filepath)
267 267 files << {:action => fileaction, :path => p}
268 268 elsif (parsing_descr == 1 || parsing_descr == 2) \
269 269 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
270 270 parsing_descr = 2
271 271 fileaction = $1
272 272 filepath = $3
273 273 p = scm_iconv('UTF-8', @path_encoding, filepath)
274 274 files << {:action => fileaction, :path => p}
275 275 elsif (parsing_descr == 1) && line.chomp.to_s == ""
276 276 parsing_descr = 2
277 277 elsif (parsing_descr == 1)
278 278 changeset[:description] << line[4..-1]
279 279 end
280 280 end
281 281
282 282 if changeset[:commit]
283 283 revision = Revision.new({
284 284 :identifier => changeset[:commit],
285 285 :scmid => changeset[:commit],
286 286 :author => changeset[:author],
287 287 :time => Time.parse(changeset[:date]),
288 288 :message => changeset[:description],
289 289 :paths => files,
290 290 :parents => changeset[:parents]
291 291 })
292 292 if block_given?
293 293 yield revision
294 294 else
295 295 revs << revision
296 296 end
297 297 end
298 298 end
299 299 revs
300 300 rescue ScmCommandAborted => e
301 301 err_msg = "git log error: #{e.message}"
302 302 logger.error(err_msg)
303 303 if block_given?
304 304 raise CommandFailed, err_msg
305 305 else
306 306 revs
307 307 end
308 308 end
309 309
310 310 def diff(path, identifier_from, identifier_to=nil)
311 311 path ||= ''
312 312 cmd_args = []
313 313 if identifier_to
314 314 cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
315 315 else
316 316 cmd_args << "show" << "--no-color" << identifier_from
317 317 end
318 318 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
319 319 diff = []
320 320 git_cmd(cmd_args) do |io|
321 321 io.each_line do |line|
322 322 diff << line
323 323 end
324 324 end
325 325 diff
326 326 rescue ScmCommandAborted
327 327 nil
328 328 end
329 329
330 330 def annotate(path, identifier=nil)
331 331 identifier = 'HEAD' if identifier.blank?
332 332 cmd_args = %w|blame --encoding=UTF-8|
333 333 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
334 334 blame = Annotate.new
335 335 content = nil
336 336 git_cmd(cmd_args) { |io| io.binmode; content = io.read }
337 337 # git annotates binary files
338 338 return nil if content.is_binary_data?
339 339 identifier = ''
340 340 # git shows commit author on the first occurrence only
341 341 authors_by_commit = {}
342 342 content.split("\n").each do |line|
343 343 if line =~ /^([0-9a-f]{39,40})\s.*/
344 344 identifier = $1
345 345 elsif line =~ /^author (.+)/
346 346 authors_by_commit[identifier] = $1.strip
347 347 elsif line =~ /^\t(.*)/
348 348 blame.add_line($1, Revision.new(
349 349 :identifier => identifier,
350 350 :revision => identifier,
351 351 :scmid => identifier,
352 352 :author => authors_by_commit[identifier]
353 353 ))
354 354 identifier = ''
355 355 author = ''
356 356 end
357 357 end
358 358 blame
359 359 rescue ScmCommandAborted
360 360 nil
361 361 end
362 362
363 363 def cat(path, identifier=nil)
364 364 if identifier.nil?
365 365 identifier = 'HEAD'
366 366 end
367 367 cmd_args = %w|show --no-color|
368 368 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
369 369 cat = nil
370 370 git_cmd(cmd_args) do |io|
371 371 io.binmode
372 372 cat = io.read
373 373 end
374 374 cat
375 375 rescue ScmCommandAborted
376 376 nil
377 377 end
378 378
379 379 class Revision < Redmine::Scm::Adapters::Revision
380 380 # Returns the readable identifier
381 381 def format_identifier
382 382 identifier[0,8]
383 383 end
384 384 end
385 385
386 386 def git_cmd(args, options = {}, &block)
387 387 repo_path = root_url || url
388 388 full_args = ['--git-dir', repo_path]
389 389 if self.class.client_version_above?([1, 7, 2])
390 390 full_args << '-c' << 'core.quotepath=false'
391 391 full_args << '-c' << 'log.decorate=no'
392 392 end
393 393 full_args += args
394 394 ret = shellout(
395 395 self.class.sq_bin + ' ' + full_args.map { |e| shell_quote e.to_s }.join(' '),
396 396 options,
397 397 &block
398 398 )
399 399 if $? && $?.exitstatus != 0
400 400 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
401 401 end
402 402 ret
403 403 end
404 404 private :git_cmd
405 405 end
406 406 end
407 407 end
408 408 end
General Comments 0
You need to be logged in to leave comments. Login now