project.rb 8.59 KB
Newer Older
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
1
2
3
4
5
require "grit"

class Project < ActiveRecord::Base
  belongs_to :owner, :class_name => "User"

6
7
  has_many :users,          :through => :users_projects
  has_many :events,         :dependent => :destroy
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
8
  has_many :merge_requests, :dependent => :destroy
9
  has_many :issues,         :dependent => :destroy, :order => "position"
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
10
  has_many :users_projects, :dependent => :destroy
11
12
13
14
15
  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 :web_hooks,      :dependent => :destroy
  has_many :wikis,          :dependent => :destroy
16
  has_many :protected_branches, :dependent => :destroy
Aleksei Kvitinskii's avatar
Aleksei Kvitinskii committed
17

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
18
19
20
21
22
23
24
25
  validates :name,
            :uniqueness => true,
            :presence => true,
            :length   => { :within => 0..255 }

  validates :path,
            :uniqueness => true,
            :presence => true,
26
            :format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
27
                         :message => "only letters, digits & '_' '-' '.' allowed" },
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
28
            :length   => { :within => 0..255 }
Nihad Abbasov's avatar
Nihad Abbasov committed
29

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
30
31
32
33
34
35
  validates :description,
            :length   => { :within => 0..2000 }

  validates :code,
            :presence => true,
            :uniqueness => true,
36
            :format => { :with => /^[a-zA-Z0-9_\-\.]*$/,
37
                         :message => "only letters, digits & '_' '-' '.' allowed"  },
gitlabhq's avatar
gitlabhq committed
38
            :length   => { :within => 3..255 }
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
39

40
  validates :owner, :presence => true
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
41
  validate :check_limit
gitlabhq's avatar
gitlabhq committed
42
43
  validate :repo_name

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
44
45
46
  attr_protected :private_flag, :owner_id

  scope :public_only, where(:private_flag => false)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
47
  scope :without_user, lambda { |user|  where("id not in (:ids)", :ids => user.projects.map(&:id) ) }
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
48

49
50
51
  def self.active
    joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
  end
52
53

  def self.access_options
54
    UsersProject.access_roles
55
56
  end

57
58
59
60
61
  def repository
    @repository ||= Repository.new(self)
  end

  delegate :repo,
62
63
    :url_to_repo,
    :path_to_repo,
64
65
    :update_repository,
    :destroy_repository,
66
67
68
69
    :tags,
    :repo_exists?,
    :commit,
    :commits,
70
    :commits_with_refs,
71
72
73
74
    :tree,
    :heads,
    :commits_since,
    :fresh_commits,
75
    :commits_between,
76
77
    :to => :repository, :prefix => nil

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
78
79
80
81
  def to_param
    code
  end

82
83
84
85
  def web_url
    [GIT_HOST['host'], code].join("/")
  end

86
87
  def observe_push(oldrev, newrev, ref, author_key_id)
    data = web_hook_data(oldrev, newrev, ref, author_key_id)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
88
89
90
91
92
93
94
95

    Event.create(
      :project => self,
      :action => Event::Pushed,
      :data => data
    )
  end

96
  def execute_web_hooks(oldrev, newrev, ref, author_key_id)
97
98
99
100
101
    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"

102
    data = web_hook_data(oldrev, newrev, ref, author_key_id)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
103

104
105
106
    web_hooks.each { |web_hook| web_hook.execute(data) }
  end

107
108
  def web_hook_data(oldrev, newrev, ref, author_key_id)
    key = Key.find_by_identifier(author_key_id)
