project.rb 7.9 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# == Schema Information
#
# Table name: projects
#
#  id                     :integer          not null, primary key
#  name                   :string(255)
#  path                   :string(255)
#  description            :text
#  created_at             :datetime         not null
#  updated_at             :datetime         not null
#  private_flag           :boolean          default(TRUE), not null
#  owner_id               :integer
#  default_branch         :string(255)
#  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
D
Dmitriy Zaporozhets 已提交
18
#  namespace_id           :integer
D
Dmitriy Zaporozhets 已提交
19 20
#

G
gitlabhq 已提交
21 22 23
require "grit"

class Project < ActiveRecord::Base
24
  include Repository
R
randx 已提交
25
  include PushObserver
26 27
  include Authority
  include Team
28
  include NamespacedProject
29

30 31
  class TransferError < StandardError; end

32
  attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
33 34
                  :wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]

35
  attr_accessible :namespace_id, :owner_id, as: :admin
36

N
Nihad Abbasov 已提交
37
  attr_accessor :error_code
38

39
  # Relations
40
  belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
41
  belongs_to :namespace
42 43 44 45

  # TODO: replace owner with creator.
  # With namespaces a project owner will be a namespace owner
  # so this field makes sense only for global projects
46 47 48 49 50 51 52 53 54 55 56 57 58
  belongs_to :owner, class_name: "User"
  has_many :users,          through: :users_projects
  has_many :events,         dependent: :destroy
  has_many :merge_requests, dependent: :destroy
  has_many :issues,         dependent: :destroy, order: "closed, created_at DESC"
  has_many :milestones,     dependent: :destroy
  has_many :users_projects, dependent: :destroy
  has_many :notes,          dependent: :destroy
  has_many :snippets,       dependent: :destroy
  has_many :deploy_keys,    dependent: :destroy, foreign_key: "project_id", class_name: "Key"
  has_many :hooks,          dependent: :destroy, class_name: "ProjectHook"
  has_many :wikis,          dependent: :destroy
  has_many :protected_branches, dependent: :destroy
59
  has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
D
Dmitriy Zaporozhets 已提交
60
  has_one :gitlab_ci_service, dependent: :destroy
A
Aleksei Kvitinskii 已提交
61

62 63
  delegate :name, to: :owner, allow_nil: true, prefix: true

A
Andrey Kumanyaev 已提交
64 65 66
  # Validations
  validates :owner, presence: true
  validates :description, length: { within: 0..2000 }
