project.rb 8.89 KB
Newer Older
gitlabhq's avatar
gitlabhq committed
1 2 3
require "grit"

class Project < ActiveRecord::Base
4 5 6 7 8
  PROJECT_N = 0
  PROJECT_R = 1
  PROJECT_RW = 2
  PROJECT_RWA = 3

gitlabhq's avatar
gitlabhq committed
9 10
  belongs_to :owner, :class_name => "User"

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
11
  has_many :merge_requests, :dependent => :destroy
VSizov's avatar
VSizov committed
12
  has_many :issues, :dependent => :destroy, :order => "position"
gitlabhq's avatar
gitlabhq committed
13 14 15
  has_many :users_projects, :dependent => :destroy
  has_many :users, :through => :users_projects
  has_many :notes, :dependent => :destroy
gitlabhq's avatar
gitlabhq committed
16
  has_many :snippets, :dependent => :destroy
17
  has_many :deploy_keys, :dependent => :destroy, :foreign_key => "project_id", :class_name => "Key"
18
  has_many :web_hooks, :dependent => :destroy
gitlabhq's avatar
gitlabhq committed
19

Aleksei Kvitinskii's avatar
Aleksei Kvitinskii committed
20 21
  acts_as_taggable

gitlabhq's avatar
gitlabhq committed
22 23 24 25 26 27 28 29
  validates :name,
            :uniqueness => true,
            :presence => true,
            :length   => { :within => 0..255 }

  validates :path,
            :uniqueness => true,
            :presence => true,
30
            :format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
31
                         :message => "only letters, digits & '_' '-' '.' allowed" },
gitlabhq's avatar
gitlabhq committed
32
            :length   => { :within => 0..255 }
Nihad Abbasov's avatar
Nihad Abbasov committed
33

gitlabhq's avatar
gitlabhq committed
34 35 36 37 38 39
  validates :description,
            :length   => { :within => 0..2000 }

  validates :code,
            :presence => true,
            :uniqueness => true,
40
            :format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
41
                         :message => "only letters, digits & '_' '-' '.' allowed"  },
gitlabhq's avatar
gitlabhq committed
42
            :length   => { :within => 3..255 }
gitlabhq's avatar
gitlabhq committed
43

gitlabhq's avatar
gitlabhq committed
44 45 46
  validates :owner,
            :presence => true

Valera Sizov's avatar
Valera Sizov committed
47
  validate :check_limit
gitlabhq's avatar
gitlabhq committed
48 49
  validate :repo_name

50 51
  after_destroy :destroy_repository
  after_save :update_repository
gitlabhq's avatar
gitlabhq committed
52

53
  attr_protected :private_flag, :owner_id
gitlabhq's avatar
gitlabhq committed
54 55 56

  scope :public_only, where(:private_flag => false)

57 58 59
  def self.active
    joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
  end
60 61 62 63 64 65 66 67 68 69

  def self.access_options
    {
      "Denied" => PROJECT_N,
      "Read"   => PROJECT_R,
      "Report" => PROJECT_RW,
      "Admin"  => PROJECT_RWA
    }
  end

70 71 72 73 74
  def repository
    @repository ||= Repository.new(self)
  end

  delegate :repo,
75 76
    :url_to_repo,
    :path_to_repo,
77 78
    :update_repository,
    :destroy_repository,
79 80 81 82
    :tags,
    :repo_exists?,
    :commit,
    :commits,
83
    :commits_with_refs,
84 85 86 87
    :tree,
    :heads,
    :commits_since,
    :fresh_commits,
88
    :commits_between,
89 90
    :to => :repository, :prefix => nil

gitlabhq's avatar
gitlabhq committed
91 92 93 94
  def to_param
    code
  end

95 96 97 98 99
  def web_url
    [GIT_HOST['host'], code].join("/")
  end

  def execute_web_hooks(oldrev, newrev, ref)
100 101 102 103 104
    ref_parts = ref.split('/')

    # Return if this is not a push to a branch (e.g. new commits)
    return if ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000"

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    data = web_hook_data(oldrev, newrev, ref)
    web_hooks.each { |web_hook| web_hook.execute(data) }
  end

  def web_hook_data(oldrev, newrev, ref)
    data = {
      before: oldrev,
      after: newrev,
      ref: ref,
      repository: {
        name: name,
        url: web_url,
        description: description,
        homepage: web_url,
        private: private?
      },
      commits: []
    }

    commits_between(oldrev, newrev).each do |commit|
      data[:commits] << {
        id: commit.id,
        message: commit.safe_message,
        timestamp: commit.date.xmlschema,
        url: "http://#{GIT_HOST['host']}/#{code}/commits/#{commit.id}",
        author: {
          name: commit.author_name,
          email: commit.author_email
        }
      }
    end

    data
  end

