2012-11-19 13:24:05 -05:00
# == Schema Information
#
# Table name: projects
#
# id :integer not null, primary key
# name :string(255)
# path :string(255)
# description :text
2013-08-21 05:34:02 -04:00
# created_at :datetime not null
# updated_at :datetime not null
2013-01-03 14:09:18 -05:00
# creator_id :integer
2012-11-19 13:24:05 -05:00
# issues_enabled :boolean default(TRUE), not null
# wall_enabled :boolean default(TRUE), not null
# merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null
2012-11-24 15:16:51 -05:00
# namespace_id :integer
2013-03-15 09:16:02 -04:00
# issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255)
2013-03-27 12:26:37 -04:00
# snippets_enabled :boolean default(TRUE), not null
2013-04-04 15:11:51 -04:00
# last_activity_at :datetime
2013-06-19 08:40:33 -04:00
# imported :boolean default(FALSE), not null
2013-08-21 05:34:02 -04:00
# import_url :string(255)
2013-11-06 10:13:21 -05:00
# visibility_level :integer default(0), not null
2012-11-19 13:24:05 -05:00
#
2011-10-08 17:36:38 -04:00
class Project < ActiveRecord :: Base
2013-03-21 15:01:14 -04:00
include Gitlab :: ShellAdapter
2013-11-06 10:13:21 -05:00
include Gitlab :: VisibilityLevel
2013-01-23 09:13:28 -05:00
extend Enumerize
2013-11-27 03:49:59 -05:00
2013-10-02 09:18:28 -04:00
ActsAsTaggableOn . strict_case_match = true
2012-06-07 08:44:57 -04:00
2013-11-06 11:45:39 -05:00
attr_accessible :name , :path , :description , :issues_tracker , :label_list ,
2013-03-18 17:33:41 -04:00
:issues_enabled , :wall_enabled , :merge_requests_enabled , :snippets_enabled , :issues_tracker_id ,
2013-11-06 10:13:21 -05:00
:wiki_enabled , :visibility_level , :import_url , :last_activity_at , as : [ :default , :admin ]
2012-11-22 23:31:09 -05:00
2013-01-16 09:54:01 -05:00
attr_accessible :namespace_id , :creator_id , as : :admin
2012-11-22 23:31:09 -05:00
2013-05-07 12:26:41 -04:00
acts_as_taggable_on :labels , :issues_default_labels
2013-04-16 05:45:45 -04:00
2013-11-06 11:45:39 -05:00
attr_accessor :new_default_branch
2012-06-07 08:44:57 -04:00
# Relations
2013-01-19 12:06:50 -05:00
belongs_to :creator , foreign_key : " creator_id " , class_name : " User "
belongs_to :group , foreign_key : " namespace_id " , conditions : " type = 'Group' "
2012-11-22 13:34:16 -05:00
belongs_to :namespace
2012-12-05 10:06:15 -05:00
2012-10-17 15:02:52 -04:00
has_one :last_event , class_name : 'Event' , order : 'events.created_at DESC' , foreign_key : 'project_id'
2012-11-19 14:34:05 -05:00
has_one :gitlab_ci_service , dependent : :destroy
2013-05-22 10:59:59 -04:00
has_one :campfire_service , dependent : :destroy
2013-09-24 18:56:25 -04:00
has_one :pivotaltracker_service , dependent : :destroy
2013-05-23 14:10:32 -04:00
has_one :hipchat_service , dependent : :destroy
2013-08-19 05:11:36 -04:00
has_one :flowdock_service , dependent : :destroy
2013-11-21 07:18:02 -05:00
has_one :assembla_service , dependent : :destroy
2013-03-19 11:37:50 -04:00
has_one :forked_project_link , dependent : :destroy , foreign_key : " forked_to_project_id "
has_one :forked_from_project , through : :forked_project_link
2011-11-04 03:42:36 -04:00
2013-05-22 09:58:44 -04:00
has_many :services , dependent : :destroy
2013-01-19 12:06:50 -05:00
has_many :events , dependent : :destroy
2013-04-25 10:15:33 -04:00
has_many :merge_requests , dependent : :destroy , foreign_key : " target_project_id "
2013-10-08 08:45:57 -04:00
has_many :fork_merge_requests , dependent : :destroy , foreign_key : " source_project_id " , class_name : MergeRequest
2013-02-28 10:46:28 -05:00
has_many :issues , dependent : :destroy , order : " state DESC, created_at DESC "
2013-01-19 12:06:50 -05:00
has_many :milestones , dependent : :destroy
has_many :notes , dependent : :destroy
2013-03-24 14:31:14 -04:00
has_many :snippets , dependent : :destroy , class_name : " ProjectSnippet "
2013-01-19 12:06:50 -05:00
has_many :hooks , dependent : :destroy , class_name : " ProjectHook "
has_many :protected_branches , dependent : :destroy
2013-06-19 12:54:10 -04:00
has_many :users_projects , dependent : :destroy
has_many :users , through : :users_projects
2013-05-06 08:10:55 -04:00
has_many :deploy_keys_projects , dependent : :destroy
has_many :deploy_keys , through : :deploy_keys_projects
2012-10-02 12:01:40 -04:00
delegate :name , to : :owner , allow_nil : true , prefix : true
2013-06-21 17:06:21 -04:00
delegate :members , to : :team , prefix : true
2012-10-02 12:01:40 -04:00
2012-10-08 20:10:04 -04:00
# Validations
2013-01-02 12:00:00 -05:00
validates :creator , presence : true
2012-10-08 20:10:04 -04:00
validates :description , length : { within : 0 .. 2000 }
2012-12-25 17:50:41 -05:00
validates :name , presence : true , length : { within : 0 .. 255 } ,
format : { with : Gitlab :: Regex . project_name_regex ,
2013-08-13 05:24:10 -04:00
message : " only letters, digits, spaces & '_' '-' '.' allowed. Letter or digit should be first " }
2012-11-23 13:11:09 -05:00
validates :path , presence : true , length : { within : 0 .. 255 } ,
2013-06-12 15:11:35 -04:00
exclusion : { in : Gitlab :: Blacklist . path } ,
2012-11-27 22:14:05 -05:00
format : { with : Gitlab :: Regex . path_regex ,
2013-08-13 05:24:10 -04:00
message : " only letters, digits & '_' '-' '.' allowed. Letter or digit should be first " }
2012-10-08 20:10:04 -04:00
validates :issues_enabled , :wall_enabled , :merge_requests_enabled ,
:wiki_enabled , inclusion : { in : [ true , false ] }
2013-02-11 09:17:43 -05:00
validates :issues_tracker_id , length : { within : 0 .. 255 }
2012-11-23 01:11:09 -05:00
2013-09-17 09:12:10 -04:00
validates :namespace , presence : true
2012-11-23 13:11:09 -05:00
validates_uniqueness_of :name , scope : :namespace_id
validates_uniqueness_of :path , scope : :namespace_id
2013-02-11 16:13:21 -05:00
validates :import_url ,
2013-06-20 04:23:40 -04:00
format : { with : URI :: regexp ( %w( git http https ) ) , message : " should be a valid url " } ,
2013-02-11 16:13:21 -05:00
if : :import?
2013-02-11 16:00:12 -05:00
2013-08-13 05:04:11 -04:00
validate :check_limit , on : :create
2012-10-08 20:10:04 -04:00
2012-06-07 08:44:57 -04:00
# Scopes
2013-04-02 23:06:18 -04:00
scope :without_user , - > ( user ) { where ( " projects.id NOT IN (:ids) " , ids : user . authorized_projects . map ( & :id ) ) }
scope :without_team , - > ( team ) { team . projects . present? ? where ( " projects.id NOT IN (:ids) " , ids : team . projects . map ( & :id ) ) : scoped }
scope :not_in_group , - > ( group ) { where ( " projects.id NOT IN (:ids) " , ids : group . project_ids ) }
scope :in_team , - > ( team ) { where ( " projects.id IN (:ids) " , ids : team . projects . map ( & :id ) ) }
2012-12-26 05:22:12 -05:00
scope :in_namespace , - > ( namespace ) { where ( namespace_id : namespace . id ) }
2013-04-02 18:28:12 -04:00
scope :in_group_namespace , - > { joins ( :group ) }
2013-06-22 06:39:34 -04:00
scope :sorted_by_activity , - > { reorder ( " projects.last_activity_at DESC " ) }
2012-11-29 22:14:05 -05:00
scope :personal , - > ( user ) { where ( namespace_id : user . namespace_id ) }
scope :joined , - > ( user ) { where ( " namespace_id != ? " , user . namespace_id ) }
2013-11-06 10:13:21 -05:00
scope :public_only , - > { where ( visibility_level : PUBLIC ) }
scope :public_or_internal_only , - > ( user ) { where ( " visibility_level IN (:levels) " , levels : user ? [ INTERNAL , PUBLIC ] : [ PUBLIC ] ) }
2011-10-08 17:36:38 -04:00
2013-04-02 22:05:00 -04:00
enumerize :issues_tracker , in : ( Gitlab . config . issues_tracker . keys ) . append ( :gitlab ) , default : :gitlab
2013-01-23 09:13:28 -05:00
2012-10-08 20:10:04 -04:00
class << self
2013-01-14 02:37:29 -05:00
def abandoned
2013-06-11 09:57:52 -04:00
where ( 'projects.last_activity_at < ?' , 6 . months . ago )
2013-01-14 02:37:29 -05:00
end
2013-01-14 02:44:27 -05:00
def with_push
2013-02-13 06:48:16 -05:00
includes ( :events ) . where ( 'events.action = ?' , Event :: PUSHED )
2013-01-14 02:44:27 -05:00
end
2012-10-08 20:10:04 -04:00
def active
joins ( :issues , :notes , :merge_requests ) . order ( " issues.created_at, notes.created_at, merge_requests.created_at DESC " )
end
2012-06-11 01:52:44 -04:00
2012-10-08 20:10:04 -04:00
def search query
2013-11-06 02:54:42 -05:00
joins ( :namespace ) . where ( " projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query " , query : " % #{ query } % " )
2012-10-08 20:10:04 -04:00
end
2012-06-11 01:52:44 -04:00
2012-11-24 15:00:30 -05:00
def find_with_namespace ( id )
if id . include? ( " / " )
id = id . split ( " / " )
2013-01-02 12:00:00 -05:00
namespace = Namespace . find_by_path ( id . first )
return nil unless namespace
where ( namespace_id : namespace . id ) . find_by_path ( id . second )
2012-11-24 15:00:30 -05:00
else
2012-12-13 11:42:15 -05:00
where ( path : id , namespace_id : nil ) . last
2012-11-24 15:00:30 -05:00
end
end
2013-11-27 04:51:46 -05:00
2013-11-06 10:13:21 -05:00
def visibility_levels
Gitlab :: VisibilityLevel . options
end
2012-07-05 14:59:37 -04:00
end
2013-01-03 14:09:18 -05:00
def team
2013-01-19 13:52:55 -05:00
@team || = ProjectTeam . new ( self )
2013-01-03 14:09:18 -05:00
end
def repository
2013-11-06 11:45:39 -05:00
@repository || = Repository . new ( path_with_namespace )
2013-01-03 14:09:18 -05:00
end
2012-07-05 14:59:37 -04:00
def saved?
2013-02-14 06:58:33 -05:00
id && persisted?
2012-06-11 01:52:44 -04:00
end
2013-02-11 16:13:21 -05:00
def import?
import_url . present?
end
2013-08-12 11:21:47 -04:00
def imported?
imported
end
2012-06-07 08:44:57 -04:00
def check_limit
2013-01-02 12:00:00 -05:00
unless creator . can_create_project?
2013-02-14 09:51:56 -05:00
errors [ :limit_reached ] << ( " Your own projects limit is #{ creator . projects_limit } ! Please contact administrator to increase it " )
2012-06-07 08:44:57 -04:00
end
rescue
2012-08-10 19:47:54 -04:00
errors [ :base ] << ( " Can't check your ability to create project " )
2011-10-08 17:36:38 -04:00
end
2012-06-07 08:44:57 -04:00
def to_param
2013-09-17 09:12:10 -04:00
namespace . path + " / " + path
2011-11-06 15:38:08 -05:00
end
2012-06-07 08:44:57 -04:00
def web_url
2012-12-14 19:16:25 -05:00
[ Gitlab . config . gitlab . url , path_with_namespace ] . join ( " / " )
2011-12-13 16:24:31 -05:00
end
2011-11-10 18:28:26 -05:00
def build_commit_note ( commit )
2012-12-18 13:02:00 -05:00
notes . new ( commit_id : commit . id , noteable_type : " Commit " )
2011-10-08 17:36:38 -04:00
end
2011-10-26 09:46:25 -04:00
2011-11-15 03:34:30 -05:00
def last_activity
2012-10-17 15:02:52 -04:00
last_event
2011-10-31 16:57:16 -04:00
end
def last_activity_date
2013-04-02 23:06:18 -04:00
last_activity_at || updated_at
2012-03-01 13:40:32 -05:00
end
2011-12-20 01:24:14 -05:00
2012-03-05 17:26:40 -05:00
def project_id
self . id
end
2012-10-09 13:39:06 -04:00
def issues_labels
2013-05-07 12:26:41 -04:00
@issues_labels || = ( issues_default_labels + issues . tags_on ( :labels ) ) . uniq . sort_by ( & :name )
2012-10-09 13:39:06 -04:00
end
2012-11-20 07:22:00 -05:00
2013-02-11 06:41:12 -05:00
def issue_exists? ( issue_id )
if used_default_issues_tracker?
2013-08-20 10:31:26 -04:00
self . issues . where ( iid : issue_id ) . first . present?
2013-02-11 06:41:12 -05:00
else
true
end
end
def used_default_issues_tracker?
self . issues_tracker == Project . issues_tracker . default_value
end
2013-02-11 09:17:43 -05:00
def can_have_issues_tracker_id?
self . issues_enabled && ! self . used_default_issues_tracker?
end
2013-05-22 09:58:44 -04:00
def build_missing_services
available_services_names . each do | service_name |
service = services . find { | service | service . to_param == service_name }
# If service is available but missing in db
# we should create an instance. Ex `create_gitlab_ci_service`
service = self . send :" create_ #{ service_name } _service " if service . nil?
end
end
def available_services_names
2013-11-21 07:18:02 -05:00
%w( gitlab_ci campfire hipchat pivotaltracker flowdock assembla )
2012-11-20 07:22:00 -05:00
end
2012-11-20 12:34:05 -05:00
def gitlab_ci?
gitlab_ci_service && gitlab_ci_service . active
end
2012-11-22 13:34:16 -05:00
2012-11-23 14:31:09 -05:00
# For compatibility with old code
def code
path
2012-11-22 15:34:06 -05:00
end
2012-11-24 05:37:30 -05:00
2012-11-21 00:24:05 -05:00
def items_for entity
case entity
when 'issue' then
issues
when 'merge_request' then
merge_requests
end
end
2012-12-20 15:16:51 -05:00
def send_move_instructions
2013-06-22 03:56:51 -04:00
team . members . each do | user |
Notify . delay . project_was_moved_email ( self . id , user . id )
2012-12-20 15:16:51 -05:00
end
end
2013-01-02 12:00:00 -05:00
def owner
2013-09-26 07:49:22 -04:00
if group
group
2013-01-02 12:00:00 -05:00
else
2013-09-26 07:49:22 -04:00
namespace . try ( :owner )
2013-01-02 12:00:00 -05:00
end
end
2013-01-02 16:35:11 -05:00
def team_member_by_name_or_email ( name = nil , email = nil )
user = users . where ( " name like ? or email like ? " , name , email ) . first
users_projects . where ( user : user ) if user
end
# Get Team Member record by user id
def team_member_by_id ( user_id )
users_projects . find_by_user_id ( user_id )
end
def name_with_namespace
@name_with_namespace || = begin
if namespace
namespace . human_name + " / " + name
else
name
end
end
end
def path_with_namespace
if namespace
namespace . path + '/' + path
else
path
end
end
2013-02-26 16:14:32 -05:00
def transfer ( new_namespace )
ProjectTransferService . new . transfer ( self , new_namespace )
2013-01-02 16:35:11 -05:00
end
def execute_hooks ( data )
2013-01-24 15:15:24 -05:00
hooks . each { | hook | hook . async_execute ( data ) }
2013-01-02 16:35:11 -05:00
end
def execute_services ( data )
services . each do | service |
# Call service hook only if it is active
service . execute ( data ) if service . active
end
end
def update_merge_requests ( oldrev , newrev , ref , user )
return true unless ref =~ / heads /
branch_name = ref . gsub ( " refs/heads/ " , " " )
2013-01-04 01:43:25 -05:00
c_ids = self . repository . commits_between ( oldrev , newrev ) . map ( & :id )
2013-01-02 16:35:11 -05:00
2013-10-08 08:45:57 -04:00
# Update code for merge requests into project between project branches
2013-02-20 08:37:20 -05:00
mrs = self . merge_requests . opened . by_branch ( branch_name ) . all
2013-10-08 08:45:57 -04:00
# Update code for merge requests between project and project fork
mrs += self . fork_merge_requests . opened . by_branch ( branch_name ) . all
2013-11-05 09:41:29 -05:00
2013-01-02 16:35:11 -05:00
mrs . each { | merge_request | merge_request . reload_code ; merge_request . mark_as_unchecked }
# Close merge requests
mrs = self . merge_requests . opened . where ( target_branch : branch_name ) . all
mrs = mrs . select ( & :last_commit ) . select { | mr | c_ids . include? ( mr . last_commit . id ) }
mrs . each { | merge_request | merge_request . merge! ( user . id ) }
true
end
def valid_repo?
2013-04-01 09:56:25 -04:00
repository . exists?
2013-01-02 16:35:11 -05:00
rescue
errors . add ( :path , " Invalid repository path " )
false
end
def empty_repo?
2013-04-01 09:56:25 -04:00
! repository . exists? || repository . empty?
2013-01-02 16:35:11 -05:00
end
2013-02-25 14:21:38 -05:00
def ensure_satellite_exists
self . satellite . create unless self . satellite . exists?
end
2013-01-02 16:35:11 -05:00
def satellite
@satellite || = Gitlab :: Satellite :: Satellite . new ( self )
end
def repo
2013-01-03 14:09:18 -05:00
repository . raw
2013-01-02 16:35:11 -05:00
end
def url_to_repo
2013-02-11 12:16:59 -05:00
gitlab_shell . url_to_repo ( path_with_namespace )
2013-01-02 16:35:11 -05:00
end
def namespace_dir
namespace . try ( :path ) || ''
end
def repo_exists?
2013-04-01 09:56:25 -04:00
@repo_exists || = repository . exists?
2013-01-02 16:35:11 -05:00
rescue
@repo_exists = false
end
def open_branches
2013-03-31 10:08:10 -04:00
all_branches = repository . branches
if protected_branches . present?
all_branches . reject! do | branch |
protected_branches_names . include? ( branch . name )
end
end
all_branches
end
def protected_branches_names
@protected_branches_names || = protected_branches . map ( & :name )
2013-01-02 16:35:11 -05:00
end
def root_ref? ( branch )
2013-01-03 14:09:18 -05:00
repository . root_ref == branch
2013-01-02 16:35:11 -05:00
end
def ssh_url_to_repo
url_to_repo
end
def http_url_to_repo
http_url = [ Gitlab . config . gitlab . url , " / " , path_with_namespace , " .git " ] . join ( '' )
end
# Check if current branch name is marked as protected in the system
def protected_branch? branch_name
2013-03-31 10:08:10 -04:00
protected_branches_names . include? ( branch_name )
2013-01-02 16:35:11 -05:00
end
2013-03-19 11:37:50 -04:00
def forked?
! ( forked_project_link . nil? || forked_project_link . forked_from_project . nil? )
end
2013-05-24 17:07:19 -04:00
2013-06-19 14:58:52 -04:00
def personal?
! group
end
2013-05-24 17:07:19 -04:00
def rename_repo
old_path_with_namespace = File . join ( namespace_dir , path_was )
new_path_with_namespace = File . join ( namespace_dir , path )
if gitlab_shell . mv_repository ( old_path_with_namespace , new_path_with_namespace )
# If repository moved successfully we need to remove old satellite
# and send update instructions to users.
# However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions
begin
2013-08-20 10:44:15 -04:00
gitlab_shell . mv_repository ( " #{ old_path_with_namespace } .wiki " , " #{ new_path_with_namespace } .wiki " )
2013-05-24 17:07:19 -04:00
gitlab_shell . rm_satellites ( old_path_with_namespace )
2013-08-22 07:49:57 -04:00
ensure_satellite_exists
2013-05-24 17:07:19 -04:00
send_move_instructions
2013-08-29 12:12:22 -04:00
reset_events_cache
2013-05-24 17:07:19 -04:00
rescue
2013-07-29 06:46:00 -04:00
# Returning false does not rollback after_* transaction but gives
2013-05-24 17:07:19 -04:00
# us information about failing some of tasks
false
end
else
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
raise Exception . new ( 'repository cannot be renamed' )
end
end
2013-08-29 12:12:22 -04:00
# Reset events cache related to this project
#
# Since we do cache @event we need to reset cache in special cases:
# * when project was moved
# * when project was renamed
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
# when the event is updated because the key changes.
def reset_events_cache
Event . where ( project_id : self . id ) .
order ( 'id DESC' ) . limit ( 100 ) .
update_all ( updated_at : Time . now )
end
2013-11-05 09:41:29 -05:00
def project_member ( user )
users_projects . where ( user_id : user ) . first
end
2013-11-06 11:45:39 -05:00
def default_branch
@default_branch || = repository . root_ref if repository . exists?
end
2013-11-27 03:49:59 -05:00
def reload_default_branch
@default_branch = nil
default_branch
end
2013-11-27 04:51:46 -05:00
2013-11-06 10:13:21 -05:00
def visibility_level_field
visibility_level
end
2011-10-08 17:36:38 -04:00
end