Project

General

Profile

Patch #9359 » invert_project_repository_relationship_2-1.3-stable-9308.patch

Robert Rath, 2012-04-03 17:15

View differences:

test/unit/user_test.rb (working copy)
314 314
  def test_destroy_should_nullify_changesets
315 315
    changeset = Changeset.create!(
316 316
      :repository => Repository::Subversion.create!(
317
        :project_id => 1,
318 317
        :url => 'file:///var/svn'
319 318
      ),
320 319
      :revision => '12',
test/unit/project_test.rb (working copy)
59 59
    should_have_many :boards
60 60
    should_have_many :changesets, :through => :repository
61 61

  
62
    should_have_one :repository
62
    should_belong_to :repository
63

  
63 64
    should_have_one :wiki
64 65

  
65 66
    should_have_and_belong_to_many :trackers
test/functional/sys_controller_test.rb (working copy)
54 54
    assert_response :created
55 55
    assert_equal 'application/xml', @response.content_type
56 56

  
57
    r = Project.find(4).repository
58
    assert r.is_a?(Repository::Subversion)
57
    assert r = Project.find(4).repository
58
    assert r.is_a?(Repository::Subversion), r.inspect
59 59
    assert_equal 'file:///create/project/repository/subproject2', r.url
60 60
    
61 61
    assert_tag 'repository-subversion',
......
67 67
    assert_no_tag 'extra_info'
68 68
  end
69 69

  
70
  def test_should_reuse_existing_repository
71
    assert_nil Project.find(4).repository
72
    assert_nil Project.find(5).repository
73

  
74
    assert_difference "Repository.count", +1 do
75
      post :create_project_repository, :id => 4,
76
                                       :vendor => 'Subversion',
77
                                       :repository => { :url => 'file:///create/project/repository/subproject2'}
78

  
79
    end
80
    assert_response :created
81
    assert r = Project.find(4).repository
82
    assert r.is_a?(Repository::Subversion), r.inspect
83
    assert_equal 'file:///create/project/repository/subproject2', r.url
84

  
85
    assert_no_difference "Repository.count" do
86
      post :create_project_repository, :id => 5,
87
                                       :vendor => 'Subversion',
88
                                       :repository => { :url => 'file:///create/project/repository/subproject2'}
89
    end
90
    assert_response :created
91
    assert_equal r, Project.find(5).repository
92
  end
93

  
70 94
  def test_fetch_changesets
71 95
    Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true)
72 96
    get :fetch_changesets
test/fixtures/repositories.yml (working copy)
1 1
--- 
2 2
repositories_001: 
3
  project_id: 1
4 3
  url: file:///<%= Rails.root %>/tmp/test/subversion_repository
5 4
  id: 10
6 5
  root_url: file:///<%= Rails.root %>/tmp/test/subversion_repository
......
8 7
  login: ""
9 8
  type: Subversion
10 9
repositories_002: 
11
  project_id: 2
12 10
  url: svn://localhost/test
13 11
  id: 11
14 12
  root_url: svn://localhost
test/fixtures/projects.yml (working copy)
11 11
  parent_id: 
12 12
  lft: 1
13 13
  rgt: 10
14
  repository_id: 10
14 15
projects_002: 
15 16
  created_on: 2006-07-19 19:14:19 +02:00
16 17
  name: OnlineStore
......
23 24
  parent_id: 
24 25
  lft: 11
25 26
  rgt: 12
27
  repository_id: 11
26 28
projects_003: 
27 29
  created_on: 2006-07-19 19:15:21 +02:00
28 30
  name: eCookbook Subproject 1
app/helpers/application_helper.rb (working copy)
721 721
                                            :class => 'news'
722 722
            end
723 723
          when 'commit'
724
            if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
724
            if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["#{Changeset.table_name}.repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
725 725
              link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
726 726
                                           :class => 'changeset',
727 727
                                           :title => truncate_single_line(h(changeset.comments), :length => 100)
app/models/repository.rb (working copy)
20 20
class Repository < ActiveRecord::Base
21 21
  include Redmine::Ciphering
22 22

  
23
  belongs_to :project
23
  has_many :projects
24
  has_one  :project, :order => 'lft ASC'
25

  
24 26
  has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
25 27
  has_many :changes, :through => :changesets
26 28

  
......
249 251
  # Can be called periodically by an external script
250 252
  # eg. ruby script/runner "Repository.fetch_changesets"
251 253
  def self.fetch_changesets
252
    Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
253
      if project.repository
254
        begin
255
          project.repository.fetch_changesets
256
        rescue Redmine::Scm::Adapters::CommandFailed => e
257
          logger.error "scm: error during fetching changesets: #{e.message}"
258
        end
254
    Project.active.has_module(:repository).find(:all, :include => :repository).
255
                                           map(&:repository).
256
                                           uniq.
257
                                           compact.each do |repository|
258
      begin
259
        repository.fetch_changesets
260
      rescue Redmine::Scm::Adapters::CommandFailed => e
261
        logger.error "scm: error during fetching changesets: #{e.message}"
259 262
      end
260 263
    end
261 264
  end
......
280 283
    nil
281 284
  end
282 285

  
286
  def self.find_existing_or_new(klass_name, attributes = {})
287
    if repo = find_existing(klass_name, attributes)
288
      repo
289
    else
290
      factory klass_name, attributes
291
    end
292
  end
293

  
294
  def self.find_existing(klass_name, attributes = {})
295
    factory(klass_name).class.find_by_url(attributes[:url]) rescue nil
296
  end
297

  
283 298
  def self.scm_adapter_class
284 299
    nil
285 300
  end
app/models/changeset.rb (working copy)
34 34
  acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.format_identifier}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
35 35
                :description => :long_comments,
36 36
                :datetime => :committed_on,
37
                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.identifier}}
37
                :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.project, :rev => o.identifier}}
38 38

  
39 39
  acts_as_searchable :columns => 'comments',
40 40
                     :include => {:repository => :project},
41
                     :project_key => "#{Repository.table_name}.project_id",
41
                     :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...
42 42
                     :date_column => 'committed_on'
43 43

  
44 44
  acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
app/models/project.rb (working copy)
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/controllers/sys_controller.rb (working copy)
30 30
      render :nothing => true, :status => 409
31 31
    else
32 32
      logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
33
      project.repository = Repository.factory(params[:vendor], params[:repository])
34
      if project.repository && project.repository.save
35
        render :xml => project.repository.to_xml(:only => [:id, :url]), :status => 201
33
      repository = Repository.find_existing_or_new params[:vendor], params[:repository]
34
      repository.projects << project if repository && !repository.projects.include?(project)
35
      if repository.save
36
        render :xml => repository, :status => 201
36 37
      else
37 38
        render :nothing => true, :status => 422
38 39
      end
app/controllers/repositories_controller.rb (working copy)
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]
db/migrate/20110928215351_invert_repository_project_relationship.rb (revision 0)
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
(3-3/4)