##// END OF EJS Templates
scm: mercurial: change two revision diff text of mercurial (#3724)....
Toshi MARUYAMA -
r4579:eda1cd2faa39
parent child
Show More
@@ -1,126 +1,130
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/mercurial_adapter'
18 require 'redmine/scm/adapters/mercurial_adapter'
19
19
20 class Repository::Mercurial < Repository
20 class Repository::Mercurial < Repository
21 # sort changesets by revision number
21 # sort changesets by revision number
22 has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
22 has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id'
23
23
24 attr_protected :root_url
24 attr_protected :root_url
25 validates_presence_of :url
25 validates_presence_of :url
26
26
27 def scm_adapter
27 def scm_adapter
28 Redmine::Scm::Adapters::MercurialAdapter
28 Redmine::Scm::Adapters::MercurialAdapter
29 end
29 end
30
30
31 def self.scm_name
31 def self.scm_name
32 'Mercurial'
32 'Mercurial'
33 end
33 end
34
34
35 # Returns the readable identifier for the given mercurial changeset
35 # Returns the readable identifier for the given mercurial changeset
36 def self.format_changeset_identifier(changeset)
36 def self.format_changeset_identifier(changeset)
37 "#{changeset.revision}:#{changeset.scmid}"
37 "#{changeset.revision}:#{changeset.scmid}"
38 end
38 end
39
39
40 # Returns the identifier for the given Mercurial changeset
40 # Returns the identifier for the given Mercurial changeset
41 def self.changeset_identifier(changeset)
41 def self.changeset_identifier(changeset)
42 changeset.scmid
42 changeset.scmid
43 end
43 end
44
44
45 def diff_format_revisions(cs, cs_to, sep=':')
46 super(cs, cs_to, ' ')
47 end
48
45 def entries(path=nil, identifier=nil)
49 def entries(path=nil, identifier=nil)
46 entries=scm.entries(path, identifier)
50 entries=scm.entries(path, identifier)
47 if entries
51 if entries
48 entries.each do |entry|
52 entries.each do |entry|
49 next unless entry.is_file?
53 next unless entry.is_file?
50 # Set the filesize unless browsing a specific revision
54 # Set the filesize unless browsing a specific revision
51 if identifier.nil?
55 if identifier.nil?
52 full_path = File.join(root_url, entry.path)
56 full_path = File.join(root_url, entry.path)
53 entry.size = File.stat(full_path).size if File.file?(full_path)
57 entry.size = File.stat(full_path).size if File.file?(full_path)
54 end
58 end
55 # Search the DB for the entry's last change
59 # Search the DB for the entry's last change
56 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
60 change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
57 if change
61 if change
58 entry.lastrev.identifier = change.changeset.revision
62 entry.lastrev.identifier = change.changeset.revision
59 entry.lastrev.name = change.changeset.revision
63 entry.lastrev.name = change.changeset.revision
60 entry.lastrev.author = change.changeset.committer
64 entry.lastrev.author = change.changeset.committer
61 entry.lastrev.revision = change.revision
65 entry.lastrev.revision = change.revision
62 end
66 end
63 end
67 end
64 end
68 end
65 entries
69 entries
66 end
70 end
67
71
68 # Finds and returns a revision with a number or the beginning of a hash
72 # Finds and returns a revision with a number or the beginning of a hash
69 def find_changeset_by_name(name)
73 def find_changeset_by_name(name)
70 if /[^\d]/ =~ name or name.to_s.size > 8
74 if /[^\d]/ =~ name or name.to_s.size > 8
71 e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
75 e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s])
72 else
76 else
73 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
77 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
74 end
78 end
75 return e if e
79 return e if e
76 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
80 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) # last ditch
77 end
81 end
78
82
79 # Returns the latest changesets for +path+; sorted by revision number
83 # Returns the latest changesets for +path+; sorted by revision number
80 def latest_changesets(path, rev, limit=10)
84 def latest_changesets(path, rev, limit=10)
81 if path.blank?
85 if path.blank?
82 changesets.find(:all, :include => :user, :limit => limit)
86 changesets.find(:all, :include => :user, :limit => limit)
83 else
87 else
84 changes.find(:all, :include => {:changeset => :user},
88 changes.find(:all, :include => {:changeset => :user},
85 :conditions => ["path = ?", path.with_leading_slash],
89 :conditions => ["path = ?", path.with_leading_slash],
86 :order => "#{Changeset.table_name}.id DESC",
90 :order => "#{Changeset.table_name}.id DESC",
87 :limit => limit).collect(&:changeset)
91 :limit => limit).collect(&:changeset)
88 end
92 end
89 end
93 end
90
94
91 def fetch_changesets
95 def fetch_changesets
92 scm_info = scm.info
96 scm_info = scm.info
93 if scm_info
97 if scm_info
94 # latest revision found in database
98 # latest revision found in database
95 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
99 db_revision = latest_changeset ? latest_changeset.revision.to_i : -1
96 # latest revision in the repository
100 # latest revision in the repository
97 latest_revision = scm_info.lastrev
101 latest_revision = scm_info.lastrev
98 return if latest_revision.nil?
102 return if latest_revision.nil?
99 scm_revision = latest_revision.identifier.to_i
103 scm_revision = latest_revision.identifier.to_i
100 if db_revision < scm_revision
104 if db_revision < scm_revision
101 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
105 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
102 identifier_from = db_revision + 1
106 identifier_from = db_revision + 1
103 while (identifier_from <= scm_revision)
107 while (identifier_from <= scm_revision)
104 # loads changesets by batches of 100
108 # loads changesets by batches of 100
105 identifier_to = [identifier_from + 99, scm_revision].min
109 identifier_to = [identifier_from + 99, scm_revision].min
106 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
110 revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true)
107 transaction do
111 transaction do
108 revisions.each do |revision|
112 revisions.each do |revision|
109 changeset = Changeset.create(:repository => self,
113 changeset = Changeset.create(:repository => self,
110 :revision => revision.identifier,
114 :revision => revision.identifier,
111 :scmid => revision.scmid,
115 :scmid => revision.scmid,
112 :committer => revision.author,
116 :committer => revision.author,
113 :committed_on => revision.time,
117 :committed_on => revision.time,
114 :comments => revision.message)
118 :comments => revision.message)
115
119
116 revision.paths.each do |change|
120 revision.paths.each do |change|
117 changeset.create_change(change)
121 changeset.create_change(change)
118 end
122 end
119 end
123 end
120 end unless revisions.nil?
124 end unless revisions.nil?
121 identifier_from = identifier_to + 1
125 identifier_from = identifier_to + 1
122 end
126 end
123 end
127 end
124 end
128 end
125 end
129 end
126 end
130 end
@@ -1,180 +1,202
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'repositories_controller'
19 require 'repositories_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class RepositoriesController; def rescue_action(e) raise e end; end
22 class RepositoriesController; def rescue_action(e) raise e end; end
23
23
24 class RepositoriesMercurialControllerTest < ActionController::TestCase
24 class RepositoriesMercurialControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules
25 fixtures :projects, :users, :roles, :members, :member_roles, :repositories, :enabled_modules
26
26
27 # No '..' in the repository path
27 # No '..' in the repository path
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/mercurial_repository'
29
29
30 def setup
30 def setup
31 @controller = RepositoriesController.new
31 @controller = RepositoriesController.new
32 @request = ActionController::TestRequest.new
32 @request = ActionController::TestRequest.new
33 @response = ActionController::TestResponse.new
33 @response = ActionController::TestResponse.new
34 User.current = nil
34 User.current = nil
35 @repository = Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH)
35 @repository = Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36 assert @repository
36 assert @repository
37 end
37 end
38
38
39 if File.directory?(REPOSITORY_PATH)
39 if File.directory?(REPOSITORY_PATH)
40 def test_show
40 def test_show
41 get :show, :id => 3
41 get :show, :id => 3
42 assert_response :success
42 assert_response :success
43 assert_template 'show'
43 assert_template 'show'
44 assert_not_nil assigns(:entries)
44 assert_not_nil assigns(:entries)
45 assert_not_nil assigns(:changesets)
45 assert_not_nil assigns(:changesets)
46 end
46 end
47
47
48 def test_show_root
48 def test_show_root
49 get :show, :id => 3
49 get :show, :id => 3
50 assert_response :success
50 assert_response :success
51 assert_template 'show'
51 assert_template 'show'
52 assert_not_nil assigns(:entries)
52 assert_not_nil assigns(:entries)
53 assert_equal 4, assigns(:entries).size
53 assert_equal 4, assigns(:entries).size
54 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
54 assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
55 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
55 assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
56 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
56 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
57 end
57 end
58
58
59 def test_show_directory
59 def test_show_directory
60 get :show, :id => 3, :path => ['images']
60 get :show, :id => 3, :path => ['images']
61 assert_response :success
61 assert_response :success
62 assert_template 'show'
62 assert_template 'show'
63 assert_not_nil assigns(:entries)
63 assert_not_nil assigns(:entries)
64 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
64 assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
65 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
65 entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
66 assert_not_nil entry
66 assert_not_nil entry
67 assert_equal 'file', entry.kind
67 assert_equal 'file', entry.kind
68 assert_equal 'images/edit.png', entry.path
68 assert_equal 'images/edit.png', entry.path
69 end
69 end
70
70
71 def test_show_at_given_revision
71 def test_show_at_given_revision
72 [0, '0', '0885933ad4f6'].each do |r1|
72 [0, '0', '0885933ad4f6'].each do |r1|
73 get :show, :id => 3, :path => ['images'], :rev => r1
73 get :show, :id => 3, :path => ['images'], :rev => r1
74 assert_response :success
74 assert_response :success
75 assert_template 'show'
75 assert_template 'show'
76 assert_not_nil assigns(:entries)
76 assert_not_nil assigns(:entries)
77 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
77 assert_equal ['delete.png'], assigns(:entries).collect(&:name)
78 end
78 end
79 end
79 end
80
80
81 def test_show_directory_sql_escape_percent
81 def test_show_directory_sql_escape_percent
82 [13, '13', '3a330eb32958'].each do |r1|
82 [13, '13', '3a330eb32958'].each do |r1|
83 get :show, :id => 3, :path => ['sql_escape', 'percent%dir'], :rev => r1
83 get :show, :id => 3, :path => ['sql_escape', 'percent%dir'], :rev => r1
84 assert_response :success
84 assert_response :success
85 assert_template 'show'
85 assert_template 'show'
86
86
87 assert_not_nil assigns(:entries)
87 assert_not_nil assigns(:entries)
88 assert_equal ['percent%file1.txt', 'percentfile1.txt'], assigns(:entries).collect(&:name)
88 assert_equal ['percent%file1.txt', 'percentfile1.txt'], assigns(:entries).collect(&:name)
89 changesets = assigns(:changesets)
89 changesets = assigns(:changesets)
90
90
91 ## This is not yet implemented.
91 ## This is not yet implemented.
92 # assert_not_nil changesets
92 # assert_not_nil changesets
93 # assert_equal %w(13 11 10 9), changesets.collect(&:revision)
93 # assert_equal %w(13 11 10 9), changesets.collect(&:revision)
94 end
94 end
95 end
95 end
96
96
97 def test_changes
97 def test_changes
98 get :changes, :id => 3, :path => ['images', 'edit.png']
98 get :changes, :id => 3, :path => ['images', 'edit.png']
99 assert_response :success
99 assert_response :success
100 assert_template 'changes'
100 assert_template 'changes'
101 assert_tag :tag => 'h2', :content => 'edit.png'
101 assert_tag :tag => 'h2', :content => 'edit.png'
102 end
102 end
103
103
104 def test_entry_show
104 def test_entry_show
105 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
105 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
106 assert_response :success
106 assert_response :success
107 assert_template 'entry'
107 assert_template 'entry'
108 # Line 10
108 # Line 10
109 assert_tag :tag => 'th',
109 assert_tag :tag => 'th',
110 :content => '10',
110 :content => '10',
111 :attributes => { :class => 'line-num' },
111 :attributes => { :class => 'line-num' },
112 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
112 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
113 end
113 end
114
114
115 def test_entry_download
115 def test_entry_download
116 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
116 get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
117 assert_response :success
117 assert_response :success
118 # File content
118 # File content
119 assert @response.body.include?('WITHOUT ANY WARRANTY')
119 assert @response.body.include?('WITHOUT ANY WARRANTY')
120 end
120 end
121
121
122 def test_directory_entry
122 def test_directory_entry
123 get :entry, :id => 3, :path => ['sources']
123 get :entry, :id => 3, :path => ['sources']
124 assert_response :success
124 assert_response :success
125 assert_template 'show'
125 assert_template 'show'
126 assert_not_nil assigns(:entry)
126 assert_not_nil assigns(:entry)
127 assert_equal 'sources', assigns(:entry).name
127 assert_equal 'sources', assigns(:entry).name
128 end
128 end
129
129
130 def test_diff
130 def test_diff
131 @repository.fetch_changesets
132 @repository.reload
133
131 [4, '4', 'def6d2f1254a'].each do |r1|
134 [4, '4', 'def6d2f1254a'].each do |r1|
132 # Full diff of changeset 4
135 # Full diff of changeset 4
133 get :diff, :id => 3, :rev => 4
136 get :diff, :id => 3, :rev => 4
134 assert_response :success
137 assert_response :success
135 assert_template 'diff'
138 assert_template 'diff'
136
139
137 if @repository.scm.class.client_version_above?([1, 2])
140 if @repository.scm.class.client_version_above?([1, 2])
138 # Line 22 removed
141 # Line 22 removed
139 assert_tag :tag => 'th',
142 assert_tag :tag => 'th',
140 :content => '22',
143 :content => '22',
141 :sibling => { :tag => 'td',
144 :sibling => { :tag => 'td',
142 :attributes => { :class => /diff_out/ },
145 :attributes => { :class => /diff_out/ },
143 :content => /def remove/ }
146 :content => /def remove/ }
147 assert_tag :tag => 'h2', :content => /4:def6d2f1254a/
148 end
149 end
150 end
151
152 def test_diff_two_revs
153 @repository.fetch_changesets
154 @repository.reload
155
156 [2, '400bb8672109', '400', 400].each do |r1|
157 [4, 'def6d2f1254a'].each do |r2|
158 get :diff, :id => 3, :rev => r1,
159 :rev_to => r2
160 assert_response :success
161 assert_template 'diff'
162
163 diff = assigns(:diff)
164 assert_not_nil diff
165 assert_tag :tag => 'h2', :content => /4:def6d2f1254a 2:400bb8672109/
144 end
166 end
145 end
167 end
146 end
168 end
147
169
148 def test_annotate
170 def test_annotate
149 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
171 get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
150 assert_response :success
172 assert_response :success
151 assert_template 'annotate'
173 assert_template 'annotate'
152 # Line 23, revision 4:def6d2f1254a
174 # Line 23, revision 4:def6d2f1254a
153 assert_tag :tag => 'th',
175 assert_tag :tag => 'th',
154 :content => '23',
176 :content => '23',
155 :attributes => { :class => 'line-num' },
177 :attributes => { :class => 'line-num' },
156 :sibling =>
178 :sibling =>
157 {
179 {
158 :tag => 'td',
180 :tag => 'td',
159 :attributes => { :class => 'revision' },
181 :attributes => { :class => 'revision' },
160 :child => { :tag => 'a', :content => '4:def6d2f1254a' }
182 :child => { :tag => 'a', :content => '4:def6d2f1254a' }
161 }
183 }
162 assert_tag :tag => 'th',
184 assert_tag :tag => 'th',
163 :content => '23',
185 :content => '23',
164 :attributes => { :class => 'line-num' },
186 :attributes => { :class => 'line-num' },
165 :sibling =>
187 :sibling =>
166 {
188 {
167 :tag => 'td' ,
189 :tag => 'td' ,
168 :content => 'jsmith' ,
190 :content => 'jsmith' ,
169 :attributes => { :class => 'author' },
191 :attributes => { :class => 'author' },
170 }
192 }
171 assert_tag :tag => 'th',
193 assert_tag :tag => 'th',
172 :content => '23',
194 :content => '23',
173 :attributes => { :class => 'line-num' },
195 :attributes => { :class => 'line-num' },
174 :sibling => { :tag => 'td', :content => /watcher =/ }
196 :sibling => { :tag => 'td', :content => /watcher =/ }
175 end
197 end
176 else
198 else
177 puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
199 puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!"
178 def test_fake; assert true end
200 def test_fake; assert true end
179 end
201 end
180 end
202 end
General Comments 0
You need to be logged in to leave comments. Login now