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', |