Patch #9359 » invert_project_repository_relationship_2.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/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', |