109
110
111
112
    data = {
      before: oldrev,
      after: newrev,
      ref: ref,
113
      user_id: key.user.id,
114
      user_name: key.user_name,
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
140
      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

141
142
143
144
145
146
147
148
149
  def open_branches
    if protected_branches.empty?
      self.repo.heads
    else
      pnames = protected_branches.map(&:name)
      self.repo.heads.reject { |h| pnames.include?(h.name) }
    end.sort_by(&:name)
  end

150
151
152
153
154
  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

155
156
157
158
  def team_member_by_id(user_id)
    users_projects.find_by_user_id(user_id)
  end

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
159
  def common_notes
gitlabhq's avatar
gitlabhq committed
160
    notes.where(:noteable_type => ["", nil]).inc_author_project
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
161
162
  end

163
164
  def build_commit_note(commit)
    notes.new(:noteable_id => commit.id, :noteable_type => "Commit")
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
165
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
166

167
  def commit_notes(commit)
168
169
170
171
    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
172
    notes.where(:noteable_id => commit.id, :noteable_type => "Commit").where("line_code is not null")
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
173
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
174

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
175
176
177
178
  def has_commits?
    !!commit
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
179
180
  # Compatible with all access rights
  # Should be rewrited for new access rights
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
181
  def add_access(user, *access)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
182
    access = if access.include?(:admin) 
183
               { :project_access => UsersProject::MASTER } 
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
184
             elsif access.include?(:write)
185
               { :project_access => UsersProject::DEVELOPER } 
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
186
             else
187
               { :project_access => UsersProject::REPORTER } 
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
188
             end
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
189
    opts = { :user => user }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
190
    opts.merge!(access)
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
191
192
193
194
195
196
197
    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

198
  def repository_readers
199
    keys = Key.joins({:user => :users_projects}).
200
      where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::REPORTER)
201
    keys.map(&:identifier) + deploy_keys.map(&:identifier)
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
202
203
  end

204
  def repository_writers
205
    keys = Key.joins({:user => :users_projects}).
206
      where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::DEVELOPER)
207
    keys.map(&:identifier)
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
208
209
  end

210
211
  def repository_masters
    keys = Key.joins({:user => :users_projects}).
212
      where("users_projects.project_id = ? AND users_projects.project_access = ?", id, UsersProject::MASTER)
213
214
215
    keys.map(&:identifier)
  end

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
216
  def readers
217
    @readers ||= users_projects.includes(:user).map(&:user)
218
219
220
  end

  def writers
221
    @writers ||= users_projects.includes(:user).map(&:user)
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
222
223
224
  end

  def admins
225
    @admins ||= users_projects.includes(:user).where(:project_access => UsersProject::MASTER).map(&:user)
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
226
227
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
228
  def allow_read_for?(user)
229
    !users_projects.where(:user_id => user.id).empty?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
230
231
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
232
  def guest_access_for?(user)
233
    !users_projects.where(:user_id => user.id).empty?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
234
235
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
236
237
  def report_access_for?(user)
    !users_projects.where(:user_id => user.id, :project_access => [UsersProject::REPORTER, UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
238
239
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
240
241
242
243
244
245
  def dev_access_for?(user)
    !users_projects.where(:user_id => user.id, :project_access => [UsersProject::DEVELOPER, UsersProject::MASTER]).empty?
  end

  def master_access_for?(user)
    !users_projects.where(:user_id => user.id, :project_access => [UsersProject::MASTER]).empty? || owner_id == user.id
246
247
  end

gitlabhq's avatar
gitlabhq committed
248
  def root_ref 
249
    default_branch || "master"
gitlabhq's avatar
gitlabhq committed
250
251
  end

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
252
253
254
255
256
257
258
259
  def public?
    !private_flag
  end

  def private?
    private_flag
  end

260
  def last_activity
261
    events.last || nil
gitlabhq's avatar
gitlabhq committed
262
263
264
  end

  def last_activity_date
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
265
266
267
268
269
    if events.last
      events.last.created_at
    else
      updated_at
    end
gitlabhq's avatar
gitlabhq committed
270
271
  end

272
  def last_activity_date_cached(expire = 1.hour)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
273
    last_activity_date
274
275
  end

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
276
277
278
279
  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
Nihad Abbasov's avatar
Nihad Abbasov committed
280
  rescue
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
281
282
283
    errors[:base] << ("Cant check your ability to create project")
  end

gitlabhq's avatar
gitlabhq committed
284
  def repo_name
285
286
    if path == "gitolite-admin"
      errors.add(:path, " like 'gitolite-admin' is not allowed")
gitlabhq's avatar
gitlabhq committed
287
288
289
    end
  end

Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
290
291
292
293
294
295
296
297
298
299
300
  def valid_repo?
    repo
  rescue
    errors.add(:path, "Invalid repository path")
    false
  end
end
# == Schema Information
#
# Table name: projects
#
301
302
303
304
305
306
307
308
309
310
311
312
313
#  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
#  issues_enabled         :boolean         default(TRUE), not null
#  wall_enabled           :boolean         default(TRUE), not null
#  merge_requests_enabled :boolean         default(TRUE), not null
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
314
#  wiki_enabled           :boolean         default(TRUE), not null
Dmitriy Zaporozhets's avatar
v1.0  
Dmitriy Zaporozhets committed
315
316
#