Patch #9359 » invert_project_repository_relationship.diff
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 |