Patch #9359 » invert_project_repository_relationship_2-1.3-stable-9308.patch
| 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 |
|