Merge commit 'master' into discussions
Conflicts: app/assets/stylesheets/sections/notes.scss app/contexts/notes/load_context.rb app/models/project.rb app/observers/note_observer.rb app/roles/votes.rb app/views/commit/show.html.haml app/views/merge_requests/_show.html.haml app/views/merge_requests/diffs.js.haml app/views/merge_requests/show.js.haml app/views/notes/_note.html.haml features/steps/project/project_merge_requests.rb spec/models/note_spec.rb
This commit is contained in:
commit
3022786948
930 changed files with 80374 additions and 103682 deletions
|
@ -15,35 +15,26 @@ class Ability
|
|||
def project_abilities(user, project)
|
||||
rules = []
|
||||
|
||||
team = project.team
|
||||
|
||||
# Rules based on role in project
|
||||
if project.master_access_for?(user)
|
||||
if team.masters.include?(user)
|
||||
rules << project_master_rules
|
||||
|
||||
elsif project.dev_access_for?(user)
|
||||
elsif team.developers.include?(user)
|
||||
rules << project_dev_rules
|
||||
|
||||
elsif project.report_access_for?(user)
|
||||
elsif team.reporters.include?(user)
|
||||
rules << project_report_rules
|
||||
|
||||
elsif project.guest_access_for?(user)
|
||||
elsif team.guests.include?(user)
|
||||
rules << project_guest_rules
|
||||
end
|
||||
|
||||
if project.namespace
|
||||
# If user own project namespace
|
||||
# (Ex. group owner or account owner)
|
||||
if project.namespace.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
else
|
||||
# For compatibility with global projects
|
||||
# use projects.owner_id
|
||||
if project.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
if project.owner == user
|
||||
rules << project_admin_rules
|
||||
end
|
||||
|
||||
|
||||
rules.flatten
|
||||
end
|
||||
|
||||
|
@ -107,9 +98,12 @@ class Ability
|
|||
def group_abilities user, group
|
||||
rules = []
|
||||
|
||||
rules << [
|
||||
:manage_group
|
||||
] if group.owner == user
|
||||
# Only group owner and administrators can manage group
|
||||
if group.owner == user || user.admin?
|
||||
rules << [
|
||||
:manage_group
|
||||
]
|
||||
end
|
||||
|
||||
rules.flatten
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class Commit
|
|||
attr_accessor :commit, :head, :refs
|
||||
|
||||
delegate :message, :authored_date, :committed_date, :parents, :sha,
|
||||
:date, :committer, :author, :message, :diffs, :tree, :id,
|
||||
:date, :committer, :author, :diffs, :tree, :id, :stats,
|
||||
:to_patch, to: :commit
|
||||
|
||||
class << self
|
||||
|
@ -83,8 +83,8 @@ class Commit
|
|||
|
||||
return result unless from && to
|
||||
|
||||
first = project.commit(to.try(:strip))
|
||||
last = project.commit(from.try(:strip))
|
||||
first = project.repository.commit(to.try(:strip))
|
||||
last = project.repository.commit(from.try(:strip))
|
||||
|
||||
if first && last
|
||||
result[:same] = (first.id == last.id)
|
||||
|
@ -98,6 +98,8 @@ class Commit
|
|||
end
|
||||
|
||||
def initialize(raw_commit, head = nil)
|
||||
raise "Nil as raw commit passed" unless raw_commit
|
||||
|
||||
@commit = raw_commit
|
||||
@head = head
|
||||
end
|
||||
|
@ -136,17 +138,17 @@ class Commit
|
|||
end
|
||||
|
||||
def prev_commit
|
||||
parents.try :first
|
||||
@prev_commit ||= if parents.present?
|
||||
Commit.new(parents.first)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def prev_commit_id
|
||||
prev_commit.try :id
|
||||
end
|
||||
|
||||
def parents_count
|
||||
parents && parents.count || 0
|
||||
end
|
||||
|
||||
# Shows the diff between the commit's parent and the commit.
|
||||
#
|
||||
# Cuts out the header and stats from #to_patch and returns only the diff.
|
||||
|
|
106
app/models/concerns/issuable.rb
Normal file
106
app/models/concerns/issuable.rb
Normal file
|
@ -0,0 +1,106 @@
|
|||
# == Issuable concern
|
||||
#
|
||||
# Contains common functionality shared between Issues and MergeRequests
|
||||
#
|
||||
# Used by Issue, MergeRequest
|
||||
#
|
||||
module Issuable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :project
|
||||
belongs_to :author, class_name: "User"
|
||||
belongs_to :assignee, class_name: "User"
|
||||
belongs_to :milestone
|
||||
has_many :notes, as: :noteable, dependent: :destroy
|
||||
|
||||
validates :project, presence: true
|
||||
validates :author, presence: true
|
||||
validates :title, presence: true, length: { within: 0..255 }
|
||||
validates :closed, inclusion: { in: [true, false] }
|
||||
|
||||
scope :opened, where(closed: false)
|
||||
scope :closed, where(closed: true)
|
||||
scope :of_group, ->(group) { where(project_id: group.project_ids) }
|
||||
scope :assigned, ->(u) { where(assignee_id: u.id)}
|
||||
scope :recent, order("created_at DESC")
|
||||
|
||||
delegate :name,
|
||||
:email,
|
||||
to: :author,
|
||||
prefix: true
|
||||
|
||||
delegate :name,
|
||||
:email,
|
||||
to: :assignee,
|
||||
allow_nil: true,
|
||||
prefix: true
|
||||
|
||||
attr_accessor :author_id_of_changes
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def search(query)
|
||||
where("title like :query", query: "%#{query}%")
|
||||
end
|
||||
end
|
||||
|
||||
def today?
|
||||
Date.today == created_at.to_date
|
||||
end
|
||||
|
||||
def new?
|
||||
today? && created_at == updated_at
|
||||
end
|
||||
|
||||
def is_assigned?
|
||||
!!assignee_id
|
||||
end
|
||||
|
||||
def is_being_reassigned?
|
||||
assignee_id_changed?
|
||||
end
|
||||
|
||||
def is_being_closed?
|
||||
closed_changed? && closed
|
||||
end
|
||||
|
||||
def is_being_reopened?
|
||||
closed_changed? && !closed
|
||||
end
|
||||
|
||||
#
|
||||
# Votes
|
||||
#
|
||||
|
||||
# Return the number of -1 comments (downvotes)
|
||||
def downvotes
|
||||
notes.select(&:downvote?).size
|
||||
end
|
||||
|
||||
def downvotes_in_percent
|
||||
if votes_count.zero?
|
||||
0
|
||||
else
|
||||
100.0 - upvotes_in_percent
|
||||
end
|
||||
end
|
||||
|
||||
# Return the number of +1 comments (upvotes)
|
||||
def upvotes
|
||||
notes.select(&:upvote?).size
|
||||
end
|
||||
|
||||
def upvotes_in_percent
|
||||
if votes_count.zero?
|
||||
0
|
||||
else
|
||||
100.0 / votes_count * upvotes
|
||||
end
|
||||
end
|
||||
|
||||
# Return the total number of votes
|
||||
def votes_count
|
||||
upvotes + downvotes
|
||||
end
|
||||
end
|
|
@ -15,9 +15,6 @@
|
|||
#
|
||||
|
||||
class Event < ActiveRecord::Base
|
||||
include NoteEvent
|
||||
include PushEvent
|
||||
|
||||
attr_accessible :project, :action, :data, :author_id, :project_id,
|
||||
:target_id, :target_type
|
||||
|
||||
|
@ -113,26 +110,6 @@ class Event < ActiveRecord::Base
|
|||
target_type == "MergeRequest"
|
||||
end
|
||||
|
||||
def new_issue?
|
||||
target_type == "Issue" &&
|
||||
action == Created
|
||||
end
|
||||
|
||||
def new_merge_request?
|
||||
target_type == "MergeRequest" &&
|
||||
action == Created
|
||||
end
|
||||
|
||||
def changed_merge_request?
|
||||
target_type == "MergeRequest" &&
|
||||
[Closed, Reopened].include?(action)
|
||||
end
|
||||
|
||||
def changed_issue?
|
||||
target_type == "Issue" &&
|
||||
[Closed, Reopened].include?(action)
|
||||
end
|
||||
|
||||
def joined?
|
||||
action == Joined
|
||||
end
|
||||
|
@ -170,4 +147,143 @@ class Event < ActiveRecord::Base
|
|||
"opened"
|
||||
end
|
||||
end
|
||||
|
||||
def valid_push?
|
||||
data[:ref]
|
||||
rescue => ex
|
||||
false
|
||||
end
|
||||
|
||||
def tag?
|
||||
data[:ref]["refs/tags"]
|
||||
end
|
||||
|
||||
def branch?
|
||||
data[:ref]["refs/heads"]
|
||||
end
|
||||
|
||||
def new_branch?
|
||||
commit_from =~ /^00000/
|
||||
end
|
||||
|
||||
def new_ref?
|
||||
commit_from =~ /^00000/
|
||||
end
|
||||
|
||||
def rm_ref?
|
||||
commit_to =~ /^00000/
|
||||
end
|
||||
|
||||
def md_ref?
|
||||
!(rm_ref? || new_ref?)
|
||||
end
|
||||
|
||||
def commit_from
|
||||
data[:before]
|
||||
end
|
||||
|
||||
def commit_to
|
||||
data[:after]
|
||||
end
|
||||
|
||||
def ref_name
|
||||
if tag?
|
||||
tag_name
|
||||
else
|
||||
branch_name
|
||||
end
|
||||
end
|
||||
|
||||
def branch_name
|
||||
@branch_name ||= data[:ref].gsub("refs/heads/", "")
|
||||
end
|
||||
|
||||
def tag_name
|
||||
@tag_name ||= data[:ref].gsub("refs/tags/", "")
|
||||
end
|
||||
|
||||
# Max 20 commits from push DESC
|
||||
def commits
|
||||
@commits ||= data[:commits].map { |commit| repository.commit(commit[:id]) }.reverse
|
||||
end
|
||||
|
||||
def commits_count
|
||||
data[:total_commits_count] || commits.count || 0
|
||||
end
|
||||
|
||||
def ref_type
|
||||
tag? ? "tag" : "branch"
|
||||
end
|
||||
|
||||
def push_action_name
|
||||
if new_ref?
|
||||
"pushed new"
|
||||
elsif rm_ref?
|
||||
"deleted"
|
||||
else
|
||||
"pushed to"
|
||||
end
|
||||
end
|
||||
|
||||
def repository
|
||||
project.repository
|
||||
end
|
||||
|
||||
def parent_commit
|
||||
repository.commit(commit_from)
|
||||
rescue => ex
|
||||
nil
|
||||
end
|
||||
|
||||
def last_commit
|
||||
repository.commit(commit_to)
|
||||
rescue => ex
|
||||
nil
|
||||
end
|
||||
|
||||
def push_with_commits?
|
||||
md_ref? && commits.any? && parent_commit && last_commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def last_push_to_non_root?
|
||||
branch? && project.default_branch != branch_name
|
||||
end
|
||||
|
||||
def note_commit_id
|
||||
target.commit_id
|
||||
end
|
||||
|
||||
def note_short_commit_id
|
||||
note_commit_id[0..8]
|
||||
end
|
||||
|
||||
def note_commit?
|
||||
target.noteable_type == "Commit"
|
||||
end
|
||||
|
||||
def note_target
|
||||
target.noteable
|
||||
end
|
||||
|
||||
def note_target_id
|
||||
if note_commit?
|
||||
target.commit_id
|
||||
else
|
||||
target.noteable_id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def wall_note?
|
||||
target.noteable_type.blank?
|
||||
end
|
||||
|
||||
def note_target_type
|
||||
if target.noteable_type.present?
|
||||
target.noteable_type.titleize
|
||||
else
|
||||
"Wall"
|
||||
end.downcase
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,20 +23,12 @@ class GitlabCiService < Service
|
|||
|
||||
after_save :compose_service_hook, if: :activated?
|
||||
|
||||
def activated?
|
||||
active
|
||||
end
|
||||
|
||||
def compose_service_hook
|
||||
hook = service_hook || build_service_hook
|
||||
hook.url = [project_url, "/build", "?token=#{token}"].join("")
|
||||
hook.save
|
||||
end
|
||||
|
||||
def commit_badge_path sha
|
||||
project_url + "/status?sha=#{sha}"
|
||||
end
|
||||
|
||||
def commit_status_path sha
|
||||
project_url + "/builds/#{sha}/status.json?token=#{token}"
|
||||
end
|
||||
|
|
|
@ -12,6 +12,14 @@
|
|||
#
|
||||
|
||||
class Group < Namespace
|
||||
def add_users_to_project_teams(user_ids, project_access)
|
||||
UsersProject.add_users_into_projects(
|
||||
projects.map(&:id),
|
||||
user_ids,
|
||||
project_access
|
||||
)
|
||||
end
|
||||
|
||||
def users
|
||||
users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})
|
||||
users = users << owner
|
||||
|
@ -21,4 +29,8 @@ class Group < Namespace
|
|||
def human_name
|
||||
name
|
||||
end
|
||||
|
||||
def truncate_teams
|
||||
UsersProject.truncate_teams(project_ids)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
#
|
||||
|
||||
class Issue < ActiveRecord::Base
|
||||
include IssueCommonality
|
||||
include Votes
|
||||
include Issuable
|
||||
|
||||
attr_accessible :title, :assignee_id, :closed, :position, :description,
|
||||
:milestone_id, :label_list, :author_id_of_changes
|
||||
|
|
|
@ -73,7 +73,7 @@ class Key < ActiveRecord::Base
|
|||
if is_deploy_key
|
||||
[project]
|
||||
else
|
||||
user.projects
|
||||
user.authorized_projects
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -20,11 +20,10 @@
|
|||
#
|
||||
|
||||
require Rails.root.join("app/models/commit")
|
||||
require Rails.root.join("app/roles/static_model")
|
||||
require Rails.root.join("lib/static_model")
|
||||
|
||||
class MergeRequest < ActiveRecord::Base
|
||||
include IssueCommonality
|
||||
include Votes
|
||||
include Issuable
|
||||
|
||||
attr_accessible :title, :assignee_id, :closed, :target_branch, :source_branch, :milestone_id,
|
||||
:author_id_of_changes
|
||||
|
|
|
@ -29,7 +29,7 @@ class Milestone < ActiveRecord::Base
|
|||
|
||||
def expired?
|
||||
if due_date
|
||||
due_date < Date.today
|
||||
due_date.past?
|
||||
else
|
||||
false
|
||||
end
|
||||
|
@ -58,7 +58,13 @@ class Milestone < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def expires_at
|
||||
"expires at #{due_date.stamp("Aug 21, 2011")}" if due_date
|
||||
if due_date
|
||||
if due_date.past?
|
||||
"expired at #{due_date.stamp("Aug 21, 2011")}"
|
||||
else
|
||||
"expires at #{due_date.stamp("Aug 21, 2011")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def can_be_closed?
|
||||
|
|
|
@ -27,10 +27,13 @@ class Namespace < ActiveRecord::Base
|
|||
|
||||
after_create :ensure_dir_exist
|
||||
after_update :move_dir
|
||||
after_commit :update_gitolite, on: :update, if: :require_update_gitolite
|
||||
after_destroy :rm_dir
|
||||
|
||||
scope :root, where('type IS NULL')
|
||||
|
||||
attr_accessor :require_update_gitolite
|
||||
|
||||
def self.search query
|
||||
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
|
||||
end
|
||||
|
@ -48,8 +51,17 @@ class Namespace < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def ensure_dir_exist
|
||||
namespace_dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
|
||||
system("mkdir -m 770 #{namespace_dir_path}") unless File.exists?(namespace_dir_path)
|
||||
unless dir_exists?
|
||||
FileUtils.mkdir( namespace_full_path, mode: 0770 )
|
||||
end
|
||||
end
|
||||
|
||||
def dir_exists?
|
||||
File.exists?(namespace_full_path)
|
||||
end
|
||||
|
||||
def namespace_full_path
|
||||
@namespace_full_path ||= File.join(Gitlab.config.gitolite.repos_path, path)
|
||||
end
|
||||
|
||||
def move_dir
|
||||
|
@ -59,16 +71,25 @@ class Namespace < ActiveRecord::Base
|
|||
if File.exists?(new_path)
|
||||
raise "Already exists"
|
||||
end
|
||||
|
||||
if system("mv #{old_path} #{new_path}")
|
||||
|
||||
begin
|
||||
FileUtils.mv( old_path, new_path )
|
||||
send_update_instructions
|
||||
@require_update_gitolite = true
|
||||
rescue Exception => e
|
||||
raise "Namespace move error #{old_path} #{new_path}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_gitolite
|
||||
@require_update_gitolite = false
|
||||
projects.each(&:update_repository)
|
||||
end
|
||||
|
||||
def rm_dir
|
||||
dir_path = File.join(Gitlab.config.gitolite.repos_path, path)
|
||||
system("rm -rf #{dir_path}")
|
||||
FileUtils.rm_r( dir_path, force: true )
|
||||
end
|
||||
|
||||
def send_update_instructions
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#
|
||||
# id :integer not null, primary key
|
||||
# note :text
|
||||
# noteable_id :string(255)
|
||||
# noteable_type :string(255)
|
||||
# author_id :integer
|
||||
# created_at :datetime not null
|
||||
|
@ -12,6 +11,8 @@
|
|||
# project_id :integer
|
||||
# attachment :string(255)
|
||||
# line_code :string(255)
|
||||
# commit_id :string(255)
|
||||
# noteable_id :integer
|
||||
#
|
||||
|
||||
require 'carrierwave/orm/activerecord'
|
||||
|
@ -41,11 +42,11 @@ class Note < ActiveRecord::Base
|
|||
mount_uploader :attachment, AttachmentUploader
|
||||
|
||||
# Scopes
|
||||
scope :for_commits, ->{ where(noteable_type: "Commit") }
|
||||
scope :common, ->{ where(noteable_id: nil, commit_id: nil) }
|
||||
scope :today, ->{ where("created_at >= :date", date: Date.today) }
|
||||
scope :last_week, ->{ where("created_at >= :date", date: (Date.today - 7.days)) }
|
||||
scope :since, ->(day) { where("created_at >= :date", date: (day)) }
|
||||
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
|
||||
scope :inline, where("line_code IS NOT NULL")
|
||||
scope :not_inline, where("line_code IS NULL")
|
||||
|
||||
scope :common, ->{ where(noteable_type: ["", nil]) }
|
||||
scope :fresh, ->{ order("created_at ASC, id ASC") }
|
||||
scope :inc_author_project, ->{ includes(:project, :author) }
|
||||
scope :inc_author, ->{ includes(:author) }
|
||||
|
@ -126,7 +127,7 @@ class Note < ActiveRecord::Base
|
|||
# override to return commits, which are not active record
|
||||
def noteable
|
||||
if for_commit?
|
||||
project.commit(commit_id)
|
||||
project.repository.commit(commit_id)
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# private_flag :boolean default(TRUE), not null
|
||||
# owner_id :integer
|
||||
# creator_id :integer
|
||||
# default_branch :string(255)
|
||||
# issues_enabled :boolean default(TRUE), not null
|
||||
# wall_enabled :boolean default(TRUE), not null
|
||||
|
@ -21,18 +21,14 @@
|
|||
require "grit"
|
||||
|
||||
class Project < ActiveRecord::Base
|
||||
include Repository
|
||||
include PushObserver
|
||||
include Authority
|
||||
include Team
|
||||
include NamespacedProject
|
||||
include Gitolited
|
||||
|
||||
class TransferError < StandardError; end
|
||||
|
||||
attr_accessible :name, :path, :description, :default_branch, :issues_enabled,
|
||||
:wall_enabled, :merge_requests_enabled, :wiki_enabled, as: [:default, :admin]
|
||||
|
||||
attr_accessible :namespace_id, :owner_id, as: :admin
|
||||
attr_accessible :namespace_id, :creator_id, as: :admin
|
||||
|
||||
attr_accessor :error_code
|
||||
|
||||
|
@ -40,10 +36,10 @@ class Project < ActiveRecord::Base
|
|||
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
|
||||
belongs_to :namespace
|
||||
|
||||
# TODO: replace owner with creator.
|
||||
# With namespaces a project owner will be a namespace owner
|
||||
# so this field makes sense only for global projects
|
||||
belongs_to :owner, class_name: "User"
|
||||
belongs_to :creator,
|
||||
class_name: "User",
|
||||
foreign_key: "creator_id"
|
||||
|
||||
has_many :users, through: :users_projects
|
||||
has_many :events, dependent: :destroy
|
||||
has_many :merge_requests, dependent: :destroy
|
||||
|
@ -62,9 +58,11 @@ class Project < ActiveRecord::Base
|
|||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
|
||||
# Validations
|
||||
validates :owner, presence: true
|
||||
validates :creator, presence: true
|
||||
validates :description, length: { within: 0..2000 }
|
||||
validates :name, presence: true, length: { within: 0..255 }
|
||||
validates :name, presence: true, length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.project_name_regex,
|
||||
message: "only letters, digits, spaces & '_' '-' '.' allowed. Letter should be first" }
|
||||
validates :path, presence: true, length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.path_regex,
|
||||
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
|
||||
|
@ -77,19 +75,14 @@ class Project < ActiveRecord::Base
|
|||
validate :check_limit, :repo_name
|
||||
|
||||
# Scopes
|
||||
scope :public_only, where(private_flag: false)
|
||||
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) }
|
||||
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
|
||||
scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
|
||||
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
|
||||
scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
|
||||
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
|
||||
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
|
||||
|
||||
class << self
|
||||
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
|
||||
|
||||
def active
|
||||
joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
|
||||
end
|
||||
|
@ -101,8 +94,10 @@ class Project < ActiveRecord::Base
|
|||
def find_with_namespace(id)
|
||||
if id.include?("/")
|
||||
id = id.split("/")
|
||||
namespace_id = Namespace.find_by_path(id.first).id
|
||||
where(namespace_id: namespace_id).find_by_path(id.last)
|
||||
namespace = Namespace.find_by_path(id.first)
|
||||
return nil unless namespace
|
||||
|
||||
where(namespace_id: namespace.id).find_by_path(id.second)
|
||||
else
|
||||
where(path: id, namespace_id: nil).last
|
||||
end
|
||||
|
@ -122,7 +117,7 @@ class Project < ActiveRecord::Base
|
|||
#
|
||||
project.path = project.name.dup.parameterize
|
||||
|
||||
project.owner = user
|
||||
project.creator = user
|
||||
|
||||
# Apply namespace if user has access to it
|
||||
# else fallback to user namespace
|
||||
|
@ -162,6 +157,20 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def team
|
||||
@team ||= Team.new(self)
|
||||
end
|
||||
|
||||
def repository
|
||||
if path
|
||||
@repository ||= Repository.new(path_with_namespace, default_branch)
|
||||
else
|
||||
nil
|
||||
end
|
||||
rescue Grit::NoSuchPathError
|
||||
nil
|
||||
end
|
||||
|
||||
def git_error?
|
||||
error_code == :gitolite
|
||||
end
|
||||
|
@ -171,8 +180,8 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def check_limit
|
||||
unless owner.can_create_project?
|
||||
errors[:base] << ("Your own projects limit is #{owner.projects_limit}! Please contact administrator to increase it")
|
||||
unless creator.can_create_project?
|
||||
errors[:base] << ("Your own projects limit is #{creator.projects_limit}! Please contact administrator to increase it")
|
||||
end
|
||||
rescue
|
||||
errors[:base] << ("Can't check your ability to create project")
|
||||
|
@ -198,30 +207,10 @@ class Project < ActiveRecord::Base
|
|||
[Gitlab.config.gitlab.url, path_with_namespace].join("/")
|
||||
end
|
||||
|
||||
def common_notes
|
||||
notes.where(noteable_type: ["", nil]).inc_author_project
|
||||
end
|
||||
|
||||
def build_commit_note(commit)
|
||||
notes.new(commit_id: commit.id, noteable_type: "Commit")
|
||||
end
|
||||
|
||||
def commit_notes(commit)
|
||||
notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
|
||||
end
|
||||
|
||||
def commit_line_notes(commit)
|
||||
notes.where(commit_id: commit.id, noteable_type: "Commit").where("line_code IS NOT NULL")
|
||||
end
|
||||
|
||||
def public?
|
||||
!private_flag
|
||||
end
|
||||
|
||||
def private?
|
||||
private_flag
|
||||
end
|
||||
|
||||
def last_activity
|
||||
last_event
|
||||
end
|
||||
|
@ -262,7 +251,282 @@ class Project < ActiveRecord::Base
|
|||
|
||||
def send_move_instructions
|
||||
self.users_projects.each do |member|
|
||||
Notify.project_was_moved_email(member.id).deliver
|
||||
Notify.delay.project_was_moved_email(member.id)
|
||||
end
|
||||
end
|
||||
|
||||
def owner
|
||||
if namespace
|
||||
namespace_owner
|
||||
else
|
||||
creator
|
||||
end
|
||||
end
|
||||
|
||||
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 transfer(new_namespace)
|
||||
Project.transaction do
|
||||
old_namespace = namespace
|
||||
self.namespace = new_namespace
|
||||
|
||||
old_dir = old_namespace.try(:path) || ''
|
||||
new_dir = new_namespace.try(:path) || ''
|
||||
|
||||
old_repo = if old_dir.present?
|
||||
File.join(old_dir, self.path)
|
||||
else
|
||||
self.path
|
||||
end
|
||||
|
||||
if Project.where(path: self.path, namespace_id: new_namespace.try(:id)).present?
|
||||
raise TransferError.new("Project with same path in target namespace already exists")
|
||||
end
|
||||
|
||||
Gitlab::ProjectMover.new(self, old_dir, new_dir).execute
|
||||
|
||||
gitolite.move_repository(old_repo, self)
|
||||
|
||||
save!
|
||||
end
|
||||
rescue Gitlab::ProjectMover::ProjectMoveError => ex
|
||||
raise Project::TransferError.new(ex.message)
|
||||
end
|
||||
|
||||
def name_with_namespace
|
||||
@name_with_namespace ||= begin
|
||||
if namespace
|
||||
namespace.human_name + " / " + name
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_owner
|
||||
namespace.try(:owner)
|
||||
end
|
||||
|
||||
def path_with_namespace
|
||||
if namespace
|
||||
namespace.path + '/' + path
|
||||
else
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
# This method will be called after each post receive and only if the provided
|
||||
# user is present in GitLab.
|
||||
#
|
||||
# All callbacks for post receive should be placed here.
|
||||
def trigger_post_receive(oldrev, newrev, ref, user)
|
||||
data = post_receive_data(oldrev, newrev, ref, user)
|
||||
|
||||
# Create push event
|
||||
self.observe_push(data)
|
||||
|
||||
if push_to_branch? ref, oldrev
|
||||
# Close merged MR
|
||||
self.update_merge_requests(oldrev, newrev, ref, user)
|
||||
|
||||
# Execute web hooks
|
||||
self.execute_hooks(data.dup)
|
||||
|
||||
# Execute project services
|
||||
self.execute_services(data.dup)
|
||||
end
|
||||
|
||||
# Create satellite
|
||||
self.satellite.create unless self.satellite.exists?
|
||||
|
||||
# Discover the default branch, but only if it hasn't already been set to
|
||||
# something else
|
||||
if repository && default_branch.nil?
|
||||
update_attributes(default_branch: self.repository.discover_default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
def push_to_branch? ref, oldrev
|
||||
ref_parts = ref.split('/')
|
||||
|
||||
# Return if this is not a push to a branch (e.g. new commits)
|
||||
!(ref_parts[1] !~ /heads/ || oldrev == "00000000000000000000000000000000")
|
||||
end
|
||||
|
||||
def observe_push(data)
|
||||
Event.create(
|
||||
project: self,
|
||||
action: Event::Pushed,
|
||||
data: data,
|
||||
author_id: data[:user_id]
|
||||
)
|
||||
end
|
||||
|
||||
def execute_hooks(data)
|
||||
hooks.each { |hook| hook.execute(data) }
|
||||
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
|
||||
|
||||
# Produce a hash of post-receive data
|
||||
#
|
||||
# data = {
|
||||
# before: String,
|
||||
# after: String,
|
||||
# ref: String,
|
||||
# user_id: String,
|
||||
# user_name: String,
|
||||
# repository: {
|
||||
# name: String,
|
||||
# url: String,
|
||||
# description: String,
|
||||
# homepage: String,
|
||||
# },
|
||||
# commits: Array,
|
||||
# total_commits_count: Fixnum
|
||||
# }
|
||||
#
|
||||
def post_receive_data(oldrev, newrev, ref, user)
|
||||
|
||||
push_commits = repository.commits_between(oldrev, newrev)
|
||||
|
||||
# Total commits count
|
||||
push_commits_count = push_commits.size
|
||||
|
||||
# Get latest 20 commits ASC
|
||||
push_commits_limited = push_commits.last(20)
|
||||
|
||||
# Hash to be passed as post_receive_data
|
||||
data = {
|
||||
before: oldrev,
|
||||
after: newrev,
|
||||
ref: ref,
|
||||
user_id: user.id,
|
||||
user_name: user.name,
|
||||
repository: {
|
||||
name: name,
|
||||
url: url_to_repo,
|
||||
description: description,
|
||||
homepage: web_url,
|
||||
},
|
||||
commits: [],
|
||||
total_commits_count: push_commits_count
|
||||
}
|
||||
|
||||
# For perfomance purposes maximum 20 latest commits
|
||||
# will be passed as post receive hook data.
|
||||
#
|
||||
push_commits_limited.each do |commit|
|
||||
data[:commits] << {
|
||||
id: commit.id,
|
||||
message: commit.safe_message,
|
||||
timestamp: commit.date.xmlschema,
|
||||
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{commit.id}",
|
||||
author: {
|
||||
name: commit.author_name,
|
||||
email: commit.author_email
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def update_merge_requests(oldrev, newrev, ref, user)
|
||||
return true unless ref =~ /heads/
|
||||
branch_name = ref.gsub("refs/heads/", "")
|
||||
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
|
||||
|
||||
# Update code for merge requests
|
||||
mrs = self.merge_requests.opened.find_all_by_branch(branch_name).all
|
||||
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?
|
||||
repo
|
||||
rescue
|
||||
errors.add(:path, "Invalid repository path")
|
||||
false
|
||||
end
|
||||
|
||||
def empty_repo?
|
||||
!repository || repository.empty?
|
||||
end
|
||||
|
||||
def satellite
|
||||
@satellite ||= Gitlab::Satellite::Satellite.new(self)
|
||||
end
|
||||
|
||||
def repo
|
||||
repository.raw
|
||||
end
|
||||
|
||||
def url_to_repo
|
||||
gitolite.url_to_repo(path_with_namespace)
|
||||
end
|
||||
|
||||
def namespace_dir
|
||||
namespace.try(:path) || ''
|
||||
end
|
||||
|
||||
def update_repository
|
||||
gitolite.update_repository(self)
|
||||
end
|
||||
|
||||
def destroy_repository
|
||||
gitolite.remove_repository(self)
|
||||
end
|
||||
|
||||
def repo_exists?
|
||||
@repo_exists ||= (repository && repository.branches.present?)
|
||||
rescue
|
||||
@repo_exists = false
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def root_ref?(branch)
|
||||
repository.root_ref == branch
|
||||
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
|
||||
protected_branches.map(&:name).include?(branch_name)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
|
||||
class ProtectedBranch < ActiveRecord::Base
|
||||
include GitHost
|
||||
include Gitolited
|
||||
|
||||
attr_accessible :name
|
||||
|
||||
|
@ -22,10 +22,10 @@ class ProtectedBranch < ActiveRecord::Base
|
|||
after_destroy :update_repository
|
||||
|
||||
def update_repository
|
||||
git_host.update_repository(project)
|
||||
gitolite.update_repository(project)
|
||||
end
|
||||
|
||||
def commit
|
||||
project.commit(self.name)
|
||||
project.repository.commit(self.name)
|
||||
end
|
||||
end
|
||||
|
|
169
app/models/repository.rb
Normal file
169
app/models/repository.rb
Normal file
|
@ -0,0 +1,169 @@
|
|||
class Repository
|
||||
# Repository directory name with namespace direcotry
|
||||
# Examples:
|
||||
# gitlab/gitolite
|
||||
# diaspora
|
||||
#
|
||||
attr_accessor :path_with_namespace
|
||||
|
||||
# Grit repo object
|
||||
attr_accessor :repo
|
||||
|
||||
# Default branch in the repository
|
||||
attr_accessor :root_ref
|
||||
|
||||
def initialize(path_with_namespace, root_ref = 'master')
|
||||
@root_ref = root_ref || "master"
|
||||
@path_with_namespace = path_with_namespace
|
||||
|
||||
# Init grit repo object
|
||||
repo
|
||||
end
|
||||
|
||||
def raw
|
||||
repo
|
||||
end
|
||||
|
||||
def path_to_repo
|
||||
@path_to_repo ||= File.join(Gitlab.config.gitolite.repos_path, "#{path_with_namespace}.git")
|
||||
end
|
||||
|
||||
def repo
|
||||
@repo ||= Grit::Repo.new(path_to_repo)
|
||||
end
|
||||
|
||||
def commit(commit_id = nil)
|
||||
Commit.find_or_first(repo, commit_id, root_ref)
|
||||
end
|
||||
|
||||
def fresh_commits(n = 10)
|
||||
Commit.fresh_commits(repo, n)
|
||||
end
|
||||
|
||||
def commits_with_refs(n = 20)
|
||||
Commit.commits_with_refs(repo, n)
|
||||
end
|
||||
|
||||
def commits_since(date)
|
||||
Commit.commits_since(repo, date)
|
||||
end
|
||||
|
||||
def commits(ref, path = nil, limit = nil, offset = nil)
|
||||
Commit.commits(repo, ref, path, limit, offset)
|
||||
end
|
||||
|
||||
def last_commit_for(ref, path = nil)
|
||||
commits(ref, path, 1).first
|
||||
end
|
||||
|
||||
def commits_between(from, to)
|
||||
Commit.commits_between(repo, from, to)
|
||||
end
|
||||
|
||||
def has_post_receive_file?
|
||||
!!hook_file
|
||||
end
|
||||
|
||||
def valid_post_receive_file?
|
||||
valid_hook_file == hook_file
|
||||
end
|
||||
|
||||
def valid_hook_file
|
||||
@valid_hook_file ||= File.read(Rails.root.join('lib', 'hooks', 'post-receive'))
|
||||
end
|
||||
|
||||
def hook_file
|
||||
@hook_file ||= begin
|
||||
hook_path = File.join(path_to_repo, 'hooks', 'post-receive')
|
||||
File.read(hook_path) if File.exists?(hook_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an Array of branch names
|
||||
def branch_names
|
||||
repo.branches.collect(&:name).sort
|
||||
end
|
||||
|
||||
# Returns an Array of Branches
|
||||
def branches
|
||||
repo.branches.sort_by(&:name)
|
||||
end
|
||||
|
||||
# Returns an Array of tag names
|
||||
def tag_names
|
||||
repo.tags.collect(&:name).sort.reverse
|
||||
end
|
||||
|
||||
# Returns an Array of Tags
|
||||
def tags
|
||||
repo.tags.sort_by(&:name).reverse
|
||||
end
|
||||
|
||||
# Returns an Array of branch and tag names
|
||||
def ref_names
|
||||
[branch_names + tag_names].flatten
|
||||
end
|
||||
|
||||
def heads
|
||||
@heads ||= repo.heads
|
||||
end
|
||||
|
||||
def tree(fcommit, path = nil)
|
||||
fcommit = commit if fcommit == :head
|
||||
tree = fcommit.tree
|
||||
path ? (tree / path) : tree
|
||||
end
|
||||
|
||||
def has_commits?
|
||||
!!commit
|
||||
rescue Grit::NoSuchPathError
|
||||
false
|
||||
end
|
||||
|
||||
def empty?
|
||||
!has_commits?
|
||||
end
|
||||
|
||||
# Discovers the default branch based on the repository's available branches
|
||||
#
|
||||
# - If no branches are present, returns nil
|
||||
# - If one branch is present, returns its name
|
||||
# - If two or more branches are present, returns the one that has a name
|
||||
# matching root_ref (default_branch or 'master' if default_branch is nil)
|
||||
def discover_default_branch
|
||||
if branch_names.length == 0
|
||||
nil
|
||||
elsif branch_names.length == 1
|
||||
branch_names.first
|
||||
else
|
||||
branch_names.select { |v| v == root_ref }.first
|
||||
end
|
||||
end
|
||||
|
||||
# Archive Project to .tar.gz
|
||||
#
|
||||
# Already packed repo archives stored at
|
||||
# app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
|
||||
#
|
||||
def archive_repo(ref)
|
||||
ref = ref || self.root_ref
|
||||
commit = self.commit(ref)
|
||||
return nil unless commit
|
||||
|
||||
# Build file path
|
||||
file_name = self.path + "-" + commit.id.to_s + ".tar.gz"
|
||||
storage_path = Rails.root.join("tmp", "repositories", self.path_with_namespace)
|
||||
file_path = File.join(storage_path, file_name)
|
||||
|
||||
# Put files into a directory before archiving
|
||||
prefix = self.path + "/"
|
||||
|
||||
# Create file if not exists
|
||||
unless File.exists?(file_path)
|
||||
FileUtils.mkdir_p storage_path
|
||||
file = self.repo.archive_to_file(ref, prefix, file_path)
|
||||
end
|
||||
|
||||
file_path
|
||||
end
|
||||
end
|
|
@ -20,4 +20,8 @@ class Service < ActiveRecord::Base
|
|||
has_one :service_hook
|
||||
|
||||
validates :project_id, presence: true
|
||||
|
||||
def activated?
|
||||
active
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,6 @@ class SystemHook < WebHook
|
|||
end
|
||||
|
||||
def async_execute(data)
|
||||
Resque.enqueue(SystemHookWorker, id, data)
|
||||
Sidekiq::Client.enqueue(SystemHookWorker, id, data)
|
||||
end
|
||||
end
|
||||
|
|
118
app/models/team.rb
Normal file
118
app/models/team.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
class Team
|
||||
attr_accessor :project
|
||||
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
# Shortcut to add users
|
||||
#
|
||||
# Use:
|
||||
# @team << [@user, :master]
|
||||
# @team << [@users, :master]
|
||||
#
|
||||
def << args
|
||||
users = args.first
|
||||
|
||||
if users.respond_to?(:each)
|
||||
add_users(users, args.second)
|
||||
else
|
||||
add_user(users, args.second)
|
||||
end
|
||||
end
|
||||
|
||||
def add_user(user, access)
|
||||
add_users_ids([user.id], access)
|
||||
end
|
||||
|
||||
def add_users(users, access)
|
||||
add_users_ids(users.map(&:id), access)
|
||||
end
|
||||
|
||||
def add_users_ids(user_ids, access)
|
||||
UsersProject.add_users_into_projects(
|
||||
[project.id],
|
||||
user_ids,
|
||||
access
|
||||
)
|
||||
end
|
||||
|
||||
# Remove all users from project team
|
||||
def truncate
|
||||
UsersProject.truncate_team(project)
|
||||
end
|
||||
|
||||
def members
|
||||
project.users_projects
|
||||
end
|
||||
|
||||
def guests
|
||||
members.guests.map(&:user)
|
||||
end
|
||||
|
||||
def reporters
|
||||
members.reporters.map(&:user)
|
||||
end
|
||||
|
||||
def developers
|
||||
members.developers.map(&:user)
|
||||
end
|
||||
|
||||
def masters
|
||||
members.masters.map(&:user)
|
||||
end
|
||||
|
||||
def repository_readers
|
||||
repository_members[UsersProject::REPORTER]
|
||||
end
|
||||
|
||||
def repository_writers
|
||||
repository_members[UsersProject::DEVELOPER]
|
||||
end
|
||||
|
||||
def repository_masters
|
||||
repository_members[UsersProject::MASTER]
|
||||
end
|
||||
|
||||
def repository_members
|
||||
keys = Hash.new {|h,k| h[k] = [] }
|
||||
UsersProject.select("keys.identifier, project_access").
|
||||
joins(user: :keys).where(project_id: project.id).
|
||||
each {|row| keys[row.project_access] << [row.identifier] }
|
||||
|
||||
keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier)
|
||||
keys
|
||||
end
|
||||
|
||||
def import(source_project)
|
||||
target_project = project
|
||||
|
||||
source_team = source_project.users_projects.all
|
||||
target_team = target_project.users_projects.all
|
||||
target_user_ids = target_team.map(&:user_id)
|
||||
|
||||
source_team.reject! do |tm|
|
||||
# Skip if user already present in team
|
||||
target_user_ids.include?(tm.user_id)
|
||||
end
|
||||
|
||||
source_team.map! do |tm|
|
||||
new_tm = tm.dup
|
||||
new_tm.id = nil
|
||||
new_tm.project_id = target_project.id
|
||||
new_tm.skip_git = true
|
||||
new_tm
|
||||
end
|
||||
|
||||
UsersProject.transaction do
|
||||
source_team.each do |tm|
|
||||
tm.save
|
||||
end
|
||||
target_project.update_repository
|
||||
end
|
||||
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
end
|
|
@ -1,12 +1,13 @@
|
|||
class Tree
|
||||
include Linguist::BlobHelper
|
||||
attr_accessor :path, :tree, :project, :ref
|
||||
|
||||
attr_accessor :path, :tree, :ref
|
||||
|
||||
delegate :contents, :basename, :name, :data, :mime_type,
|
||||
:mode, :size, :text?, :colorize, to: :tree
|
||||
|
||||
def initialize(raw_tree, project, ref = nil, path = nil)
|
||||
@project, @ref, @path = project, ref, path
|
||||
def initialize(raw_tree, ref = nil, path = nil)
|
||||
@ref, @path = ref, path
|
||||
@tree = if path.present?
|
||||
raw_tree / path
|
||||
else
|
||||
|
|
|
@ -34,8 +34,6 @@
|
|||
#
|
||||
|
||||
class User < ActiveRecord::Base
|
||||
include Account
|
||||
|
||||
devise :database_authenticatable, :token_authenticatable, :lockable,
|
||||
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
|
||||
|
||||
|
@ -51,7 +49,6 @@ class User < ActiveRecord::Base
|
|||
has_many :groups, class_name: "Group", foreign_key: :owner_id
|
||||
|
||||
has_many :keys, dependent: :destroy
|
||||
has_many :projects, through: :users_projects
|
||||
has_many :users_projects, dependent: :destroy
|
||||
has_many :issues, foreign_key: :author_id, dependent: :destroy
|
||||
has_many :notes, foreign_key: :author_id, dependent: :destroy
|
||||
|
@ -70,6 +67,8 @@ class User < ActiveRecord::Base
|
|||
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
|
||||
|
||||
|
||||
validate :namespace_uniq, if: ->(user) { user.username_changed? }
|
||||
|
||||
before_validation :generate_password, on: :create
|
||||
before_save :ensure_authentication_token
|
||||
alias_attribute :private_token, :authentication_token
|
||||
|
@ -77,11 +76,14 @@ class User < ActiveRecord::Base
|
|||
delegate :path, to: :namespace, allow_nil: true, prefix: true
|
||||
|
||||
# Scopes
|
||||
scope :not_in_project, ->(project) { where("id not in (:ids)", ids: project.users.map(&:id) ) }
|
||||
scope :admins, where(admin: true)
|
||||
scope :blocked, where(blocked: true)
|
||||
scope :active, where(blocked: false)
|
||||
scope :alphabetically, order('name ASC')
|
||||
|
||||
#
|
||||
# Class methods
|
||||
#
|
||||
class << self
|
||||
def filter filter_name
|
||||
case filter_name
|
||||
|
@ -93,6 +95,14 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def not_in_project(project)
|
||||
if project.users.present?
|
||||
where("id not in (:ids)", ids: project.users.map(&:id) )
|
||||
else
|
||||
scoped
|
||||
end
|
||||
end
|
||||
|
||||
def without_projects
|
||||
where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)')
|
||||
end
|
||||
|
@ -118,9 +128,158 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Instance methods
|
||||
#
|
||||
def generate_password
|
||||
if self.force_random_password
|
||||
self.password = self.password_confirmation = Devise.friendly_token.first(8)
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_uniq
|
||||
namespace_name = self.username
|
||||
if Namespace.find_by_path(namespace_name)
|
||||
self.errors.add :username, "already exist"
|
||||
end
|
||||
end
|
||||
|
||||
# Namespaces user has access to
|
||||
def namespaces
|
||||
namespaces = []
|
||||
|
||||
# Add user account namespace
|
||||
namespaces << self.namespace if self.namespace
|
||||
|
||||
# Add groups you can manage
|
||||
namespaces += if admin
|
||||
Group.all
|
||||
else
|
||||
groups.all
|
||||
end
|
||||
namespaces
|
||||
end
|
||||
|
||||
# Groups where user is an owner
|
||||
def owned_groups
|
||||
groups
|
||||
end
|
||||
|
||||
# Groups user has access to
|
||||
def authorized_groups
|
||||
@authorized_groups ||= begin
|
||||
groups = Group.where(id: self.authorized_projects.pluck(:namespace_id)).all
|
||||
groups = groups + self.groups
|
||||
groups.uniq
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Projects user has access to
|
||||
def authorized_projects
|
||||
project_ids = users_projects.pluck(:project_id)
|
||||
project_ids = project_ids | owned_projects.pluck(:id)
|
||||
Project.where(id: project_ids)
|
||||
end
|
||||
|
||||
# Projects in user namespace
|
||||
def personal_projects
|
||||
Project.personal(self)
|
||||
end
|
||||
|
||||
# Projects where user is an owner
|
||||
def owned_projects
|
||||
Project.where("(projects.namespace_id IN (:namespaces)) OR
|
||||
(projects.namespace_id IS NULL AND projects.creator_id = :user_id)",
|
||||
namespaces: namespaces.map(&:id), user_id: self.id)
|
||||
end
|
||||
|
||||
# Team membership in personal projects
|
||||
def tm_in_personal_projects
|
||||
UsersProject.where(project_id: personal_projects.map(&:id), user_id: self.id)
|
||||
end
|
||||
|
||||
# Returns a string for use as a Gitolite user identifier
|
||||
#
|
||||
# Note that Gitolite 2.x requires the following pattern for users:
|
||||
#
|
||||
# ^@?[0-9a-zA-Z][0-9a-zA-Z._\@+-]*$
|
||||
def identifier
|
||||
# Replace non-word chars with underscores, then make sure it starts with
|
||||
# valid chars
|
||||
email.gsub(/\W/, '_').gsub(/\A([\W\_])+/, '')
|
||||
end
|
||||
|
||||
def is_admin?
|
||||
admin
|
||||
end
|
||||
|
||||
def require_ssh_key?
|
||||
keys.count == 0
|
||||
end
|
||||
|
||||
def can_create_project?
|
||||
projects_limit > personal_projects.count
|
||||
end
|
||||
|
||||
def can_create_group?
|
||||
is_admin?
|
||||
end
|
||||
|
||||
def abilities
|
||||
@abilities ||= begin
|
||||
abilities = Six.new
|
||||
abilities << Ability
|
||||
abilities
|
||||
end
|
||||
end
|
||||
|
||||
def can? action, subject
|
||||
abilities.allowed?(self, action, subject)
|
||||
end
|
||||
|
||||
def first_name
|
||||
name.split.first unless name.blank?
|
||||
end
|
||||
|
||||
def cared_merge_requests
|
||||
MergeRequest.where("author_id = :id or assignee_id = :id", id: self.id)
|
||||
end
|
||||
|
||||
# Remove user from all projects and
|
||||
# set blocked attribute to true
|
||||
def block
|
||||
users_projects.find_each do |membership|
|
||||
return false unless membership.destroy
|
||||
end
|
||||
|
||||
self.blocked = true
|
||||
save
|
||||
end
|
||||
|
||||
def projects_limit_percent
|
||||
return 100 if projects_limit.zero?
|
||||
(personal_projects.count.to_f / projects_limit) * 100
|
||||
end
|
||||
|
||||
def recent_push project_id = nil
|
||||
# Get push events not earlier than 2 hours ago
|
||||
events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
|
||||
events = events.where(project_id: project_id) if project_id
|
||||
|
||||
# Take only latest one
|
||||
events = events.recent.limit(1).first
|
||||
end
|
||||
|
||||
def projects_sorted_by_activity
|
||||
authorized_projects.sorted_by_activity
|
||||
end
|
||||
|
||||
def several_namespaces?
|
||||
namespaces.size > 1
|
||||
end
|
||||
|
||||
def namespace_id
|
||||
namespace.try :id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
|
||||
class UsersProject < ActiveRecord::Base
|
||||
include GitHost
|
||||
include Gitolited
|
||||
|
||||
GUEST = 10
|
||||
REPORTER = 20
|
||||
|
@ -23,87 +23,96 @@ class UsersProject < ActiveRecord::Base
|
|||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
||||
after_save :update_repository
|
||||
after_destroy :update_repository
|
||||
attr_accessor :skip_git
|
||||
|
||||
after_save :update_repository, unless: :skip_git?
|
||||
after_destroy :update_repository, unless: :skip_git?
|
||||
|
||||
validates :user, presence: true
|
||||
validates :user_id, uniqueness: { :scope => [:project_id], message: "already exists in project" }
|
||||
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
|
||||
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
|
||||
validates :project, presence: true
|
||||
|
||||
delegate :name, :email, to: :user, prefix: true
|
||||
|
||||
scope :guests, where(project_access: GUEST)
|
||||
scope :reporters, where(project_access: REPORTER)
|
||||
scope :developers, where(project_access: DEVELOPER)
|
||||
scope :masters, where(project_access: MASTER)
|
||||
scope :in_project, ->(project) { where(project_id: project.id) }
|
||||
|
||||
class << self
|
||||
def import_team(source_project, target_project)
|
||||
UsersProject.without_repository_callback do
|
||||
UsersProject.transaction do
|
||||
team = source_project.users_projects.all
|
||||
|
||||
team.each do |tm|
|
||||
# Skip if user already present in team
|
||||
next if target_project.users.include?(tm.user)
|
||||
# Add users to project teams with passed access option
|
||||
#
|
||||
# access can be an integer representing a access code
|
||||
# or symbol like :master representing role
|
||||
#
|
||||
# Ex.
|
||||
# add_users_into_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# UsersProject::MASTER
|
||||
# )
|
||||
#
|
||||
# add_users_into_projects(
|
||||
# project_ids,
|
||||
# user_ids,
|
||||
# :master
|
||||
# )
|
||||
#
|
||||
def add_users_into_projects(project_ids, user_ids, access)
|
||||
project_access = if roles_hash.has_key?(access)
|
||||
roles_hash[access]
|
||||
elsif roles_hash.values.include?(access.to_i)
|
||||
access
|
||||
else
|
||||
raise "Non valid access"
|
||||
end
|
||||
|
||||
new_tm = tm.dup
|
||||
new_tm.id = nil
|
||||
new_tm.project_id = target_project.id
|
||||
new_tm.save
|
||||
UsersProject.transaction do
|
||||
project_ids.each do |project_id|
|
||||
user_ids.each do |user_id|
|
||||
users_project = UsersProject.new(project_access: project_access, user_id: user_id)
|
||||
users_project.project_id = project_id
|
||||
users_project.skip_git = true
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
|
||||
end
|
||||
|
||||
target_project.update_repository
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
def without_repository_callback
|
||||
UsersProject.skip_callback(:destroy, :after, :update_repository)
|
||||
yield
|
||||
UsersProject.set_callback(:destroy, :after, :update_repository)
|
||||
end
|
||||
|
||||
def bulk_delete(project, user_ids)
|
||||
def truncate_teams(project_ids)
|
||||
UsersProject.transaction do
|
||||
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
|
||||
users_projects = UsersProject.where(project_id: project_ids)
|
||||
users_projects.each do |users_project|
|
||||
users_project.skip_git = true
|
||||
users_project.destroy
|
||||
end
|
||||
Gitlab::Gitolite.new.update_repositories(Project.where(id: project_ids))
|
||||
end
|
||||
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
def bulk_update(project, user_ids, project_access)
|
||||
UsersProject.transaction do
|
||||
UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project|
|
||||
users_project.project_access = project_access
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
def truncate_team project
|
||||
truncate_teams [project.id]
|
||||
end
|
||||
|
||||
def bulk_import(project, user_ids, project_access)
|
||||
UsersProject.transaction do
|
||||
user_ids.each do |user_id|
|
||||
users_project = UsersProject.new(
|
||||
project_access: project_access,
|
||||
user_id: user_id
|
||||
)
|
||||
users_project.project = project
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_bulk_import(user, project_ids, project_access)
|
||||
UsersProject.transaction do
|
||||
project_ids.each do |project_id|
|
||||
users_project = UsersProject.new(
|
||||
project_access: project_access,
|
||||
)
|
||||
users_project.project_id = project_id
|
||||
users_project.user_id = user.id
|
||||
users_project.save
|
||||
end
|
||||
end
|
||||
def roles_hash
|
||||
{
|
||||
guest: GUEST,
|
||||
reporter: REPORTER,
|
||||
developer: DEVELOPER,
|
||||
master: MASTER
|
||||
}
|
||||
end
|
||||
|
||||
def access_roles
|
||||
|
@ -116,12 +125,8 @@ class UsersProject < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def role_access
|
||||
project_access
|
||||
end
|
||||
|
||||
def update_repository
|
||||
git_host.update_repository(project)
|
||||
gitolite.update_repository(project)
|
||||
end
|
||||
|
||||
def project_access_human
|
||||
|
@ -131,4 +136,8 @@ class UsersProject < ActiveRecord::Base
|
|||
def repo_access_human
|
||||
self.class.access_roles.invert[self.project_access]
|
||||
end
|
||||
|
||||
def skip_git?
|
||||
!!@skip_git
|
||||
end
|
||||
end
|
||||
|
|
|
@ -50,5 +50,4 @@ class Wiki < ActiveRecord::Base
|
|||
def set_slug
|
||||
self.slug = self.title.parameterize
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue