##// END OF EJS Templates
Fixed: deleted files should not be shown when browsing a Darcs repository (#2385)....
Jean-Philippe Lang -
r2187:a7a4c9f84878
parent child
Show More
@@ -1,189 +1,196
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 require 'rexml/document'
19 require 'rexml/document'
20
20
21 module Redmine
21 module Redmine
22 module Scm
22 module Scm
23 module Adapters
23 module Adapters
24 class DarcsAdapter < AbstractAdapter
24 class DarcsAdapter < AbstractAdapter
25 # Darcs executable name
25 # Darcs executable name
26 DARCS_BIN = "darcs"
26 DARCS_BIN = "darcs"
27
27
28 class << self
28 class << self
29 def client_version
29 def client_version
30 @@client_version ||= (darcs_binary_version || [])
30 @@client_version ||= (darcs_binary_version || [])
31 end
31 end
32
32
33 def darcs_binary_version
33 def darcs_binary_version
34 cmd = "#{DARCS_BIN} --version"
34 cmd = "#{DARCS_BIN} --version"
35 version = nil
35 version = nil
36 shellout(cmd) do |io|
36 shellout(cmd) do |io|
37 # Read darcs version in first returned line
37 # Read darcs version in first returned line
38 if m = io.gets.match(%r{((\d+\.)+\d+)})
38 if m = io.gets.match(%r{((\d+\.)+\d+)})
39 version = m[0].scan(%r{\d+}).collect(&:to_i)
39 version = m[0].scan(%r{\d+}).collect(&:to_i)
40 end
40 end
41 end
41 end
42 return nil if $? && $?.exitstatus != 0
42 return nil if $? && $?.exitstatus != 0
43 version
43 version
44 end
44 end
45 end
45 end
46
46
47 def initialize(url, root_url=nil, login=nil, password=nil)
47 def initialize(url, root_url=nil, login=nil, password=nil)
48 @url = url
48 @url = url
49 @root_url = url
49 @root_url = url
50 end
50 end
51
51
52 def supports_cat?
52 def supports_cat?
53 # cat supported in darcs 2.0.0 and higher
53 # cat supported in darcs 2.0.0 and higher
54 self.class.client_version_above?([2, 0, 0])
54 self.class.client_version_above?([2, 0, 0])
55 end
55 end
56
56
57 # Get info about the darcs repository
57 # Get info about the darcs repository
58 def info
58 def info
59 rev = revisions(nil,nil,nil,{:limit => 1})
59 rev = revisions(nil,nil,nil,{:limit => 1})
60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
60 rev ? Info.new({:root_url => @url, :lastrev => rev.last}) : nil
61 end
61 end
62
62
63 # Returns an Entries collection
63 # Returns an Entries collection
64 # or nil if the given path doesn't exist in the repository
64 # or nil if the given path doesn't exist in the repository
65 def entries(path=nil, identifier=nil)
65 def entries(path=nil, identifier=nil)
66 path_prefix = (path.blank? ? '' : "#{path}/")
66 path_prefix = (path.blank? ? '' : "#{path}/")
67 path = '.' if path.blank?
67 path = '.' if path.blank?
68 entries = Entries.new
68 entries = Entries.new
69 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
69 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --xml-output"
70 cmd << " --match \"hash #{identifier}\"" if identifier
70 cmd << " --match \"hash #{identifier}\"" if identifier
71 cmd << " #{path}"
71 cmd << " #{path}"
72 shellout(cmd) do |io|
72 shellout(cmd) do |io|
73 begin
73 begin
74 doc = REXML::Document.new(io)
74 doc = REXML::Document.new(io)
75 if doc.root.name == 'directory'
75 if doc.root.name == 'directory'
76 doc.elements.each('directory/*') do |element|
76 doc.elements.each('directory/*') do |element|
77 next unless ['file', 'directory'].include? element.name
77 next unless ['file', 'directory'].include? element.name
78 entries << entry_from_xml(element, path_prefix)
78 entries << entry_from_xml(element, path_prefix)
79 end
79 end
80 elsif doc.root.name == 'file'
80 elsif doc.root.name == 'file'
81 entries << entry_from_xml(doc.root, path_prefix)
81 entries << entry_from_xml(doc.root, path_prefix)
82 end
82 end
83 rescue
83 rescue
84 end
84 end
85 end
85 end
86 return nil if $? && $?.exitstatus != 0
86 return nil if $? && $?.exitstatus != 0
87 entries.sort_by_name
87 entries.compact.sort_by_name
88 end
88 end
89
89
90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
90 def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
91 path = '.' if path.blank?
91 path = '.' if path.blank?
92 revisions = Revisions.new
92 revisions = Revisions.new
93 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
93 cmd = "#{DARCS_BIN} changes --repodir #{@url} --xml-output"
94 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
94 cmd << " --from-match \"hash #{identifier_from}\"" if identifier_from
95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
95 cmd << " --last #{options[:limit].to_i}" if options[:limit]
96 shellout(cmd) do |io|
96 shellout(cmd) do |io|
97 begin
97 begin
98 doc = REXML::Document.new(io)
98 doc = REXML::Document.new(io)
99 doc.elements.each("changelog/patch") do |patch|
99 doc.elements.each("changelog/patch") do |patch|
100 message = patch.elements['name'].text
100 message = patch.elements['name'].text
101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
101 message << "\n" + patch.elements['comment'].text.gsub(/\*\*\*END OF DESCRIPTION\*\*\*.*\z/m, '') if patch.elements['comment']
102 revisions << Revision.new({:identifier => nil,
102 revisions << Revision.new({:identifier => nil,
103 :author => patch.attributes['author'],
103 :author => patch.attributes['author'],
104 :scmid => patch.attributes['hash'],
104 :scmid => patch.attributes['hash'],
105 :time => Time.parse(patch.attributes['local_date']),
105 :time => Time.parse(patch.attributes['local_date']),
106 :message => message,
106 :message => message,
107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
107 :paths => (options[:with_path] ? get_paths_for_patch(patch.attributes['hash']) : nil)
108 })
108 })
109 end
109 end
110 rescue
110 rescue
111 end
111 end
112 end
112 end
113 return nil if $? && $?.exitstatus != 0
113 return nil if $? && $?.exitstatus != 0
114 revisions
114 revisions
115 end
115 end
116
116
117 def diff(path, identifier_from, identifier_to=nil)
117 def diff(path, identifier_from, identifier_to=nil)
118 path = '*' if path.blank?
118 path = '*' if path.blank?
119 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
119 cmd = "#{DARCS_BIN} diff --repodir #{@url}"
120 if identifier_to.nil?
120 if identifier_to.nil?
121 cmd << " --match \"hash #{identifier_from}\""
121 cmd << " --match \"hash #{identifier_from}\""
122 else
122 else
123 cmd << " --to-match \"hash #{identifier_from}\""
123 cmd << " --to-match \"hash #{identifier_from}\""
124 cmd << " --from-match \"hash #{identifier_to}\""
124 cmd << " --from-match \"hash #{identifier_to}\""
125 end
125 end
126 cmd << " -u #{path}"
126 cmd << " -u #{path}"
127 diff = []
127 diff = []
128 shellout(cmd) do |io|
128 shellout(cmd) do |io|
129 io.each_line do |line|
129 io.each_line do |line|
130 diff << line
130 diff << line
131 end
131 end
132 end
132 end
133 return nil if $? && $?.exitstatus != 0
133 return nil if $? && $?.exitstatus != 0
134 diff
134 diff
135 end
135 end
136
136
137 def cat(path, identifier=nil)
137 def cat(path, identifier=nil)
138 cmd = "#{DARCS_BIN} show content --repodir #{@url}"
138 cmd = "#{DARCS_BIN} show content --repodir #{@url}"
139 cmd << " --match \"hash #{identifier}\"" if identifier
139 cmd << " --match \"hash #{identifier}\"" if identifier
140 cmd << " #{shell_quote path}"
140 cmd << " #{shell_quote path}"
141 cat = nil
141 cat = nil
142 shellout(cmd) do |io|
142 shellout(cmd) do |io|
143 io.binmode
143 io.binmode
144 cat = io.read
144 cat = io.read
145 end
145 end
146 return nil if $? && $?.exitstatus != 0
146 return nil if $? && $?.exitstatus != 0
147 cat
147 cat
148 end
148 end
149
149
150 private
150 private
151
151
152 # Returns an Entry from the given XML element
153 # or nil if the entry was deleted
152 def entry_from_xml(element, path_prefix)
154 def entry_from_xml(element, path_prefix)
155 modified_element = element.elements['modified']
156 if modified_element.elements['modified_how'].text.match(/removed/)
157 return nil
158 end
159
153 Entry.new({:name => element.attributes['name'],
160 Entry.new({:name => element.attributes['name'],
154 :path => path_prefix + element.attributes['name'],
161 :path => path_prefix + element.attributes['name'],
155 :kind => element.name == 'file' ? 'file' : 'dir',
162 :kind => element.name == 'file' ? 'file' : 'dir',
156 :size => nil,
163 :size => nil,
157 :lastrev => Revision.new({
164 :lastrev => Revision.new({
158 :identifier => nil,
165 :identifier => nil,
159 :scmid => element.elements['modified'].elements['patch'].attributes['hash']
166 :scmid => modified_element.elements['patch'].attributes['hash']
160 })
167 })
161 })
168 })
162 end
169 end
163
170
164 # Retrieve changed paths for a single patch
171 # Retrieve changed paths for a single patch
165 def get_paths_for_patch(hash)
172 def get_paths_for_patch(hash)
166 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
173 cmd = "#{DARCS_BIN} annotate --repodir #{@url} --summary --xml-output"
167 cmd << " --match \"hash #{hash}\" "
174 cmd << " --match \"hash #{hash}\" "
168 paths = []
175 paths = []
169 shellout(cmd) do |io|
176 shellout(cmd) do |io|
170 begin
177 begin
171 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
178 # Darcs xml output has multiple root elements in this case (tested with darcs 1.0.7)
172 # A root element is added so that REXML doesn't raise an error
179 # A root element is added so that REXML doesn't raise an error
173 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
180 doc = REXML::Document.new("<fake_root>" + io.read + "</fake_root>")
174 doc.elements.each('fake_root/summary/*') do |modif|
181 doc.elements.each('fake_root/summary/*') do |modif|
175 paths << {:action => modif.name[0,1].upcase,
182 paths << {:action => modif.name[0,1].upcase,
176 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
183 :path => "/" + modif.text.chomp.gsub(/^\s*/, '')
177 }
184 }
178 end
185 end
179 rescue
186 rescue
180 end
187 end
181 end
188 end
182 paths
189 paths
183 rescue CommandFailed
190 rescue CommandFailed
184 paths
191 paths
185 end
192 end
186 end
193 end
187 end
194 end
188 end
195 end
189 end
196 end
@@ -1,62 +1,68
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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class RepositoryDarcsTest < Test::Unit::TestCase
20 class RepositoryDarcsTest < Test::Unit::TestCase
21 fixtures :projects
21 fixtures :projects
22
22
23 # No '..' in the repository path
23 # No '..' in the repository path
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository'
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository'
25
25
26 def setup
26 def setup
27 @project = Project.find(1)
27 @project = Project.find(1)
28 assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH)
28 assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH)
29 end
29 end
30
30
31 if File.directory?(REPOSITORY_PATH)
31 if File.directory?(REPOSITORY_PATH)
32 def test_fetch_changesets_from_scratch
32 def test_fetch_changesets_from_scratch
33 @repository.fetch_changesets
33 @repository.fetch_changesets
34 @repository.reload
34 @repository.reload
35
35
36 assert_equal 6, @repository.changesets.count
36 assert_equal 6, @repository.changesets.count
37 assert_equal 13, @repository.changes.count
37 assert_equal 13, @repository.changes.count
38 assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments
38 assert_equal "Initial commit.", @repository.changesets.find_by_revision('1').comments
39 end
39 end
40
40
41 def test_fetch_changesets_incremental
41 def test_fetch_changesets_incremental
42 @repository.fetch_changesets
42 @repository.fetch_changesets
43 # Remove changesets with revision > 3
43 # Remove changesets with revision > 3
44 @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3}
44 @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 3}
45 @repository.reload
45 @repository.reload
46 assert_equal 3, @repository.changesets.count
46 assert_equal 3, @repository.changesets.count
47
47
48 @repository.fetch_changesets
48 @repository.fetch_changesets
49 assert_equal 6, @repository.changesets.count
49 assert_equal 6, @repository.changesets.count
50 end
50 end
51
51
52 def test_deleted_files_should_not_be_listed
53 entries = @repository.entries('sources')
54 assert entries.detect {|e| e.name == 'watchers_controller.rb'}
55 assert_nil entries.detect {|e| e.name == 'welcome_controller.rb'}
56 end
57
52 def test_cat
58 def test_cat
53 @repository.fetch_changesets
59 @repository.fetch_changesets
54 cat = @repository.cat("sources/welcome_controller.rb", 2)
60 cat = @repository.cat("sources/welcome_controller.rb", 2)
55 assert_not_nil cat
61 assert_not_nil cat
56 assert cat.include?('class WelcomeController < ApplicationController')
62 assert cat.include?('class WelcomeController < ApplicationController')
57 end
63 end
58 else
64 else
59 puts "Darcs test repository NOT FOUND. Skipping unit tests !!!"
65 puts "Darcs test repository NOT FOUND. Skipping unit tests !!!"
60 def test_fake; assert true end
66 def test_fake; assert true end
61 end
67 end
62 end
68 end
General Comments 0
You need to be logged in to leave comments. Login now