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/models/changeset.rb app/models/changeset.rb index d87dd26..6b85dbd 100644 --- app/models/changeset.rb +++ app/models/changeset.rb @@ -26,7 +26,7 @@ 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}, @@ -75,7 +75,7 @@ class Changeset < ActiveRecord::Base end def project - repository.project + repository.projects.first end def author 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..245ff46 100644 --- app/models/repository.rb +++ app/models/repository.rb @@ -20,7 +20,7 @@ class ScmFetchError < Exception; end class Repository < ActiveRecord::Base include Redmine::Ciphering - belongs_to :project + has_many :projects, :order => 'lft ASC' has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changes, :through => :changesets @@ -245,13 +245,14 @@ class Repository < ActiveRecord::Base # 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 +276,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 + 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