##// END OF EJS Templates
scm: mercurial: latest changesets improvement and supporting tag (#1981)....
Toshi MARUYAMA -
r5003:c8ce22c2751a
parent child
Show More
@@ -1,125 +1,137
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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/mercurial_adapter'
19 19
20 20 class Repository::Mercurial < Repository
21 21 # sort changesets by revision number
22 22 has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
23 23
24 24 attr_protected :root_url
25 25 validates_presence_of :url
26 26
27 27 FETCH_AT_ONCE = 100 # number of changesets to fetch at once
28 28
29 29 ATTRIBUTE_KEY_NAMES = {
30 30 "url" => "Root directory",
31 31 }
32 32 def self.human_attribute_name(attribute_key_name)
33 33 ATTRIBUTE_KEY_NAMES[attribute_key_name] || super
34 34 end
35 35
36 36 def self.scm_adapter_class
37 37 Redmine::Scm::Adapters::MercurialAdapter
38 38 end
39 39
40 40 def self.scm_name
41 41 'Mercurial'
42 42 end
43 43
44 44 def repo_log_encoding
45 45 'UTF-8'
46 46 end
47 47
48 48 # Returns the readable identifier for the given mercurial changeset
49 49 def self.format_changeset_identifier(changeset)
50 50 "#{changeset.revision}:#{changeset.scmid}"
51 51 end
52 52
53 53 # Returns the identifier for the given Mercurial changeset
54 54 def self.changeset_identifier(changeset)
55 55 changeset.scmid
56 56 end
57 57
58 58 def branches
59 59 nil
60 60 end
61 61
62 62 def tags
63 63 nil
64 64 end
65 65
66 66 def diff_format_revisions(cs, cs_to, sep=':')
67 67 super(cs, cs_to, ' ')
68 68 end
69 69
70 70 # Finds and returns a revision with a number or the beginning of a hash
71 71 def find_changeset_by_name(name)
72 72 return nil if name.nil? || name.empty?
73 73 if /[^\d]/ =~ name or name.to_s.size > 8
74 74 e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
75 75 else
76 76 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
77 77 end
78 78 return e if e
79 79 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
80 80 end
81 81
82 82 # Returns the latest changesets for +path+; sorted by revision number
83 # Default behavior is to search in cached changesets
84 83 #
85 84 # Because :order => 'id DESC' is defined at 'has_many',
86 85 # there is no need to set 'order'.
87 86 # But, MySQL test fails.
88 87 # Sqlite3 and PostgreSQL pass.
89 88 # Is this MySQL bug?
90 89 def latest_changesets(path, rev, limit=10)
91 if path.blank?
92 changesets.find(:all, :include => :user, :limit => limit, :order => 'id DESC')
93 else
94 changesets.find(:all, :select => "DISTINCT #{Changeset.table_name}.*",
95 :joins => :changes,
96 :conditions => ["#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ? ESCAPE ?",
97 path.with_leading_slash,
98 "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%", '\\'],
99 :include => :user, :limit => limit,
100 :order => "#{Changeset.table_name}.id DESC" )
90 changesets.find(:all, :include => :user,
91 :conditions => latest_changesets_cond(path, rev, limit),
92 :limit => limit, :order => "#{Changeset.table_name}.id DESC")
93 end
94
95 def latest_changesets_cond(path, rev, limit)
96 cond, args = [], []
97
98 if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
99 cond << "#{Changeset.table_name}.id <= ?"
100 args << last.id
101 101 end
102
103 unless path.blank?
104 cond << "EXISTS (SELECT * FROM #{Change.table_name}
105 WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id
106 AND (#{Change.table_name}.path = ?
107 OR #{Change.table_name}.path LIKE ? ESCAPE ?))"
108 args << path.with_leading_slash
109 args << "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%" << '\\'
110 end
111
112 [cond.join(' AND '), *args] unless cond.empty?
102 113 end
114 private :latest_changesets_cond
103 115
104 116 def fetch_changesets
105 117 scm_rev = scm.info.lastrev.revision.to_i
106 118 db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
107 119 return unless db_rev < scm_rev # already up-to-date
108 120
109 121 logger.debug "Fetching changesets for repository #{url}" if logger
110 122 (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i|
111 123 transaction do
112 124 scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re|
113 125 cs = Changeset.create(:repository => self,
114 126 :revision => re.revision,
115 127 :scmid => re.scmid,
116 128 :committer => re.author,
117 129 :committed_on => re.time,
118 130 :comments => re.message)
119 131 re.paths.each { |e| cs.create_change(e) }
120 132 end
121 133 end
122 134 end
123 135 self
124 136 end
125 137 end
@@ -1,337 +1,337
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 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 File.expand_path('../../test_helper', __FILE__)
19 19 require 'repositories_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class RepositoriesController; def rescue_action(e) raise e end; end
23 23
24 24 class RepositoriesMercurialControllerTest < ActionController::TestCase
25 25 fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules
26 26
27 27 # No '..' in the repository path
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
29 29 CHAR_1_HEX = "\xc3\x9c"
30 30
31 31 ruby19_non_utf8_pass = (RUBY_VERSION >= '1.9' && Encoding.default_external.to_s != 'UTF-8')
32 32
33 33 def setup
34 34 @controller = RepositoriesController.new
35 35 @request = ActionController::TestRequest.new
36 36 @response = ActionController::TestResponse.new
37 37 User.current = nil
38 38 @repository = Repository::Mercurial.create(
39 39 :project => Project.find(3),
40 40 :url => REPOSITORY_PATH,
41 41 :path_encoding => 'ISO-8859-1'
42 42 )
43 43 assert @repository
44 44 @diff_c_support = true
45 45 @char_1 = CHAR_1_HEX.dup
46 46 @tag_char_1 = "tag-#{CHAR_1_HEX}-00"
47 47 @branch_char_0 = "branch-#{CHAR_1_HEX}-00"
48 48 @branch_char_1 = "branch-#{CHAR_1_HEX}-01"
49 49 if @char_1.respond_to?(:force_encoding)
50 50 @char_1.force_encoding('UTF-8')
51 51 @tag_char_1.force_encoding('UTF-8')
52 52 @branch_char_0.force_encoding('UTF-8')
53 53 @branch_char_1.force_encoding('UTF-8')
54 54 end
55 55 end
56 56
57 57 if ruby19_non_utf8_pass
58 58 puts "TODO: Mercurial functional test fails in Ruby 1.9 " +
59 59 "and Encoding.default_external is not UTF-8. " +
60 60 "Current value is '#{Encoding.default_external.to_s}'"
61 61 def test_fake; assert true end
62 62 elsif File.directory?(REPOSITORY_PATH)
63 63 def test_show
64 64 get :show, :id => 3
65 65 assert_response :success
66 66 assert_template 'show'
67 67 assert_not_nil assigns(:entries)
68 68 assert_not_nil assigns(:changesets)
69 69 end
70 70
71 71 def test_show_root
72 72 @repository.fetch_changesets
73 73 @repository.reload
74 74 get :show, :id => 3
75 75 assert_response :success
76 76 assert_template 'show'
77 77 assert_not_nil assigns(:entries)
78 78 assert_equal 4, assigns(:entries).size
79 79 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
80 80 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
81 81 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
82 82 assert_not_nil assigns(:changesets)
83 83 assigns(:changesets).size > 0
84 84 end
85 85
86 86 def test_show_directory
87 87 @repository.fetch_changesets
88 88 @repository.reload
89 89 get :show, :id => 3, :path => ['images']
90 90 assert_response :success
91 91 assert_template 'show'
92 92 assert_not_nil assigns(:entries)
93 93 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
94 94 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
95 95 assert_not_nil entry
96 96 assert_equal 'file', entry.kind
97 97 assert_equal 'images/edit.png', entry.path
98 98 assert_not_nil assigns(:changesets)
99 99 assigns(:changesets).size > 0
100 100 end
101 101
102 102 def test_show_at_given_revision
103 103 @repository.fetch_changesets
104 104 @repository.reload
105 105 [0, '0', '0885933ad4f6'].each do |r1|
106 106 get :show, :id => 3, :path => ['images'], :rev => r1
107 107 assert_response :success
108 108 assert_template 'show'
109 109 assert_not_nil assigns(:entries)
110 110 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
111 111 assert_not_nil assigns(:changesets)
112 112 assigns(:changesets).size > 0
113 113 end
114 114 end
115 115
116 116 def test_show_directory_sql_escape_percent
117 117 @repository.fetch_changesets
118 118 @repository.reload
119 119 [13, '13', '3a330eb32958'].each do |r1|
120 120 get :show, :id => 3, :path => ['sql_escape', 'percent%dir'], :rev => r1
121 121 assert_response :success
122 122 assert_template 'show'
123 123
124 124 assert_not_nil assigns(:entries)
125 125 assert_equal ['percent%file1.txt', 'percentfile1.txt'], assigns(:entries).collect(&:name)
126 126 changesets = assigns(:changesets)
127 127 assigns(:changesets).size > 0
128 128
129 129 ## This is not yet implemented.
130 130 # assert_not_nil changesets
131 131 # assert_equal %w(13 11 10 9), changesets.collect(&:revision)
132 132 end
133 133 end
134 134
135 135 def test_show_directory_latin_1
136 136 @repository.fetch_changesets
137 137 @repository.reload
138 138 [21, '21', 'adf805632193'].each do |r1|
139 139 get :show, :id => 3, :path => ['latin-1-dir'], :rev => r1
140 140 assert_response :success
141 141 assert_template 'show'
142 142
143 143 assert_not_nil assigns(:entries)
144 144 assert_equal ["make-latin-1-file.rb",
145 145 "test-#{@char_1}-1.txt",
146 146 "test-#{@char_1}-2.txt",
147 147 "test-#{@char_1}.txt"], assigns(:entries).collect(&:name)
148 148 changesets = assigns(:changesets)
149 149 assert_not_nil changesets
150 assert_equal %w(27 21 20 19 18 17), changesets.collect(&:revision)
150 assert_equal %w(21 20 19 18 17), changesets.collect(&:revision)
151 151 end
152 152 end
153 153
154 154 def test_changes
155 155 get :changes, :id => 3, :path => ['images', 'edit.png']
156 156 assert_response :success
157 157 assert_template 'changes'
158 158 assert_tag :tag => 'h2', :content => 'edit.png'
159 159 end
160 160
161 161 def test_entry_show
162 162 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
163 163 assert_response :success
164 164 assert_template 'entry'
165 165 # Line 10
166 166 assert_tag :tag => 'th',
167 167 :content => '10',
168 168 :attributes => { :class => 'line-num' },
169 169 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
170 170 end
171 171
172 172 def test_entry_show_latin_1
173 173 [21, '21', 'adf805632193'].each do |r1|
174 174 get :entry, :id => 3, :path => ['latin-1-dir', "test-#{@char_1}-2.txt"], :rev => r1
175 175 assert_response :success
176 176 assert_template 'entry'
177 177 assert_tag :tag => 'th',
178 178 :content => '1',
179 179 :attributes => { :class => 'line-num' },
180 180 :sibling => { :tag => 'td', :content => /Mercurial is a distributed version control system/ }
181 181 end
182 182 end
183 183
184 184 def test_entry_download
185 185 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
186 186 assert_response :success
187 187 # File content
188 188 assert @response.body.include?('WITHOUT ANY WARRANTY')
189 189 end
190 190
191 191 def test_directory_entry
192 192 get :entry, :id => 3, :path => ['sources']
193 193 assert_response :success
194 194 assert_template 'show'
195 195 assert_not_nil assigns(:entry)
196 196 assert_equal 'sources', assigns(:entry).name
197 197 end
198 198
199 199 def test_diff
200 200 @repository.fetch_changesets
201 201 @repository.reload
202 202
203 203 [4, '4', 'def6d2f1254a'].each do |r1|
204 204 # Full diff of changeset 4
205 205 get :diff, :id => 3, :rev => r1
206 206 assert_response :success
207 207 assert_template 'diff'
208 208
209 209 if @diff_c_support
210 210 # Line 22 removed
211 211 assert_tag :tag => 'th',
212 212 :content => '22',
213 213 :sibling => { :tag => 'td',
214 214 :attributes => { :class => /diff_out/ },
215 215 :content => /def remove/ }
216 216 assert_tag :tag => 'h2', :content => /4:def6d2f1254a/
217 217 end
218 218 end
219 219 end
220 220
221 221 def test_diff_two_revs
222 222 @repository.fetch_changesets
223 223 @repository.reload
224 224
225 225 [2, '400bb8672109', '400', 400].each do |r1|
226 226 [4, 'def6d2f1254a'].each do |r2|
227 227 get :diff, :id => 3, :rev => r1,
228 228 :rev_to => r2
229 229 assert_response :success
230 230 assert_template 'diff'
231 231
232 232 diff = assigns(:diff)
233 233 assert_not_nil diff
234 234 assert_tag :tag => 'h2', :content => /4:def6d2f1254a 2:400bb8672109/
235 235 end
236 236 end
237 237 end
238 238
239 239 def test_diff_latin_1
240 240 [21, 'adf805632193'].each do |r1|
241 241 get :diff, :id => 3, :rev => r1
242 242 assert_response :success
243 243 assert_template 'diff'
244 244 assert_tag :tag => 'th',
245 245 :content => '2',
246 246 :sibling => { :tag => 'td',
247 247 :attributes => { :class => /diff_in/ },
248 248 :content => /It is written in Python/ }
249 249 end
250 250 end
251 251
252 252 def test_annotate
253 253 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
254 254 assert_response :success
255 255 assert_template 'annotate'
256 256 # Line 23, revision 4:def6d2f1254a
257 257 assert_tag :tag => 'th',
258 258 :content => '23',
259 259 :attributes => { :class => 'line-num' },
260 260 :sibling =>
261 261 {
262 262 :tag => 'td',
263 263 :attributes => { :class => 'revision' },
264 264 :child => { :tag => 'a', :content => '4:def6d2f1254a' }
265 265 }
266 266 assert_tag :tag => 'th',
267 267 :content => '23',
268 268 :attributes => { :class => 'line-num' },
269 269 :sibling =>
270 270 {
271 271 :tag => 'td' ,
272 272 :content => 'jsmith' ,
273 273 :attributes => { :class => 'author' },
274 274 }
275 275 assert_tag :tag => 'th',
276 276 :content => '23',
277 277 :attributes => { :class => 'line-num' },
278 278 :sibling => { :tag => 'td', :content => /watcher =/ }
279 279 end
280 280
281 281 def test_annotate_at_given_revision
282 282 @repository.fetch_changesets
283 283 @repository.reload
284 284 [2, '400bb8672109', '400', 400].each do |r1|
285 285 get :annotate, :id => 3, :rev => r1, :path => ['sources', 'watchers_controller.rb']
286 286 assert_response :success
287 287 assert_template 'annotate'
288 288 assert_tag :tag => 'h2', :content => /@ 2:400bb8672109/
289 289 end
290 290 end
291 291
292 292 def test_annotate_latin_1
293 293 [21, '21', 'adf805632193'].each do |r1|
294 294 get :annotate, :id => 3, :path => ['latin-1-dir', "test-#{@char_1}-2.txt"], :rev => r1
295 295 assert_response :success
296 296 assert_template 'annotate'
297 297 assert_tag :tag => 'th',
298 298 :content => '1',
299 299 :attributes => { :class => 'line-num' },
300 300 :sibling =>
301 301 {
302 302 :tag => 'td',
303 303 :attributes => { :class => 'revision' },
304 304 :child => { :tag => 'a', :content => '20:709858aafd1b' }
305 305 }
306 306 assert_tag :tag => 'th',
307 307 :content => '1',
308 308 :attributes => { :class => 'line-num' },
309 309 :sibling =>
310 310 {
311 311 :tag => 'td' ,
312 312 :content => 'jsmith' ,
313 313 :attributes => { :class => 'author' },
314 314
315 315 }
316 316 assert_tag :tag => 'th',
317 317 :content => '1',
318 318 :attributes => { :class => 'line-num' },
319 319 :sibling => { :tag => 'td', :content => /Mercurial is a distributed version control system/ }
320 320
321 321 end
322 322 end
323 323
324 324 def test_empty_revision
325 325 @repository.fetch_changesets
326 326 @repository.reload
327 327 ['', ' ', nil].each do |r|
328 328 get :revision, :id => 3, :rev => r
329 329 assert_response 404
330 330 assert_error_tag :content => /was not found/
331 331 end
332 332 end
333 333 else
334 334 puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
335 335 def test_fake; assert true end
336 336 end
337 337 end
General Comments 0
You need to be logged in to leave comments. Login now