Index: test/unit/user_test.rb
===================================================================
--- test/unit/user_test.rb	(revision 9308)
+++ test/unit/user_test.rb	(working copy)
@@ -314,7 +314,6 @@
   def test_destroy_should_nullify_changesets
     changeset = Changeset.create!(
       :repository => Repository::Subversion.create!(
-        :project_id => 1,
         :url => 'file:///var/svn'
       ),
       :revision => '12',
Index: test/unit/project_test.rb
===================================================================
--- test/unit/project_test.rb	(revision 9308)
+++ test/unit/project_test.rb	(working copy)
@@ -59,7 +59,8 @@
     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
Index: test/functional/sys_controller_test.rb
===================================================================
--- test/functional/sys_controller_test.rb	(revision 9308)
+++ test/functional/sys_controller_test.rb	(working copy)
@@ -54,8 +54,8 @@
     assert_response :created
     assert_equal 'application/xml', @response.content_type
 
-    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
     
     assert_tag 'repository-subversion',
@@ -67,6 +67,30 @@
     assert_no_tag 'extra_info'
   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
Index: test/fixtures/repositories.yml
===================================================================
--- test/fixtures/repositories.yml	(revision 9308)
+++ test/fixtures/repositories.yml	(working copy)
@@ -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 @@
   login: ""
   type: Subversion
 repositories_002: 
-  project_id: 2
   url: svn://localhost/test
   id: 11
   root_url: svn://localhost
Index: test/fixtures/projects.yml
===================================================================
--- test/fixtures/projects.yml	(revision 9308)
+++ test/fixtures/projects.yml	(working copy)
@@ -11,6 +11,7 @@
   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 @@
   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
Index: test/fixtures/files/060719210727_archive.zip
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: app/helpers/application_helper.rb
===================================================================
--- app/helpers/application_helper.rb	(revision 9308)
+++ app/helpers/application_helper.rb	(working copy)
@@ -721,7 +721,7 @@
                                             :class => 'news'
             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)
Index: app/models/repository.rb
===================================================================
--- app/models/repository.rb	(revision 9308)
+++ app/models/repository.rb	(working copy)
@@ -20,7 +20,9 @@
 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
 
@@ -249,13 +251,14 @@
   # 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
@@ -280,6 +283,18 @@
     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
   end
Index: app/models/changeset.rb
===================================================================
--- app/models/changeset.rb	(revision 9308)
+++ app/models/changeset.rb	(working copy)
@@ -34,11 +34,11 @@
   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",
Index: app/models/project.rb
===================================================================
--- app/models/project.rb	(revision 9308)
+++ app/models/project.rb	(working copy)
@@ -46,7 +46,7 @@
   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 @@
   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 @@
     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
Index: app/controllers/sys_controller.rb
===================================================================
--- app/controllers/sys_controller.rb	(revision 9308)
+++ app/controllers/sys_controller.rb	(working copy)
@@ -30,9 +30,10 @@
       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.to_xml(:only => [:id, :url]), :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
Index: app/controllers/repositories_controller.rb
===================================================================
--- app/controllers/repositories_controller.rb	(revision 9308)
+++ app/controllers/repositories_controller.rb	(working copy)
@@ -37,8 +37,8 @@
   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]
Index: db/migrate/20110928215351_invert_repository_project_relationship.rb
===================================================================
--- db/migrate/20110928215351_invert_repository_project_relationship.rb	(revision 0)
+++ db/migrate/20110928215351_invert_repository_project_relationship.rb	(revision 0)
@@ -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

Property changes on: db/migrate/20110928215351_invert_repository_project_relationship.rb
___________________________________________________________________
Added: svn:executable
   + *

