Project

General

Profile

Patch #9359 » invert_project_repository_relationship.diff

the patch against current HEAD - Jens Krämer, 2011-09-30 16:06

View differences:

app/controllers/repositories_controller.rb
37 37
  def edit
38 38
    @repository = @project.repository
39 39
    if !@repository && !params[:repository_scm].blank?
40
      @repository = Repository.factory(params[:repository_scm])
41
      @repository.project = @project if @repository
40
      @repository = Repository.find_existing_or_new params[:repository_scm], params[:repository]
41
      @repository.projects << @project if @repository && !@repository.projects.include?(@project)
42 42
    end
43 43
    if request.post? && @repository
44 44
      p1 = params[:repository]
app/models/changeset.rb
26 26
  acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 27
                :description => :long_comments,
28 28
                :datetime => :committed_on,
29
                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
29
                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.project, :rev => o.identifier}}
30 30

  
31 31
  acts_as_searchable :columns => 'comments',
32 32
                     :include => {:repository => :project},
......
75 75
  end
76 76

  
77 77
  def project
78
    repository.project
78
    repository.projects.first
79 79
  end
80 80

  
81 81
  def author
app/models/project.rb
46 46
  has_many :news, :dependent => :destroy, :include => :author
47 47
  has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
48 48
  has_many :boards, :dependent => :destroy, :order => "position ASC"
49
  has_one :repository, :dependent => :destroy
49
  belongs_to :repository
50 50
  has_many :changesets, :through => :repository
51 51
  has_one :wiki, :dependent => :destroy
52 52
  # Custom field for the project issues
......
80 80
  validates_exclusion_of :identifier, :in => %w( new )
81 81

  
82 82
  before_destroy :delete_all_members
83
  after_destroy :destroy_repository_if_orphaned
83 84

  
84 85
  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] } }
85 86
  named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
......
425 426
    connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
426 427
    Member.delete_all(['project_id = ?', id])
427 428
  end
429
  
430
  # destroys the repository after deletion of the project if it isn't attached to any other projects.
431
  def destroy_repository_if_orphaned
432
    if (repo = Repository.find_by_id(repository_id)) && repo.projects.blank?
433
      repo.destroy
434
    end
435
  end
428 436

  
429 437
  # Users/groups issues can be assigned to
430 438
  def assignable_users
app/models/repository.rb
20 20
class Repository < ActiveRecord::Base
21 21
  include Redmine::Ciphering
22 22

  
23
  belongs_to :project
23
  has_many :projects, :order => 'lft ASC'
24 24
  has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 25
  has_many :changes, :through => :changesets
26 26

  
......
245 245
  # Can be called periodically by an external script
246 246
  # eg. ruby script/runner "Repository.fetch_changesets"
247 247
  def self.fetch_changesets
248
    Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
249
      if project.repository
250
        begin
251
          project.repository.fetch_changesets
252
        rescue Redmine::Scm::Adapters::CommandFailed => e
253
          logger.error "scm: error during fetching changesets: #{e.message}"
254
        end
248
    Project.active.has_module(:repository).find(:all, :include => :repository).
249
                                           map(&:repository).
250
                                           uniq.
251
                                           compact.each do |repository|
252
      begin
253
        repository.fetch_changesets
254
      rescue Redmine::Scm::Adapters::CommandFailed => e
255
        logger.error "scm: error during fetching changesets: #{e.message}"
255 256
      end
256 257
    end
257 258
  end
......
275 276
  rescue
276 277
    nil
277 278
  end
279
  
280
  def self.find_existing_or_new(klass_name, attributes = {})
281
    if repo = find_existing(klass_name, attributes)
282
      repo
283
    else
284
      factory klass_name
285
    end
286
  end
287
  
288
  def self.find_existing(klass_name, attributes = {})
289
    factory(klass_name).class.find_by_url(attributes[:url]) rescue nil
290
  end
278 291

  
279 292
  def self.scm_adapter_class
280 293
    nil
db/migrate/20110928215351_invert_repository_project_relationship.rb
1
class InvertRepositoryProjectRelationship < ActiveRecord::Migration
2
  class OldRepository < ActiveRecord::Base
3
    set_table_name 'repositories'
4
    set_inheritance_column 'foo'
5
    belongs_to :project, :class_name => 'OldProject', :foreign_key => :project_id
6
    has_many :changesets, :dependent => :destroy, :foreign_key => :repository_id
7
  end
8
  class OldProject < ActiveRecord::Base
9
    set_table_name 'projects'
10
    has_one :repository, :class_name => 'OldRepository', :foreign_key => :project_id
11
  end
12
  class NewRepository < ActiveRecord::Base
13
    set_table_name 'repositories'
14
    set_inheritance_column 'foo'
15
    has_many :projects, :class_name => 'NewProject', :foreign_key => :repository_id
16
  end
17
  class NewProject < ActiveRecord::Base
18
    set_table_name 'projects'
19
    belongs_to :repository, :class_name => 'NewRepository', :foreign_key => :repository_id
20
  end
21
  
22
  
23
  def self.up
24
    add_column :projects, :repository_id, :integer
25
    
26
    # make sure multiple projects referencing the same physical repository also use the same repository record
27
    OldProject.find_each do |p|
28
      if old_repo = p.repository
29
        puts "migrating #{p.identifier}"
30
        if repo = OldRepository.find( :first,
31
                                      :conditions => { :url => p.repository.url,
32
                                                       :type => p.repository['type'] },
33
                                      :order => 'id ASC' )
34
          puts "setting repo to #{repo.id} (is: #{p.repository_id})"
35
          execute "update projects set repository_id = #{repo.id} where id = #{p.id}"
36
          unless old_repo.id == repo.id
37
            # remove the now orphaned repository
38
            old_repo.destroy
39
          end
40
        else
41
          puts "repository not found for project #{p.inspcet}"
42
        end        
43
      end
44
    end
45
    
46
    remove_column :repositories, :project_id
47
  end
48

  
49
  def self.down
50
    add_column :repositories, :project_id, :integer
51

  
52
    NewRepository.find_each do |repo|
53
      if repo.projects.size > 1
54
        repo.projects.each do |p|
55
          r = repo.class.new(repo.attributes)
56
          r.project_id = p.id
57
          r.save!
58
        end
59
        repo.destroy
60
      elsif p = repo.projects.first
61
        repo.update_attribute :project_id, p.id
62
      end
63
    end
64
    change_column :repositories, :project_id, :integer, :null => false
65
    remove_column :projects, :repository_id
66
  end
67
end
(1-1/3)