diff -r eec8e3031fc2 app/helpers/repositories_helper.rb --- a/app/helpers/repositories_helper.rb Fri May 01 13:39:22 2009 +0200 +++ b/app/helpers/repositories_helper.rb Sun May 03 19:42:07 2009 +0200 @@ -154,6 +154,7 @@ :onfocus => "this.value=''; this.name='repository[password]';", :onchange => "this.name='repository[password]';")) end + alias subversion_libsvn_field_tags subversion_field_tags def darcs_field_tags(form, repository) content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) diff -r eec8e3031fc2 app/models/repository/subversion_libsvn.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/models/repository/subversion_libsvn.rb Sun May 03 19:42:07 2009 +0200 @@ -0,0 +1,30 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/subversion_adapter' + +class Repository::SubversionLibsvn < Repository::Subversion + + def scm_adapter + Redmine::Scm::Adapters::SubversionLibsvnAdapter + end + + def self.scm_name + 'Subversion (lib SVN)' + end + +end diff -r eec8e3031fc2 lib/redmine/scm/adapters/subversion_libsvn_adapter.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/redmine/scm/adapters/subversion_libsvn_adapter.rb Sun May 03 19:42:07 2009 +0200 @@ -0,0 +1,190 @@ +# redMine - project management software +# Copyright (C) 2006-2009 Jean-Philippe Lang +# +# Author : Pierre Paysant-Le Roux +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'redmine/scm/adapters/abstract_adapter' +require 'svn/client' + + +module Redmine + module Scm + module Adapters + class SubversionLibsvnAdapter < AbstractAdapter + + # SVN executable name + SVN_BIN = "svn" + + KIND = { Svn::Core::NODE_NONE => "none", + Svn::Core::NODE_DIR => "dir", + Svn::Core::NODE_FILE => "file", + Svn::Core::NODE_UNKNOWN => "unknown"} + + class << self + def client_version + Svn::Core.subr_version.to_a[0..2] + end + end + + def target(path = '') + base = path.match(/^\//) ? root_url : url + # Remove leading slash else libsvn crash + File.join(base, path).gsub(/\/\Z/,'') + end + + def ctx + if @ctx.nil? + @ctx = Svn::Client::Context.new + + @ctx.add_simple_prompt_provider(1) do |cred, realm, username, may_save| + cred.username = @login || "" + cred.password = @password || "" + cred.may_save = false + end + @ctx.add_username_prompt_provider(1) do |cred, realm, username, may_save| + cred.username = @login || "" + cred.may_save = false + end + + end + @ctx + end + + # Get info about the svn repository + def info + ctx.info(target()) do |p,i| + return Info.new({:root_url => i.repos_root_URL, #Nombre de slashs? + :lastrev => Revision.new({ + :identifier => i.last_changed_rev.to_s, + :time => i.last_changed_date, + :author => i.last_changed_author + }) + }) + end + end + + # Returns an Entries collection + # or nil if the given path doesn't exist in the repository + def entries(path=nil, identifier=nil) + path ||= '' + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + entries = Entries.new + ctx.list(target(path), identifier) do |path,dirent,lock,abs_path| + entries.push(Entry.new({:name => path, + :path => File.join(abs_path,path), # TODO : slash au début + :kind => KIND[dirent.kind], + :size => dirent.size, + :lastrev => + Revision.new({ :identifier => + dirent.created_rev.to_s, + :time => dirent.time2, + :author => + dirent.last_author + }) + })) unless path.blank? + + end + logger.debug("Found #{entries.size} entries in the repository for #{target(path)}") if logger && logger.debug? + entries.sort_by_name + rescue Svn::Error::FsNotFound + return nil + end + + def properties(path, identifier=nil) + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + properties = {} + ctx.proplist(target(path), identifier) do |path, prop_hash| + properties.merge!(prop_hash) + end + properties + end + + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + path ||= '' + identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : "HEAD" + identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : 1 + revisions = Revisions.new + + ctx.log(target(URI.escape(path)), identifier_from, + identifier_to, options[:limit] || 0, + options[:with_paths], + false) do |changed_paths, rev, author, date, message| + + paths = [] + changed_paths.each do |path, change| + paths << {:action => change.action, + :path => path, + :from_path => change.copyfrom_path, + :from_revision => change.copyfrom_rev + } + end unless changed_paths.nil? + paths.sort! { |x,y| x[:path] <=> y[:path] } + + revisions << Revision.new({:identifier => rev, + :author => author, + :time => date, + :message => message, + :paths => paths + }) + end + revisions + end + + + def diff(path, identifier_from, identifier_to=nil, type="inline") + path ||= '' + identifier_from = (identifier_from and identifier_from.to_i > 0) ? identifier_from.to_i : '' + identifier_to = (identifier_to and identifier_to.to_i > 0) ? identifier_to.to_i : (identifier_from.to_i - 1) + begin + out_file = Tempfile.new("redmine") + err_file = Tempfile.new("redmine") + ctx.diff_peg( [], target(URI.escape(path)), + identifier_from, + identifier_to, out_file.path, err_file.path, "HEAD") + out_file.rewind + diff = [] + out_file.each_line do |line| + diff << line + end + return diff + ensure + out_file.unlink + err_file.unlink + end + end + + def cat(path, identifier=nil) + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + ctx.cat(target(URI.escape(path)), identifier) + end + + def annotate(path, identifier=nil) + identifier = (identifier and identifier.to_i > 0) ? identifier.to_i : "HEAD" + blame = Annotate.new + ctx.blame(target(URI.escape(path)), + nil, identifier) do |line_no, revision, author, date, line| + blame.add_line(line, Revision.new(:identifier => revision.to_i, + :author => author, + :time => date)) + end + blame + end + + end + end + end +end diff -r eec8e3031fc2 test/functional/repositories_subversion_controller_test.rb --- a/test/functional/repositories_subversion_controller_test.rb Fri May 01 13:39:22 2009 +0200 +++ b/test/functional/repositories_subversion_controller_test.rb Sun May 03 19:42:07 2009 +0200 @@ -192,3 +192,4 @@ def test_fake; assert true end end end + diff -r eec8e3031fc2 test/functional/repositories_subversion_libsvn_controller_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/functional/repositories_subversion_libsvn_controller_test.rb Sun May 03 19:42:07 2009 +0200 @@ -0,0 +1,32 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' +require File.join(File.dirname(__FILE__), 'repositories_subversion_controller_test') +require 'repositories_controller' + +# Re-raise errors caught by the controller. +class RepositoriesController; def rescue_action(e) raise e end; end + +class RepositoriesSubversionLibsvnControllerTest < RepositoriesSubversionControllerTest + def setup + super + Repository::SubversionLibsvn.create(:project => Project.find(3), :url => "file://" + REPOSITORY_PATH, + :root_url => "file://" + REPOSITORY_PATH) + end +end + diff -r eec8e3031fc2 test/unit/repository_subversion_libsvn_test.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/unit/repository_subversion_libsvn_test.rb Sun May 03 19:42:07 2009 +0200 @@ -0,0 +1,95 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' + +class RepositorySubversionLibsvnTest < Test::Unit::TestCase + fixtures :projects + + # No '..' in the repository path for svn + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/subversion_repository' + + def setup + @project = Project.find(1) + assert @repository = Repository::SubversionLibsvn.create(:project => @project, :url => "file://#{REPOSITORY_PATH}") + assert @repository_old = Repository::Subversion.create(:project => @project, :url => "file://#{REPOSITORY_PATH}") + end + + if File.directory?(REPOSITORY_PATH) + + + def test_regression_info + assert_equal(@repository_old.scm.info.root_url, + @repository.scm.info.root_url) + [:author, :branch, :identifier, :message, :name, + :paths, :revision, :scmid, :time].each do |meth| + assert_equal(@repository_old.scm.info.lastrev.send(meth), + @repository.scm.info.lastrev.send(meth)) + end + + end + + def test_regression_entries + entries = @repository.entries + entries_old = @repository_old.entries + entries_old.each_index do |index| + assert_equal entries_old[index].kind, entries[index].kind + [:author, :branch, :identifier, :message, :name, + :paths, :revision, :scmid, :time].each do |meth| + assert_equal entries_old[index].lastrev.send(meth), entries[index].lastrev.send(meth) + end + end + + # assert_equal @repository_old.entries, @repository.entries + # assert_equal @repository_old.entries("subversion_test"), @repository.entries("subversion_test") + end + + def test_regressions_diff + assert_equal @repository_old.diff("subversion_test/helloworld.c", 5, 7), @repository.diff("subversion_test/helloworld.c", 5, 7) + end + + def test_fetch_changesets_from_scratch + @repository.fetch_changesets + @repository.reload + + assert_equal 8, @repository.changesets.count + assert_equal 16, @repository.changes.count + assert_equal 'Initial import.', @repository.changesets.find_by_revision('1').comments + end + + def test_fetch_changesets_incremental + @repository.fetch_changesets + # Remove changesets with revision > 5 + @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 5} + @repository.reload + assert_equal 5, @repository.changesets.count + + @repository.fetch_changesets + assert_equal 8, @repository.changesets.count + end + + def test_changesets_for_path_with_limit + @repository.fetch_changesets + changesets = @repository.changesets_for_path('', :limit => 2) + assert_equal 2, changesets.size + assert_equal @repository.changesets_for_path('').slice(0,2), changesets + end + else + puts "Subversion test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end