##// END OF EJS Templates
Fixed wrong commit range in git log command. #5628...
Jean-Baptiste Barth -
r3811:780bdccc4246
parent child
Show More
@@ -1,270 +1,270
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 = "git"
25 GIT_BIN = "git"
26
26
27 def info
27 def info
28 begin
28 begin
29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
30 rescue
30 rescue
31 nil
31 nil
32 end
32 end
33 end
33 end
34
34
35 def branches
35 def branches
36 return @branches if @branches
36 return @branches if @branches
37 @branches = []
37 @branches = []
38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch"
38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch"
39 shellout(cmd) do |io|
39 shellout(cmd) do |io|
40 io.each_line do |line|
40 io.each_line do |line|
41 @branches << line.match('\s*\*?\s*(.*)$')[1]
41 @branches << line.match('\s*\*?\s*(.*)$')[1]
42 end
42 end
43 end
43 end
44 @branches.sort!
44 @branches.sort!
45 end
45 end
46
46
47 def tags
47 def tags
48 return @tags if @tags
48 return @tags if @tags
49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
50 shellout(cmd) do |io|
50 shellout(cmd) do |io|
51 @tags = io.readlines.sort!.map{|t| t.strip}
51 @tags = io.readlines.sort!.map{|t| t.strip}
52 end
52 end
53 end
53 end
54
54
55 def default_branch
55 def default_branch
56 branches.include?('master') ? 'master' : branches.first
56 branches.include?('master') ? 'master' : branches.first
57 end
57 end
58
58
59 def entries(path=nil, identifier=nil)
59 def entries(path=nil, identifier=nil)
60 path ||= ''
60 path ||= ''
61 entries = Entries.new
61 entries = Entries.new
62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
64 cmd << shell_quote(identifier + ":" + path) if identifier
64 cmd << shell_quote(identifier + ":" + path) if identifier
65 shellout(cmd) do |io|
65 shellout(cmd) do |io|
66 io.each_line do |line|
66 io.each_line do |line|
67 e = line.chomp.to_s
67 e = line.chomp.to_s
68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
69 type = $1
69 type = $1
70 sha = $2
70 sha = $2
71 size = $3
71 size = $3
72 name = $4
72 name = $4
73 full_path = path.empty? ? name : "#{path}/#{name}"
73 full_path = path.empty? ? name : "#{path}/#{name}"
74 entries << Entry.new({:name => name,
74 entries << Entry.new({:name => name,
75 :path => full_path,
75 :path => full_path,
76 :kind => (type == "tree") ? 'dir' : 'file',
76 :kind => (type == "tree") ? 'dir' : 'file',
77 :size => (type == "tree") ? nil : size,
77 :size => (type == "tree") ? nil : size,
78 :lastrev => lastrev(full_path,identifier)
78 :lastrev => lastrev(full_path,identifier)
79 }) unless entries.detect{|entry| entry.name == name}
79 }) unless entries.detect{|entry| entry.name == name}
80 end
80 end
81 end
81 end
82 end
82 end
83 return nil if $? && $?.exitstatus != 0
83 return nil if $? && $?.exitstatus != 0
84 entries.sort_by_name
84 entries.sort_by_name
85 end
85 end
86
86
87 def lastrev(path,rev)
87 def lastrev(path,rev)
88 return nil if path.nil?
88 return nil if path.nil?
89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --no-merges -n 1 "
89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --no-merges -n 1 "
90 cmd << " #{shell_quote rev} " if rev
90 cmd << " #{shell_quote rev} " if rev
91 cmd << "-- #{path} " unless path.empty?
91 cmd << "-- #{path} " unless path.empty?
92 shellout(cmd) do |io|
92 shellout(cmd) do |io|
93 begin
93 begin
94 id = io.gets.split[1]
94 id = io.gets.split[1]
95 author = io.gets.match('Author:\s+(.*)$')[1]
95 author = io.gets.match('Author:\s+(.*)$')[1]
96 2.times { io.gets }
96 2.times { io.gets }
97 time = io.gets.match('CommitDate:\s+(.*)$')[1]
97 time = io.gets.match('CommitDate:\s+(.*)$')[1]
98
98
99 Revision.new({
99 Revision.new({
100 :identifier => id,
100 :identifier => id,
101 :scmid => id,
101 :scmid => id,
102 :author => author,
102 :author => author,
103 :time => time,
103 :time => time,
104 :message => nil,
104 :message => nil,
105 :paths => nil
105 :paths => nil
106 })
106 })
107 rescue NoMethodError => e
107 rescue NoMethodError => e
108 logger.error("The revision '#{path}' has a wrong format")
108 logger.error("The revision '#{path}' has a wrong format")
109 return nil
109 return nil
110 end
110 end
111 end
111 end
112 end
112 end
113
113
114 def revisions(path, identifier_from, identifier_to, options={})
114 def revisions(path, identifier_from, identifier_to, options={})
115 revisions = Revisions.new
115 revisions = Revisions.new
116
116
117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller "
118 cmd << " --reverse" if options[:reverse]
118 cmd << " --reverse " if options[:reverse]
119 cmd << " --all" if options[:all]
119 cmd << " --all " if options[:all]
120 cmd << " -n #{options[:limit]} " if options[:limit]
120 cmd << " -n #{options[:limit]} " if options[:limit]
121 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
121 cmd << "#{shell_quote(identifier_from + '..')}" if identifier_from
122 cmd << " #{shell_quote identifier_to} " if identifier_to
122 cmd << "#{shell_quote identifier_to}" if identifier_to
123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
124 cmd << " -- #{path}" if path && !path.empty?
124 cmd << " -- #{path}" if path && !path.empty?
125
125
126 shellout(cmd) do |io|
126 shellout(cmd) do |io|
127 files=[]
127 files=[]
128 changeset = {}
128 changeset = {}
129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
130 revno = 1
130 revno = 1
131
131
132 io.each_line do |line|
132 io.each_line do |line|
133 if line =~ /^commit ([0-9a-f]{40})$/
133 if line =~ /^commit ([0-9a-f]{40})$/
134 key = "commit"
134 key = "commit"
135 value = $1
135 value = $1
136 if (parsing_descr == 1 || parsing_descr == 2)
136 if (parsing_descr == 1 || parsing_descr == 2)
137 parsing_descr = 0
137 parsing_descr = 0
138 revision = Revision.new({
138 revision = Revision.new({
139 :identifier => changeset[:commit],
139 :identifier => changeset[:commit],
140 :scmid => changeset[:commit],
140 :scmid => changeset[:commit],
141 :author => changeset[:author],
141 :author => changeset[:author],
142 :time => Time.parse(changeset[:date]),
142 :time => Time.parse(changeset[:date]),
143 :message => changeset[:description],
143 :message => changeset[:description],
144 :paths => files
144 :paths => files
145 })
145 })
146 if block_given?
146 if block_given?
147 yield revision
147 yield revision
148 else
148 else
149 revisions << revision
149 revisions << revision
150 end
150 end
151 changeset = {}
151 changeset = {}
152 files = []
152 files = []
153 revno = revno + 1
153 revno = revno + 1
154 end
154 end
155 changeset[:commit] = $1
155 changeset[:commit] = $1
156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
157 key = $1
157 key = $1
158 value = $2
158 value = $2
159 if key == "Author"
159 if key == "Author"
160 changeset[:author] = value
160 changeset[:author] = value
161 elsif key == "CommitDate"
161 elsif key == "CommitDate"
162 changeset[:date] = value
162 changeset[:date] = value
163 end
163 end
164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
165 parsing_descr = 1
165 parsing_descr = 1
166 changeset[:description] = ""
166 changeset[:description] = ""
167 elsif (parsing_descr == 1 || parsing_descr == 2) \
167 elsif (parsing_descr == 1 || parsing_descr == 2) \
168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
169 parsing_descr = 2
169 parsing_descr = 2
170 fileaction = $1
170 fileaction = $1
171 filepath = $2
171 filepath = $2
172 files << {:action => fileaction, :path => filepath}
172 files << {:action => fileaction, :path => filepath}
173 elsif (parsing_descr == 1 || parsing_descr == 2) \
173 elsif (parsing_descr == 1 || parsing_descr == 2) \
174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
175 parsing_descr = 2
175 parsing_descr = 2
176 fileaction = $1
176 fileaction = $1
177 filepath = $3
177 filepath = $3
178 files << {:action => fileaction, :path => filepath}
178 files << {:action => fileaction, :path => filepath}
179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
180 parsing_descr = 2
180 parsing_descr = 2
181 elsif (parsing_descr == 1)
181 elsif (parsing_descr == 1)
182 changeset[:description] << line[4..-1]
182 changeset[:description] << line[4..-1]
183 end
183 end
184 end
184 end
185
185
186 if changeset[:commit]
186 if changeset[:commit]
187 revision = Revision.new({
187 revision = Revision.new({
188 :identifier => changeset[:commit],
188 :identifier => changeset[:commit],
189 :scmid => changeset[:commit],
189 :scmid => changeset[:commit],
190 :author => changeset[:author],
190 :author => changeset[:author],
191 :time => Time.parse(changeset[:date]),
191 :time => Time.parse(changeset[:date]),
192 :message => changeset[:description],
192 :message => changeset[:description],
193 :paths => files
193 :paths => files
194 })
194 })
195
195
196 if block_given?
196 if block_given?
197 yield revision
197 yield revision
198 else
198 else
199 revisions << revision
199 revisions << revision
200 end
200 end
201 end
201 end
202 end
202 end
203
203
204 return nil if $? && $?.exitstatus != 0
204 return nil if $? && $?.exitstatus != 0
205 revisions
205 revisions
206 end
206 end
207
207
208 def diff(path, identifier_from, identifier_to=nil)
208 def diff(path, identifier_from, identifier_to=nil)
209 path ||= ''
209 path ||= ''
210
210
211 if identifier_to
211 if identifier_to
212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}"
212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}"
213 else
213 else
214 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
214 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
215 end
215 end
216
216
217 cmd << " -- #{shell_quote path}" unless path.empty?
217 cmd << " -- #{shell_quote path}" unless path.empty?
218 diff = []
218 diff = []
219 shellout(cmd) do |io|
219 shellout(cmd) do |io|
220 io.each_line do |line|
220 io.each_line do |line|
221 diff << line
221 diff << line
222 end
222 end
223 end
223 end
224 return nil if $? && $?.exitstatus != 0
224 return nil if $? && $?.exitstatus != 0
225 diff
225 diff
226 end
226 end
227
227
228 def annotate(path, identifier=nil)
228 def annotate(path, identifier=nil)
229 identifier = 'HEAD' if identifier.blank?
229 identifier = 'HEAD' if identifier.blank?
230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -p #{shell_quote identifier} -- #{shell_quote path}"
231 blame = Annotate.new
231 blame = Annotate.new
232 content = nil
232 content = nil
233 shellout(cmd) { |io| io.binmode; content = io.read }
233 shellout(cmd) { |io| io.binmode; content = io.read }
234 return nil if $? && $?.exitstatus != 0
234 return nil if $? && $?.exitstatus != 0
235 # git annotates binary files
235 # git annotates binary files
236 return nil if content.is_binary_data?
236 return nil if content.is_binary_data?
237 identifier = ''
237 identifier = ''
238 # git shows commit author on the first occurrence only
238 # git shows commit author on the first occurrence only
239 authors_by_commit = {}
239 authors_by_commit = {}
240 content.split("\n").each do |line|
240 content.split("\n").each do |line|
241 if line =~ /^([0-9a-f]{39,40})\s.*/
241 if line =~ /^([0-9a-f]{39,40})\s.*/
242 identifier = $1
242 identifier = $1
243 elsif line =~ /^author (.+)/
243 elsif line =~ /^author (.+)/
244 authors_by_commit[identifier] = $1.strip
244 authors_by_commit[identifier] = $1.strip
245 elsif line =~ /^\t(.*)/
245 elsif line =~ /^\t(.*)/
246 blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier]))
246 blame.add_line($1, Revision.new(:identifier => identifier, :author => authors_by_commit[identifier]))
247 identifier = ''
247 identifier = ''
248 author = ''
248 author = ''
249 end
249 end
250 end
250 end
251 blame
251 blame
252 end
252 end
253
253
254 def cat(path, identifier=nil)
254 def cat(path, identifier=nil)
255 if identifier.nil?
255 if identifier.nil?
256 identifier = 'HEAD'
256 identifier = 'HEAD'
257 end
257 end
258 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
258 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
259 cat = nil
259 cat = nil
260 shellout(cmd) do |io|
260 shellout(cmd) do |io|
261 io.binmode
261 io.binmode
262 cat = io.read
262 cat = io.read
263 end
263 end
264 return nil if $? && $?.exitstatus != 0
264 return nil if $? && $?.exitstatus != 0
265 cat
265 cat
266 end
266 end
267 end
267 end
268 end
268 end
269 end
269 end
270 end
270 end
@@ -1,37 +1,41
1 require File.dirname(__FILE__) + '/../../../../../test_helper'
1 require File.dirname(__FILE__) + '/../../../../../test_helper'
2
2
3 class GitAdapterTest < ActiveSupport::TestCase
3 class GitAdapterTest < ActiveSupport::TestCase
4 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
4 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
5
5
6 if File.directory?(REPOSITORY_PATH)
6 if File.directory?(REPOSITORY_PATH)
7 def setup
7 def setup
8 @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH)
8 @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH)
9 end
9 end
10
10
11 def test_branches
11 def test_branches
12 assert_equal @adapter.branches, ['master', 'test_branch']
12 assert_equal @adapter.branches, ['master', 'test_branch']
13 end
13 end
14
14
15 def test_getting_all_revisions
15 def test_getting_all_revisions
16 assert_equal 13, @adapter.revisions('',nil,nil,:all => true).length
16 assert_equal 13, @adapter.revisions('',nil,nil,:all => true).length
17 end
17 end
18
18
19 def test_getting_certain_revisions
20 assert_equal 1, @adapter.revisions('','899a15d^','899a15d').length
21 end
22
19 def test_annotate
23 def test_annotate
20 annotate = @adapter.annotate('sources/watchers_controller.rb')
24 annotate = @adapter.annotate('sources/watchers_controller.rb')
21 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
25 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
22 assert_equal 41, annotate.lines.size
26 assert_equal 41, annotate.lines.size
23 assert_equal "# This program is free software; you can redistribute it and/or", annotate.lines[4].strip
27 assert_equal "# This program is free software; you can redistribute it and/or", annotate.lines[4].strip
24 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", annotate.revisions[4].identifier
28 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", annotate.revisions[4].identifier
25 assert_equal "jsmith", annotate.revisions[4].author
29 assert_equal "jsmith", annotate.revisions[4].author
26 end
30 end
27
31
28 def test_annotate_moved_file
32 def test_annotate_moved_file
29 annotate = @adapter.annotate('renamed_test.txt')
33 annotate = @adapter.annotate('renamed_test.txt')
30 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
34 assert_kind_of Redmine::Scm::Adapters::Annotate, annotate
31 assert_equal 2, annotate.lines.size
35 assert_equal 2, annotate.lines.size
32 end
36 end
33 else
37 else
34 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
38 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
35 def test_fake; assert true end
39 def test_fake; assert true end
36 end
40 end
37 end
41 end
General Comments 0
You need to be logged in to leave comments. Login now