A
Andrey Kumanyaev 已提交
67 68
  validates :name, presence: true, length: { within: 0..255 },
            format: { with: Gitlab::Regex.project_name_regex,
D
Dmitriy Zaporozhets 已提交
69
                      message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
70
  validates :path, presence: true, length: { within: 0..255 },
71
            format: { with: Gitlab::Regex.path_regex,
A
Andrey Kumanyaev 已提交
72 73 74
                      message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
  validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
            :wiki_enabled, inclusion: { in: [true, false] }
75

76 77 78
  validates_uniqueness_of :name, scope: :namespace_id
  validates_uniqueness_of :path, scope: :namespace_id

A
Andrey Kumanyaev 已提交
79 80
  validate :check_limit, :repo_name

81
  # Scopes
82
  scope :public_only, where(private_flag: false)
D
Dmitriy Zaporozhets 已提交
83 84
  scope :without_user, ->(user)  { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) }
  scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
85
  scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
86
  scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
D
Dmitriy Zaporozhets 已提交
87 88
  scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
  scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
G
gitlabhq 已提交
89

A
Andrey Kumanyaev 已提交
90
  class << self
91 92 93 94 95
    def authorized_for user
      projects = includes(:users_projects, :namespace)
      projects = projects.where("users_projects.user_id = :user_id or projects.owner_id = :user_id or namespaces.owner_id = :user_id", user_id: user.id)
    end

A
Andrey Kumanyaev 已提交
96 97 98
    def active
      joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
    end
99

A
Andrey Kumanyaev 已提交
100
    def search query
101
      where("projects.name LIKE :query OR projects.path LIKE :query", query: "%#{query}%")
A
Andrey Kumanyaev 已提交
102
    end
103

104 105 106 107
    def find_with_namespace(id)
      if id.include?("/")
        id = id.split("/")
        namespace_id = Namespace.find_by_path(id.first).id
108
        where(namespace_id: namespace_id).find_by_path(id.second)
109
      else
110
        where(path: id, namespace_id: nil).last
111 112 113
      end
    end

A
Andrey Kumanyaev 已提交
114
    def create_by_user(params, user)
115
      namespace_id = params.delete(:namespace_id)
116

A
Andrey Kumanyaev 已提交
117 118 119
      project = Project.new params

      Project.transaction do
120

121
        # Parametrize path for project
122
        #
123 124 125 126
        # Ex.
        #  'GitLab HQ'.parameterize => "gitlab-hq"
        #
        project.path = project.name.dup.parameterize
127

A
Andrey Kumanyaev 已提交
128
        project.owner = user
129 130 131

        # Apply namespace if user has access to it
        # else fallback to user namespace
132 133 134 135 136 137 138 139
        if namespace_id != Namespace.global_id
          project.namespace_id = user.namespace_id

          if namespace_id
            group = Group.find_by_id(namespace_id)
            if user.can? :manage_group, group
              project.namespace_id = namespace_id
            end
140 141 142
          end
        end

A
Andrey Kumanyaev 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
        project.save!

        # Add user as project master
        project.users_projects.create!(project_access: UsersProject::MASTER, user: user)

        # when project saved no team member exist so
        # project repository should be updated after first user add
        project.update_repository
      end

      project
    rescue Gitlab::Gitolite::AccessDenied => ex
      project.error_code = :gitolite
      project
    rescue => ex
      project.error_code = :db
      project.errors.add(:base, "Can't save project. Please try again later")
      project
161 162
    end

A
Andrey Kumanyaev 已提交
163 164 165
    def access_options
      UsersProject.access_roles
    end
166 167 168 169 170 171 172 173
  end

  def git_error?
    error_code == :gitolite
  end

  def saved?
    id && valid?
174 175
  end

176 177 178 179 180
  def check_limit
    unless owner.can_create_project?
      errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
    end
  rescue
R
Robert Speicher 已提交
181
    errors[:base] << ("Can't check your ability to create project")
G
gitlabhq 已提交
182 183
  end

184
  def repo_name
S
Sergey Linnik 已提交
185
    denied_paths = %w(gitolite-admin admin dashboard groups help profile projects search)
186 187 188

    if denied_paths.include?(path)
      errors.add(:path, "like #{path} is not allowed")
189 190
    end
  end
V
Valeriy Sizov 已提交
191

192
  def to_param
193
    if namespace
194
      namespace.path + "/" + path
195
    else
196
      path
197
    end
198 199
  end

200
  def web_url
201
    [Gitlab.config.gitlab.url, path_with_namespace].join("/")
202 203
  end

G
gitlabhq 已提交
204
  def common_notes
205
    notes.where(noteable_type: ["", nil]).inc_author_project
G
gitlabhq 已提交
206 207
  end

208
  def build_commit_note(commit)
209
    notes.new(commit_id: commit.id, noteable_type: "Commit")
G
gitlabhq 已提交
210
  end
N
Nihad Abbasov 已提交
211

212
  def commit_notes(commit)
213
    notes.where(commit_id: commit.id, noteable_type: "Commit", line_code: nil)
D
Dmitriy Zaporozhets 已提交
214 215 216
  end

  def commit_line_notes(commit)
217
    notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
G
gitlabhq 已提交
218
  end
N
Nihad Abbasov 已提交
219

G
gitlabhq 已提交
220 221 222 223 224 225 226 227
  def public?
    !private_flag
  end

  def private?
    private_flag
  end

N
Nihad Abbasov 已提交
228
  def last_activity
229
    last_event
G
gitlabhq 已提交
230 231 232
  end

  def last_activity_date
233
    last_event.try(:created_at) || updated_at
D
Dmitriy Zaporozhets 已提交
234
  end
235

D
Dmitriy Zaporozhets 已提交
236 237 238
  def project_id
    self.id
  end
R
randx 已提交
239 240 241 242

  def issues_labels
    issues.tag_counts_on(:labels)
  end
243 244 245 246

  def services
    [gitlab_ci_service].compact
  end
D
Dmitriy Zaporozhets 已提交
247 248 249 250

  def gitlab_ci?
    gitlab_ci_service && gitlab_ci_service.active
  end
251

252 253 254
  # For compatibility with old code
  def code
    path
255
  end
256

D
Dmitriy Zaporozhets 已提交
257 258 259 260 261 262 263 264
  def items_for entity
    case entity
    when 'issue' then
      issues
    when 'merge_request' then
      merge_requests
    end
  end
265 266 267 268 269 270

  def send_move_instructions
    self.users_projects.each do |member|
      Notify.project_was_moved_email(member.id).deliver
    end
  end
G
gitlabhq 已提交
271
end