OSDN Git Service

Merge branch 'feature/refactoring_scopes_pr' of https://github.com/Undev/gitlabhq...
[wvm/gitlab.git] / app / models / project.rb
1 # == Schema Information
2 #
3 # Table name: projects
4 #
5 #  id                     :integer          not null, primary key
6 #  name                   :string(255)
7 #  path                   :string(255)
8 #  description            :text
9 #  created_at             :datetime         not null
10 #  updated_at             :datetime         not null
11 #  creator_id             :integer
12 #  default_branch         :string(255)
13 #  issues_enabled         :boolean          default(TRUE), not null
14 #  wall_enabled           :boolean          default(TRUE), not null
15 #  merge_requests_enabled :boolean          default(TRUE), not null
16 #  wiki_enabled           :boolean          default(TRUE), not null
17 #  namespace_id           :integer
18 #  public                 :boolean          default(FALSE), not null
19 #  issues_tracker         :string(255)      default("gitlab"), not null
20 #  issues_tracker_id      :string(255)
21 #  snippets_enabled       :boolean          default(TRUE), not null
22 #  last_activity_at       :datetime
23 #
24
25 require "grit"
26
27 class Project < ActiveRecord::Base
28   include Gitlab::ShellAdapter
29   extend Enumerize
30
31   attr_accessible :name, :path, :description, :default_branch, :issues_tracker,
32     :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
33     :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin]
34
35   attr_accessible :namespace_id, :creator_id, as: :admin
36
37   attr_accessor :import_url
38
39   # Relations
40   belongs_to :creator,      foreign_key: "creator_id", class_name: "User"
41   belongs_to :group,        foreign_key: "namespace_id", conditions: "type = 'Group'"
42   belongs_to :namespace
43
44   has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
45   has_one :gitlab_ci_service, dependent: :destroy
46
47   has_many :events,             dependent: :destroy
48   has_many :merge_requests,     dependent: :destroy
49   has_many :issues,             dependent: :destroy, order: "state DESC, created_at DESC"
50   has_many :milestones,         dependent: :destroy
51   has_many :users_projects,     dependent: :destroy
52   has_many :notes,              dependent: :destroy
53   has_many :snippets,           dependent: :destroy
54   has_many :deploy_keys,        dependent: :destroy, class_name: "Key", foreign_key: "project_id"
55   has_many :hooks,              dependent: :destroy, class_name: "ProjectHook"
56   has_many :wikis,              dependent: :destroy
57   has_many :protected_branches, dependent: :destroy
58   has_many :user_team_project_relationships, dependent: :destroy
59
60   has_many :users,          through: :users_projects
61   has_many :user_teams,     through: :user_team_project_relationships
62   has_many :user_team_user_relationships, through: :user_teams
63   has_many :user_teams_members, through: :user_team_user_relationships
64
65   delegate :name, to: :owner, allow_nil: true, prefix: true
66
67   # Validations
68   validates :creator, presence: true
69   validates :description, length: { within: 0..2000 }
70   validates :name, presence: true, length: { within: 0..255 },
71             format: { with: Gitlab::Regex.project_name_regex,
72                       message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
73   validates :path, presence: true, length: { within: 0..255 },
74             format: { with: Gitlab::Regex.path_regex,
75                       message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
76   validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
77             :wiki_enabled, inclusion: { in: [true, false] }
78   validates :issues_tracker_id, length: { within: 0..255 }
79
80   validates_uniqueness_of :name, scope: :namespace_id
81   validates_uniqueness_of :path, scope: :namespace_id
82
83   validates :import_url,
84     format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
85     if: :import?
86
87   validate :check_limit, :repo_name
88
89   # Scopes
90   scope :without_user, ->(user)  { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
91   scope :without_team, ->(team) { team.projects.present? ? where("projects.id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped  }
92   scope :not_in_group, ->(group) { where("projects.id NOT IN (:ids)", ids: group.project_ids ) }
93   scope :in_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) }
94   scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
95   scope :in_group_namespace, -> { joins(:group) }
96   scope :sorted_by_activity, -> { order("projects.last_activity_at DESC") }
97   scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
98   scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
99   scope :public_only, -> { where(public: true) }
100
101   enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
102
103   class << self
104     def abandoned
105       project_ids = Event.select('max(created_at) as latest_date, project_id').
106         group('project_id').
107         having('latest_date < ?', 6.months.ago).map(&:project_id)
108
109       where(id: project_ids)
110     end
111
112     def with_push
113       includes(:events).where('events.action = ?', Event::PUSHED)
114     end
115
116     def active
117       joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
118     end
119
120     def search query
121       where("projects.name LIKE :query OR projects.path LIKE :query", query: "%#{query}%")
122     end
123
124     def find_with_namespace(id)
125       if id.include?("/")
126         id = id.split("/")
127         namespace = Namespace.find_by_path(id.first)
128         return nil unless namespace
129
130         where(namespace_id: namespace.id).find_by_path(id.second)
131       else
132         where(path: id, namespace_id: nil).last
133       end
134     end
135
136     def access_options
137       UsersProject.access_roles
138     end
139   end
140
141   def team
142     @team ||= ProjectTeam.new(self)
143   end
144
145   def repository
146     @repository ||= Repository.new(path_with_namespace, default_branch)
147   end
148
149   def saved?
150     id && persisted?
151   end
152
153   def import?
154     import_url.present?
155   end
156
157   def check_limit
158     unless creator.can_create_project?
159       errors[:limit_reached] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
160     end
161   rescue
162     errors[:base] << ("Can't check your ability to create project")
163   end
164
165   def repo_name
166     denied_paths = %w(admin dashboard groups help profile projects search)
167
168     if denied_paths.include?(path)
169       errors.add(:path, "like #{path} is not allowed")
170     end
171   end
172
173   def to_param
174     if namespace
175       namespace.path + "/" + path
176     else
177       path
178     end
179   end
180
181   def web_url
182     [Gitlab.config.gitlab.url, path_with_namespace].join("/")
183   end
184
185   def build_commit_note(commit)
186     notes.new(commit_id: commit.id, noteable_type: "Commit")
187   end
188
189   def last_activity
190     last_event
191   end
192
193   def last_activity_date
194     last_activity_at || updated_at
195   end
196
197   def project_id
198     self.id
199   end
200
201   def issues_labels
202     issues.tag_counts_on(:labels)
203   end
204
205   def issue_exists?(issue_id)
206     if used_default_issues_tracker?
207       self.issues.where(id: issue_id).first.present?
208     else
209       true
210     end
211   end
212
213   def used_default_issues_tracker?
214     self.issues_tracker == Project.issues_tracker.default_value
215   end
216
217   def can_have_issues_tracker_id?
218     self.issues_enabled && !self.used_default_issues_tracker?
219   end
220
221   def services
222     [gitlab_ci_service].compact
223   end
224
225   def gitlab_ci?
226     gitlab_ci_service && gitlab_ci_service.active
227   end
228
229   # For compatibility with old code
230   def code
231     path
232   end
233
234   def items_for entity
235     case entity
236     when 'issue' then
237       issues
238     when 'merge_request' then
239       merge_requests
240     end
241   end
242
243   def send_move_instructions
244     self.users_projects.each do |member|
245       Notify.delay.project_was_moved_email(member.id)
246     end
247   end
248
249   def owner
250     if namespace
251       namespace_owner
252     else
253       creator
254     end
255   end
256
257   def team_member_by_name_or_email(name = nil, email = nil)
258     user = users.where("name like ? or email like ?", name, email).first
259     users_projects.where(user: user) if user
260   end
261
262   # Get Team Member record by user id
263   def team_member_by_id(user_id)
264     users_projects.find_by_user_id(user_id)
265   end
266
267   def name_with_namespace
268     @name_with_namespace ||= begin
269                                if namespace
270                                  namespace.human_name + " / " + name
271                                else
272                                  name
273                                end
274                              end
275   end
276
277   def namespace_owner
278     namespace.try(:owner)
279   end
280
281   def path_with_namespace
282     if namespace
283       namespace.path + '/' + path
284     else
285       path
286     end
287   end
288
289   def transfer(new_namespace)
290     ProjectTransferService.new.transfer(self, new_namespace)
291   end
292
293   def execute_hooks(data)
294     hooks.each { |hook| hook.async_execute(data) }
295   end
296
297   def execute_services(data)
298     services.each do |service|
299
300       # Call service hook only if it is active
301       service.execute(data) if service.active
302     end
303   end
304
305   def discover_default_branch
306     # Discover the default branch, but only if it hasn't already been set to
307     # something else
308     if repository && default_branch.nil?
309       update_attributes(default_branch: self.repository.discover_default_branch)
310     end
311   end
312
313   def update_merge_requests(oldrev, newrev, ref, user)
314     return true unless ref =~ /heads/
315     branch_name = ref.gsub("refs/heads/", "")
316     c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
317
318     # Update code for merge requests
319     mrs = self.merge_requests.opened.by_branch(branch_name).all
320     mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
321
322     # Close merge requests
323     mrs = self.merge_requests.opened.where(target_branch: branch_name).all
324     mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
325     mrs.each { |merge_request| merge_request.merge!(user.id) }
326
327     true
328   end
329
330   def valid_repo?
331     repository.exists?
332   rescue
333     errors.add(:path, "Invalid repository path")
334     false
335   end
336
337   def empty_repo?
338     !repository.exists? || repository.empty?
339   end
340
341   def ensure_satellite_exists
342     self.satellite.create unless self.satellite.exists?
343   end
344
345   def satellite
346     @satellite ||= Gitlab::Satellite::Satellite.new(self)
347   end
348
349   def repo
350     repository.raw
351   end
352
353   def url_to_repo
354     gitlab_shell.url_to_repo(path_with_namespace)
355   end
356
357   def namespace_dir
358     namespace.try(:path) || ''
359   end
360
361   def repo_exists?
362     @repo_exists ||= repository.exists?
363   rescue
364     @repo_exists = false
365   end
366
367   def open_branches
368     all_branches = repository.branches
369
370     if protected_branches.present?
371       all_branches.reject! do |branch|
372         protected_branches_names.include?(branch.name)
373       end
374     end
375
376     all_branches
377   end
378
379   def protected_branches_names
380     @protected_branches_names ||= protected_branches.map(&:name)
381   end
382
383   def root_ref?(branch)
384     repository.root_ref == branch
385   end
386
387   def ssh_url_to_repo
388     url_to_repo
389   end
390
391   def http_url_to_repo
392     http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
393   end
394
395   def project_access_human(member)
396     project_user_relation = self.users_projects.find_by_user_id(member.id)
397     self.class.access_options.key(project_user_relation.project_access)
398   end
399
400   # Check if current branch name is marked as protected in the system
401   def protected_branch? branch_name
402     protected_branches_names.include?(branch_name)
403   end
404 end