diff --git app/controllers/repositories_controller.rb app/controllers/repositories_controller.rb index 119ac82..cbfe712 100644 --- app/controllers/repositories_controller.rb +++ app/controllers/repositories_controller.rb @@ -37,8 +37,8 @@ class RepositoriesController < ApplicationController def edit @repository = @project.repository if !@repository && !params[:repository_scm].blank? - @repository = Repository.factory(params[:repository_scm]) - @repository.project = @project if @repository + @repository = Repository.find_existing_or_new params[:repository_scm], params[:repository] + @repository.projects << @project if @repository && !@repository.projects.include?(@project) end if request.post? && @repository p1 = params[:repository] diff --git app/controllers/sys_controller.rb app/controllers/sys_controller.rb index 9a8c22f..f302990 100644 --- app/controllers/sys_controller.rb +++ app/controllers/sys_controller.rb @@ -29,9 +29,11 @@ class SysController < ActionController::Base render :nothing => true, :status => 409 else logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}." - project.repository = Repository.factory(params[:vendor], params[:repository]) - if project.repository && project.repository.save - render :xml => project.repository, :status => 201 + + repository = Repository.find_existing_or_new params[:vendor], params[:repository] + repository.projects << project if repository && !repository.projects.include?(project) + if repository.save + render :xml => repository, :status => 201 else render :nothing => true, :status => 422 end diff --git app/helpers/application_helper.rb app/helpers/application_helper.rb index c3706ff..3984671 100644 --- app/helpers/application_helper.rb +++ app/helpers/application_helper.rb @@ -680,7 +680,7 @@ module ApplicationHelper :class => 'version' end when 'commit' - if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) + if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["#{Changeset.table_name}.repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"])) link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier}, :class => 'changeset', :title => truncate_single_line(h(changeset.comments), :length => 100) diff --git app/models/changeset.rb app/models/changeset.rb index d87dd26..39e82c5 100644 --- app/models/changeset.rb +++ app/models/changeset.rb @@ -26,11 +26,11 @@ class Changeset < ActiveRecord::Base acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))}, :description => :long_comments, :datetime => :committed_on, - :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}} + :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.project, :rev => o.identifier}} acts_as_searchable :columns => 'comments', :include => {:repository => :project}, - :project_key => "#{Repository.table_name}.project_id", + :project_key => "#{Project.table_name}.id", # this will only include a matching commit in results if the user has access to the first project of the repository. hard to fix... :date_column => 'committed_on' acts_as_activity_provider :timestamp => "#{table_name}.committed_on", diff --git app/models/project.rb app/models/project.rb index a2626f1..43ad616 100644 --- app/models/project.rb +++ app/models/project.rb @@ -46,7 +46,7 @@ class Project < ActiveRecord::Base has_many :news, :dependent => :destroy, :include => :author has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" has_many :boards, :dependent => :destroy, :order => "position ASC" - has_one :repository, :dependent => :destroy + belongs_to :repository has_many :changesets, :through => :repository has_one :wiki, :dependent => :destroy # Custom field for the project issues @@ -80,6 +80,7 @@ class Project < ActiveRecord::Base validates_exclusion_of :identifier, :in => %w( new ) before_destroy :delete_all_members + after_destroy :destroy_repository_if_orphaned named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } } named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"} @@ -425,6 +426,13 @@ class Project < ActiveRecord::Base connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})") Member.delete_all(['project_id = ?', id]) end + + # destroys the repository after deletion of the project if it isn't attached to any other projects. + def destroy_repository_if_orphaned + if (repo = Repository.find_by_id(repository_id)) && repo.projects.blank? + repo.destroy + end + end # Users/groups issues can be assigned to def assignable_users diff --git app/models/repository.rb app/models/repository.rb index 15bfee6..48a49a3 100644 --- app/models/repository.rb +++ app/models/repository.rb @@ -20,7 +20,9 @@ class ScmFetchError < Exception; end class Repository < ActiveRecord::Base include Redmine::Ciphering - belongs_to :project + has_many :projects + has_one :project, :order => 'lft ASC' + has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changes, :through => :changesets @@ -240,18 +242,19 @@ class Repository < ActiveRecord::Base encoding = log_encoding.to_s.strip encoding.blank? ? 'UTF-8' : encoding end - + # Fetches new changesets for all repositories of active projects # Can be called periodically by an external script # eg. ruby script/runner "Repository.fetch_changesets" def self.fetch_changesets - Project.active.has_module(:repository).find(:all, :include => :repository).each do |project| - if project.repository - begin - project.repository.fetch_changesets - rescue Redmine::Scm::Adapters::CommandFailed => e - logger.error "scm: error during fetching changesets: #{e.message}" - end + Project.active.has_module(:repository).find(:all, :include => :repository). + map(&:repository). + uniq. + compact.each do |repository| + begin + repository.fetch_changesets + rescue Redmine::Scm::Adapters::CommandFailed => e + logger.error "scm: error during fetching changesets: #{e.message}" end end end @@ -275,6 +278,18 @@ class Repository < ActiveRecord::Base rescue nil end + + def self.find_existing_or_new(klass_name, attributes = {}) + if repo = find_existing(klass_name, attributes) + repo + else + factory klass_name, attributes + end + end + + def self.find_existing(klass_name, attributes = {}) + factory(klass_name).class.find_by_url(attributes[:url]) rescue nil + end def self.scm_adapter_class nil diff --git db/migrate/20110928215351_invert_repository_project_relationship.rb db/migrate/20110928215351_invert_repository_project_relationship.rb new file mode 100644 index 0000000..19dd7bc --- /dev/null +++ db/migrate/20110928215351_invert_repository_project_relationship.rb @@ -0,0 +1,67 @@ +class InvertRepositoryProjectRelationship < ActiveRecord::Migration + class OldRepository < ActiveRecord::Base + set_table_name 'repositories' + set_inheritance_column 'foo' + belongs_to :project, :class_name => 'OldProject', :foreign_key => :project_id + has_many :changesets, :dependent => :destroy, :foreign_key => :repository_id + end + class OldProject < ActiveRecord::Base + set_table_name 'projects' + has_one :repository, :class_name => 'OldRepository', :foreign_key => :project_id + end + class NewRepository < ActiveRecord::Base + set_table_name 'repositories' + set_inheritance_column 'foo' + has_many :projects, :class_name => 'NewProject', :foreign_key => :repository_id + end + class NewProject < ActiveRecord::Base + set_table_name 'projects' + belongs_to :repository, :class_name => 'NewRepository', :foreign_key => :repository_id + end + + + def self.up + add_column :projects, :repository_id, :integer + + # make sure multiple projects referencing the same physical repository also use the same repository record + OldProject.find_each do |p| + if old_repo = p.repository + puts "migrating #{p.identifier}" + if repo = OldRepository.find( :first, + :conditions => { :url => p.repository.url, + :type => p.repository['type'] }, + :order => 'id ASC' ) + puts "setting repo to #{repo.id} (is: #{p.repository_id})" + execute "update projects set repository_id = #{repo.id} where id = #{p.id}" + unless old_repo.id == repo.id + # remove the now orphaned repository + old_repo.destroy + end + else + puts "repository not found for project #{p.inspcet}" + end + end + end + + remove_column :repositories, :project_id + end + + def self.down + add_column :repositories, :project_id, :integer + + NewRepository.find_each do |repo| + if repo.projects.size > 1 + repo.projects.each do |p| + r = repo.class.new(repo.attributes) + r.project_id = p.id + r.save! + end + repo.destroy + elsif p = repo.projects.first + repo.update_attribute :project_id, p.id + end + end + change_column :repositories, :project_id, :integer, :null => false + remove_column :projects, :repository_id + end +end \ No newline at end of file diff --git test/fixtures/files/060719210727_archive.zip test/fixtures/files/060719210727_archive.zip deleted file mode 100644 index 5467885..0000000 Binary files test/fixtures/files/060719210727_archive.zip and /dev/null differ diff --git test/fixtures/projects.yml test/fixtures/projects.yml index 6ecc8ad..742c3b2 100644 --- test/fixtures/projects.yml +++ test/fixtures/projects.yml @@ -11,6 +11,7 @@ projects_001: parent_id: lft: 1 rgt: 10 + repository_id: 10 projects_002: created_on: 2006-07-19 19:14:19 +02:00 name: OnlineStore @@ -23,6 +24,7 @@ projects_002: parent_id: lft: 11 rgt: 12 + repository_id: 11 projects_003: created_on: 2006-07-19 19:15:21 +02:00 name: eCookbook Subproject 1 diff --git test/fixtures/repositories.yml test/fixtures/repositories.yml index 61930f3..8c75571 100644 --- test/fixtures/repositories.yml +++ test/fixtures/repositories.yml @@ -1,6 +1,5 @@ --- repositories_001: - project_id: 1 url: file:///<%= Rails.root %>/tmp/test/subversion_repository id: 10 root_url: file:///<%= Rails.root %>/tmp/test/subversion_repository @@ -8,7 +7,6 @@ repositories_001: login: "" type: Subversion repositories_002: - project_id: 2 url: svn://localhost/test id: 11 root_url: svn://localhost diff --git test/functional/sys_controller_test.rb test/functional/sys_controller_test.rb index c330573..7ebaf33 100644 --- test/functional/sys_controller_test.rb +++ test/functional/sys_controller_test.rb @@ -50,11 +50,36 @@ class SysControllerTest < ActionController::TestCase :repository => { :url => 'file:///create/project/repository/subproject2'} assert_response :created - r = Project.find(4).repository - assert r.is_a?(Repository::Subversion) + assert r = Project.find(4).repository + assert r.is_a?(Repository::Subversion), r.inspect assert_equal 'file:///create/project/repository/subproject2', r.url end + + def test_should_reuse_existing_repository + assert_nil Project.find(4).repository + assert_nil Project.find(5).repository + + assert_difference "Repository.count", +1 do + post :create_project_repository, :id => 4, + :vendor => 'Subversion', + :repository => { :url => 'file:///create/project/repository/subproject2'} + + end + assert_response :created + assert r = Project.find(4).repository + assert r.is_a?(Repository::Subversion), r.inspect + assert_equal 'file:///create/project/repository/subproject2', r.url + + assert_no_difference "Repository.count" do + post :create_project_repository, :id => 5, + :vendor => 'Subversion', + :repository => { :url => 'file:///create/project/repository/subproject2'} + end + assert_response :created + assert_equal r, Project.find(5).repository + end + def test_fetch_changesets Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true) get :fetch_changesets diff --git test/unit/project_test.rb test/unit/project_test.rb index e45111d..5d34562 100644 --- test/unit/project_test.rb +++ test/unit/project_test.rb @@ -48,7 +48,8 @@ class ProjectTest < ActiveSupport::TestCase should_have_many :boards should_have_many :changesets, :through => :repository - should_have_one :repository + should_belong_to :repository + should_have_one :wiki should_have_and_belong_to_many :trackers diff --git test/unit/user_test.rb test/unit/user_test.rb index 1d98224..0467ed3 100644 --- test/unit/user_test.rb +++ test/unit/user_test.rb @@ -306,7 +306,6 @@ class UserTest < ActiveSupport::TestCase def test_destroy_should_nullify_changesets changeset = Changeset.create!( :repository => Repository::Subversion.create!( - :project_id => 1, :url => 'file:///var/svn' ), :revision => '12',