140 141 142 143 144
  def team_member_by_name_or_email(email = nil, name = nil)
    user = users.where("email like ? or name like ?", email, name).first
    users_projects.find_by_user_id(user.id) if user
  end

145 146 147 148
  def team_member_by_id(user_id)
    users_projects.find_by_user_id(user_id)
  end

149 150 151 152
  def fresh_merge_requests(n)
    merge_requests.includes(:project, :author).order("created_at desc").first(n)
  end

gitlabhq's avatar
gitlabhq committed
153 154 155 156 157 158 159 160
  def fresh_issues(n)
    issues.includes(:project, :author).order("created_at desc").first(n)
  end

  def fresh_notes(n)
    notes.inc_author_project.order("created_at desc").first(n)
  end

gitlabhq's avatar
gitlabhq committed
161
  def common_notes
gitlabhq's avatar
gitlabhq committed
162
    notes.where(:noteable_type => ["", nil]).inc_author_project
gitlabhq's avatar
gitlabhq committed
163 164
  end

165 166
  def build_commit_note(commit)
    notes.new(:noteable_id => commit.id, :noteable_type => "Commit")
gitlabhq's avatar
gitlabhq committed
167
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
168

169
  def commit_notes(commit)
170 171 172 173
    notes.where(:noteable_id => commit.id, :noteable_type => "Commit", :line_code => nil)
  end

  def commit_line_notes(commit)
Valery Sizov's avatar
Valery Sizov committed
174
    notes.where(:noteable_id => commit.id, :noteable_type => "Commit").where("line_code is not null")
gitlabhq's avatar
gitlabhq committed
175
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
176

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
177 178 179 180
  def has_commits?
    !!commit
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
181 182
  # Compatible with all access rights
  # Should be rewrited for new access rights
gitlabhq's avatar
gitlabhq committed
183
  def add_access(user, *access)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
184 185 186 187 188 189 190
    access = if access.include?(:admin) 
               { :project_access => PROJECT_RWA } 
             elsif access.include?(:write)
               { :project_access => PROJECT_RW } 
             else
               { :project_access => PROJECT_R } 
             end
gitlabhq's avatar
gitlabhq committed
191
    opts = { :user => user }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
192
    opts.merge!(access)
gitlabhq's avatar
gitlabhq committed
193 194 195 196 197 198 199
    users_projects.create(opts)
  end

  def reset_access(user)
    users_projects.where(:project_id => self.id, :user_id => user.id).destroy if self.id
  end

200 201 202
  def repository_readers
    keys = Key.joins({:user => :users_projects}).
      where("users_projects.project_id = ? AND users_projects.repo_access = ?", id, Repository::REPO_R)
203
    keys.map(&:identifier) + deploy_keys.map(&:identifier)
gitlabhq's avatar
gitlabhq committed
204 205
  end

206
  def repository_writers
207 208
    keys = Key.joins({:user => :users_projects}).
      where("users_projects.project_id = ? AND users_projects.repo_access = ?", id, Repository::REPO_RW)
gitlabhq's avatar
gitlabhq committed
209 210 211 212
    keys.map(&:identifier)
  end

  def readers
213 214 215 216 217
    @readers ||= users_projects.includes(:user).where(:project_access => [PROJECT_R, PROJECT_RW, PROJECT_RWA]).map(&:user)
  end

  def writers
    @writers ||= users_projects.includes(:user).where(:project_access => [PROJECT_RW, PROJECT_RWA]).map(&:user)
gitlabhq's avatar
gitlabhq committed
218 219 220
  end

  def admins
221
    @admins ||= users_projects.includes(:user).where(:project_access => PROJECT_RWA).map(&:user)
