Project

General

Profile

Patch #9359 » invert_project_repository_relationship_2.diff

updated patch, all tests passing - Jens Krämer, 2011-09-30 18:27

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/controllers/sys_controller.rb
29 29
      render :nothing => true, :status => 409
30 30
    else
31 31
      logger.info "Repository for #{project.name} was reported to be created by #{request.remote_ip}."
32
      project.repository = Repository.factory(params[:vendor], params[:repository])
33
      if project.repository && project.repository.save
34
        render :xml => project.repository, :status => 201
32

  
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
35 37
      else
36 38
        render :nothing => true, :status => 422
37 39
      end
app/helpers/application_helper.rb
680 680
                                              :class => 'version'
681 681
            end
682 682
          when 'commit'
683
            if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
683
            if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["#{Changeset.table_name}.repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
684 684
              link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
685 685
                                           :class => 'changeset',
686 686
                                           :title => truncate_single_line(h(changeset.comments), :length => 100)
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},
33
                     :project_key => "#{Repository.table_name}.project_id",
33
                     :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...
34 34
                     :date_column => 'committed_on'
35 35

  
36 36
  acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
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
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

  
......
240 242
    encoding = log_encoding.to_s.strip
241 243
    encoding.blank? ? 'UTF-8' : encoding
242 244
  end
243

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

  
279 294
  def self.scm_adapter_class
280 295
    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
test/fixtures/projects.yml
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
test/fixtures/repositories.yml
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/functional/sys_controller_test.rb
50 50
                                     :repository => { :url => 'file:///create/project/repository/subproject2'}
51 51
    assert_response :created
52 52

  
53
    r = Project.find(4).repository
54
    assert r.is_a?(Repository::Subversion)
53
    assert r = Project.find(4).repository
54
    assert r.is_a?(Repository::Subversion), r.inspect
55 55
    assert_equal 'file:///create/project/repository/subproject2', r.url
56 56
  end
57 57

  
58

  
59
  def test_should_reuse_existing_repository
60
    assert_nil Project.find(4).repository
61
    assert_nil Project.find(5).repository
62

  
63
    assert_difference "Repository.count", +1 do
64
      post :create_project_repository, :id => 4,
65
                                       :vendor => 'Subversion',
66
                                       :repository => { :url => 'file:///create/project/repository/subproject2'}
67

  
68
    end
69
    assert_response :created
70
    assert r = Project.find(4).repository
71
    assert r.is_a?(Repository::Subversion), r.inspect
72
    assert_equal 'file:///create/project/repository/subproject2', r.url
73

  
74
    assert_no_difference "Repository.count" do
75
      post :create_project_repository, :id => 5,
76
                                       :vendor => 'Subversion',
77
                                       :repository => { :url => 'file:///create/project/repository/subproject2'}
78
    end
79
    assert_response :created
80
    assert_equal r, Project.find(5).repository
81
  end
82

  
58 83
  def test_fetch_changesets
59 84
    Repository::Subversion.any_instance.expects(:fetch_changesets).returns(true)
60 85
    get :fetch_changesets
test/unit/project_test.rb
48 48
    should_have_many :boards
49 49
    should_have_many :changesets, :through => :repository
50 50

  
51
    should_have_one :repository
51
    should_belong_to :repository
52
    
52 53
    should_have_one :wiki
53 54

  
54 55
    should_have_and_belong_to_many :trackers
test/unit/user_test.rb
306 306
  def test_destroy_should_nullify_changesets
307 307
    changeset = Changeset.create!(
308 308
      :repository => Repository::Subversion.create!(
309
        :project_id => 1,
310 309
        :url => 'file:///var/svn'
311 310
      ),
312 311
      :revision => '12',
(2-2/3)