##// END OF EJS Templates
Fixed: View differences for individual file of a changeset fails if the subversion repository URL doesn't point to the repository root (#1209, #1262, #1275)....
Jean-Philippe Lang -
r1432:193b2450f47a
parent child
Show More
@@ -1,22 +1,26
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 class Change < ActiveRecord::Base
19 19 belongs_to :changeset
20 20
21 21 validates_presence_of :changeset_id, :action, :path
22
23 def relative_path
24 changeset.repository.relative_path(path)
25 end
22 26 end
@@ -1,110 +1,115
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 class Repository < ActiveRecord::Base
19 19 belongs_to :project
20 20 has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 21 has_many :changes, :through => :changesets
22 22
23 23 # Removes leading and trailing whitespace
24 24 def url=(arg)
25 25 write_attribute(:url, arg ? arg.to_s.strip : nil)
26 26 end
27 27
28 28 # Removes leading and trailing whitespace
29 29 def root_url=(arg)
30 30 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
31 31 end
32 32
33 33 def scm
34 34 @scm ||= self.scm_adapter.new url, root_url, login, password
35 35 update_attribute(:root_url, @scm.root_url) if root_url.blank?
36 36 @scm
37 37 end
38 38
39 39 def scm_name
40 40 self.class.scm_name
41 41 end
42 42
43 43 def supports_cat?
44 44 scm.supports_cat?
45 45 end
46 46
47 47 def supports_annotate?
48 48 scm.supports_annotate?
49 49 end
50 50
51 51 def entries(path=nil, identifier=nil)
52 52 scm.entries(path, identifier)
53 53 end
54 54
55 55 def diff(path, rev, rev_to, type)
56 56 scm.diff(path, rev, rev_to, type)
57 57 end
58 58
59 59 # Default behaviour: we search in cached changesets
60 60 def changesets_for_path(path)
61 61 path = "/#{path}" unless path.starts_with?('/')
62 62 Change.find(:all, :include => :changeset,
63 63 :conditions => ["repository_id = ? AND path = ?", id, path],
64 64 :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
65 65 end
66 66
67 # Returns a path relative to the url of the repository
68 def relative_path(path)
69 path
70 end
71
67 72 def latest_changeset
68 73 @latest_changeset ||= changesets.find(:first)
69 74 end
70 75
71 76 def scan_changesets_for_issue_ids
72 77 self.changesets.each(&:scan_comment_for_issue_ids)
73 78 end
74 79
75 80 # fetch new changesets for all repositories
76 81 # can be called periodically by an external script
77 82 # eg. ruby script/runner "Repository.fetch_changesets"
78 83 def self.fetch_changesets
79 84 find(:all).each(&:fetch_changesets)
80 85 end
81 86
82 87 # scan changeset comments to find related and fixed issues for all repositories
83 88 def self.scan_changesets_for_issue_ids
84 89 find(:all).each(&:scan_changesets_for_issue_ids)
85 90 end
86 91
87 92 def self.scm_name
88 93 'Abstract'
89 94 end
90 95
91 96 def self.available_scm
92 97 subclasses.collect {|klass| [klass.scm_name, klass.name]}
93 98 end
94 99
95 100 def self.factory(klass_name, *args)
96 101 klass = "Repository::#{klass_name}".constantize
97 102 klass.new(*args)
98 103 rescue
99 104 nil
100 105 end
101 106
102 107 private
103 108
104 109 def before_save
105 110 # Strips url and root_url
106 111 url.strip!
107 112 root_url.strip!
108 113 true
109 114 end
110 115 end
@@ -1,74 +1,89
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/subversion_adapter'
19 19
20 20 class Repository::Subversion < Repository
21 21 attr_protected :root_url
22 22 validates_presence_of :url
23 23 validates_format_of :url, :with => /^(http|https|svn|svn\+ssh|file):\/\/.+/i
24 24
25 25 def scm_adapter
26 26 Redmine::Scm::Adapters::SubversionAdapter
27 27 end
28 28
29 29 def self.scm_name
30 30 'Subversion'
31 31 end
32 32
33 33 def changesets_for_path(path)
34 34 revisions = scm.revisions(path)
35 35 revisions ? changesets.find_all_by_revision(revisions.collect(&:identifier), :order => "committed_on DESC") : []
36 36 end
37 37
38 # Returns a path relative to the url of the repository
39 def relative_path(path)
40 path.gsub(Regexp.new("^\/?#{Regexp.escape(relative_url)}"), '')
41 end
42
38 43 def fetch_changesets
39 44 scm_info = scm.info
40 45 if scm_info
41 46 # latest revision found in database
42 47 db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
43 48 # latest revision in the repository
44 49 scm_revision = scm_info.lastrev.identifier.to_i
45 50 if db_revision < scm_revision
46 51 logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug?
47 52 identifier_from = db_revision + 1
48 53 while (identifier_from <= scm_revision)
49 54 # loads changesets by batches of 200
50 55 identifier_to = [identifier_from + 199, scm_revision].min
51 56 revisions = scm.revisions('', identifier_to, identifier_from, :with_paths => true)
52 57 transaction do
53 58 revisions.reverse_each do |revision|
54 59 changeset = Changeset.create(:repository => self,
55 60 :revision => revision.identifier,
56 61 :committer => revision.author,
57 62 :committed_on => revision.time,
58 63 :comments => revision.message)
59 64
60 65 revision.paths.each do |change|
61 66 Change.create(:changeset => changeset,
62 67 :action => change[:action],
63 68 :path => change[:path],
64 69 :from_path => change[:from_path],
65 70 :from_revision => change[:from_revision])
66 71 end
67 72 end
68 73 end unless revisions.nil?
69 74 identifier_from = identifier_to + 1
70 75 end
71 76 end
72 77 end
73 78 end
79
80 private
81
82 # Returns the relative url of the repository
83 # Eg: root_url = file:///var/svn/foo
84 # url = file:///var/svn/foo/bar
85 # => returns /bar
86 def relative_url
87 @relative_url ||= url.gsub(Regexp.new("^#{Regexp.escape(root_url)}"), '')
88 end
74 89 end
@@ -1,65 +1,65
1 1 <div class="contextual">
2 2 &#171;
3 3 <% unless @changeset.previous.nil? -%>
4 4 <%= link_to l(:label_previous), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.previous.revision %>
5 5 <% else -%>
6 6 <%= l(:label_previous) %>
7 7 <% end -%>
8 8 |
9 9 <% unless @changeset.next.nil? -%>
10 10 <%= link_to l(:label_next), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.next.revision %>
11 11 <% else -%>
12 12 <%= l(:label_next) %>
13 13 <% end -%>
14 14 &#187;&nbsp;
15 15
16 16 <% form_tag do %>
17 17 <%= text_field_tag 'rev', @rev, :size => 5 %>
18 18 <%= submit_tag 'OK' %>
19 19 <% end %>
20 20 </div>
21 21
22 22 <h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
23 23
24 24 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
25 25 <em><%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %></em></p>
26 26
27 27 <%= textilizable @changeset.comments %>
28 28
29 29 <% if @changeset.issues.any? %>
30 30 <h3><%= l(:label_related_issues) %></h3>
31 31 <ul>
32 32 <% @changeset.issues.each do |issue| %>
33 33 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
34 34 <% end %>
35 35 </ul>
36 36 <% end %>
37 37
38 38 <h3><%= l(:label_attachment_plural) %></h3>
39 39 <div style="float:right;">
40 40 <div class="square action_A"></div> <div style="float:left;"><%= l(:label_added) %>&nbsp;</div>
41 41 <div class="square action_M"></div> <div style="float:left;"><%= l(:label_modified) %>&nbsp;</div>
42 42 <div class="square action_D"></div> <div style="float:left;"><%= l(:label_deleted) %>&nbsp;</div>
43 43 </div>
44 44 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
45 45 <table class="list">
46 46 <tbody>
47 47 <% @changes.each do |change| %>
48 48 <tr class="<%= cycle 'odd', 'even' %>">
49 49 <td><div class="square action_<%= change.action %>"></div> <%= change.path %> <%= "(#{change.revision})" unless change.revision.blank? %></td>
50 50 <td align="right">
51 51 <% if change.action == "M" %>
52 <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => without_leading_slash(change.path), :rev => @changeset.revision %>
52 <%= link_to l(:label_view_diff), :action => 'diff', :id => @project, :path => without_leading_slash(change.relative_path), :rev => @changeset.revision %>
53 53 <% end %>
54 54 </td>
55 55 </tr>
56 56 <% end %>
57 57 </tbody>
58 58 </table>
59 59 <p class="pagination"><%= pagination_links_full @changes_pages %></p>
60 60
61 61 <% content_for :header_tags do %>
62 62 <%= stylesheet_link_tag "scm" %>
63 63 <% end %>
64 64
65 65 <% html_title("#{l(:label_revision)} #{@changeset.revision}") -%>
@@ -1,16 +1,23
1 1 ---
2 2 changes_001:
3 3 id: 1
4 4 changeset_id: 100
5 5 action: A
6 6 path: /test/some/path/in/the/repo
7 7 from_path:
8 8 from_revision:
9 9 changes_002:
10 10 id: 2
11 11 changeset_id: 100
12 12 action: A
13 13 path: /test/some/path/elsewhere/in/the/repo
14 14 from_path:
15 15 from_revision:
16 changes_003:
17 id: 3
18 changeset_id: 101
19 action: M
20 path: /test/some/path/in/the/repo
21 from_path:
22 from_revision:
16 23 No newline at end of file
@@ -1,115 +1,141
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.dirname(__FILE__) + '/../test_helper'
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 RepositoriesSubversionControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :users, :roles, :members, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :trackers
26 26
27 27 # No '..' in the repository path for svn
28 28 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/subversion_repository'
29 29
30 30 def setup
31 31 @controller = RepositoriesController.new
32 32 @request = ActionController::TestRequest.new
33 33 @response = ActionController::TestResponse.new
34 34 Setting.default_language = 'en'
35 35 User.current = nil
36 36 end
37 37
38 38 if File.directory?(REPOSITORY_PATH)
39 39 def test_show
40 40 get :show, :id => 1
41 41 assert_response :success
42 42 assert_template 'show'
43 43 assert_not_nil assigns(:entries)
44 44 assert_not_nil assigns(:changesets)
45 45 end
46 46
47 47 def test_browse_root
48 48 get :browse, :id => 1
49 49 assert_response :success
50 50 assert_template 'browse'
51 51 assert_not_nil assigns(:entries)
52 52 entry = assigns(:entries).detect {|e| e.name == 'subversion_test'}
53 53 assert_equal 'dir', entry.kind
54 54 end
55 55
56 56 def test_browse_directory
57 57 get :browse, :id => 1, :path => ['subversion_test']
58 58 assert_response :success
59 59 assert_template 'browse'
60 60 assert_not_nil assigns(:entries)
61 61 assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name)
62 62 entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'}
63 63 assert_equal 'file', entry.kind
64 64 assert_equal 'subversion_test/helloworld.c', entry.path
65 65 end
66 66
67 67 def test_browse_at_given_revision
68 68 get :browse, :id => 1, :path => ['subversion_test'], :rev => 4
69 69 assert_response :success
70 70 assert_template 'browse'
71 71 assert_not_nil assigns(:entries)
72 72 assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name)
73 73 end
74 74
75 75 def test_entry
76 76 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c']
77 77 assert_response :success
78 78 assert_template 'entry'
79 79 end
80 80
81 81 def test_entry_not_found
82 82 get :entry, :id => 1, :path => ['subversion_test', 'zzz.c']
83 83 assert_tag :tag => 'div', :attributes => { :class => /error/ },
84 84 :content => /The entry or revision was not found in the repository/
85 85 end
86 86
87 87 def test_entry_download
88 88 get :entry, :id => 1, :path => ['subversion_test', 'helloworld.c'], :format => 'raw'
89 89 assert_response :success
90 90 end
91 91
92 92 def test_directory_entry
93 93 get :entry, :id => 1, :path => ['subversion_test', 'folder']
94 94 assert_response :success
95 95 assert_template 'browse'
96 96 assert_not_nil assigns(:entry)
97 97 assert_equal 'folder', assigns(:entry).name
98 98 end
99 99
100 def test_revision
101 get :revision, :id => 1, :rev => 2
102 assert_response :success
103 assert_template 'revision'
104 assert_tag :tag => 'tr',
105 :child => { :tag => 'td', :content => %r{/test/some/path/in/the/repo} },
106 :child => { :tag => 'td',
107 :child => { :tag => 'a', :attributes => { :href => '/repositories/diff/ecookbook/test/some/path/in/the/repo?rev=2' } }
108 }
109 end
110
111 def test_revision_with_repository_pointing_to_a_subdirectory
112 r = Project.find(1).repository
113 # Changes repository url to a subdirectory
114 r.update_attribute :url, (r.url + '/test/some')
115
116 get :revision, :id => 1, :rev => 2
117 assert_response :success
118 assert_template 'revision'
119 assert_tag :tag => 'tr',
120 :child => { :tag => 'td', :content => %r{/test/some/path/in/the/repo} },
121 :child => { :tag => 'td',
122 :child => { :tag => 'a', :attributes => { :href => '/repositories/diff/ecookbook/path/in/the/repo?rev=2' } }
123 }
124 end
125
100 126 def test_diff
101 127 get :diff, :id => 1, :rev => 3
102 128 assert_response :success
103 129 assert_template 'diff'
104 130 end
105 131
106 132 def test_annotate
107 133 get :annotate, :id => 1, :path => ['subversion_test', 'helloworld.c']
108 134 assert_response :success
109 135 assert_template 'annotate'
110 136 end
111 137 else
112 138 puts "Subversion test repository NOT FOUND. Skipping functional tests !!!"
113 139 def test_fake; assert true end
114 140 end
115 141 end
General Comments 0
You need to be logged in to leave comments. Login now