gitlabhq's avatar
gitlabhq committed
222 223
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
224 225 226 227 228 229 230 231 232 233 234 235
  def allow_read_for?(user)
    !users_projects.where(:user_id => user.id, :project_access => [PROJECT_R, PROJECT_RW, PROJECT_RWA]).empty?
  end

  def allow_write_for?(user)
    !users_projects.where(:user_id => user.id, :project_access => [PROJECT_RW, PROJECT_RWA]).empty?
  end

  def allow_admin_for?(user)
    !users_projects.where(:user_id => user.id, :project_access => [PROJECT_RWA]).empty? || owner_id == user.id
  end

gitlabhq's avatar
gitlabhq committed
236
  def root_ref 
237
    default_branch || "master"
gitlabhq's avatar
gitlabhq committed
238 239
  end

gitlabhq's avatar
gitlabhq committed
240 241 242 243 244 245 246 247
  def public?
    !private_flag
  end

  def private?
    private_flag
  end

248
  def last_activity
gitlabhq's avatar
gitlabhq committed
249
    updates(1).first
250
  rescue
gitlabhq's avatar
gitlabhq committed
251 252 253 254 255 256 257
    nil
  end

  def last_activity_date
    last_activity.try(:created_at)
  end

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
  def last_activity_date_cached(expire = 1.hour)
    activity_date_key = "project_#{id}_activity_date"

    cached_activities = Rails.cache.read(activity_date_key)
    if cached_activities
      activity_date = if cached_activities == "Never"
                        nil
                      else
                        cached_activities
                      end
    else
      activity_date = last_activity_date
      Rails.cache.write(activity_date_key, activity_date || "Never", :expires_in => expire)
    end

    activity_date
  end

276 277 278 279 280 281 282 283 284
  # Get project updates from cache
  # or calculate. 
  def cached_updates(limit, expire = 2.minutes)
    activities_key = "project_#{id}_activities"
    cached_activities = Rails.cache.read(activities_key)
    if cached_activities
      activities = cached_activities
    else
      activities = updates(limit)
285
      Rails.cache.write(activities_key, activities, :expires_in => expire)
286 287 288 289 290 291 292
    end

    activities
  end

  # Get 20 events for project like
  # commits, issues or notes
gitlabhq's avatar
gitlabhq committed
293
  def updates(n = 3)
294
    [
gitlabhq's avatar
gitlabhq committed
295
      fresh_commits(n),
gitlabhq's avatar
gitlabhq committed
296 297
      fresh_issues(n),
      fresh_notes(n)
gitlabhq's avatar
gitlabhq committed
298
    ].compact.flatten.sort do |x, y|
299 300 301 302
      y.created_at <=> x.created_at
    end[0...n]
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
303
  def activities(n=3)
304 305 306
    [
      fresh_issues(n),
      fresh_merge_requests(n),
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
307
      notes.inc_author_project.where("noteable_type is not null").order("created_at desc").first(n)
308
    ].compact.flatten.sort do |x, y|
gitlabhq's avatar
gitlabhq committed
309
      y.created_at <=> x.created_at
gitlabhq's avatar
gitlabhq committed
310
    end[0...n]
gitlabhq's avatar
gitlabhq committed
311 312
  end

Valera Sizov's avatar
Valera Sizov committed
313 314
  def check_limit
    unless owner.can_create_project?
gitlabhq's avatar
gitlabhq committed
315
      errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
Valera Sizov's avatar
Valera Sizov committed
316
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
317
  rescue
gitlabhq's avatar
gitlabhq committed
318
    errors[:base] << ("Cant check your ability to create project")
Valera Sizov's avatar
Valera Sizov committed
319 320
  end

gitlabhq's avatar
gitlabhq committed
321
  def repo_name
322 323
    if path == "gitolite-admin"
      errors.add(:path, " like 'gitolite-admin' is not allowed")
gitlabhq's avatar
gitlabhq committed
324 325 326
    end
  end

gitlabhq's avatar
gitlabhq committed
327 328 329 330 331 332 333 334 335 336 337
  def valid_repo?
    repo
  rescue
    errors.add(:path, "Invalid repository path")
    false
  end
end
# == Schema Information
#
# Table name: projects
#
Valery Sizov's avatar
Valery Sizov committed
338 339 340 341 342 343 344 345 346 347
#  id             :integer         not null, primary key
#  name           :string(255)
#  path           :string(255)
#  description    :text
#  created_at     :datetime
#  updated_at     :datetime
#  private_flag   :boolean         default(TRUE), not null
#  code           :string(255)
#  owner_id       :integer
#  default_branch :string(255)     default("master"), not null
gitlabhq's avatar
gitlabhq committed
348 349
#