diff options
| author | Javier Castro <javier.alejandro.castro@gmail.com> | 2014-03-04 15:09:33 -0300 |
|---|---|---|
| committer | Javier Castro <javier.alejandro.castro@gmail.com> | 2014-03-04 15:09:33 -0300 |
| commit | b61f80babb10d0583d3199d557b4e8234d981a33 (patch) | |
| tree | 747f124631f6547103bd30d4b48586382b91ae89 /app/models | |
| parent | 556ae5ae8187b89a3785219d5621e7ebc9bb7a8c (diff) | |
| parent | fbbd989770ea48d472a5c4d7d95b970542279099 (diff) | |
| download | gitlab-ce-b61f80babb10d0583d3199d557b4e8234d981a33.tar.gz | |
Merge remote-tracking branch 'upstream/master' into fix-4305
Diffstat (limited to 'app/models')
29 files changed, 612 insertions, 202 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb index cf925141f2d..69ada753d02 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -14,6 +14,7 @@ class Ability when "MergeRequest" then merge_request_abilities(user, subject) when "Group" then group_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject) + when "UsersGroup" then users_group_abilities(user, subject) else [] end.concat(global_abilities(user)) end @@ -42,7 +43,19 @@ class Ability :download_code ] else - [] + group = if subject.kind_of?(Group) + subject + elsif subject.respond_to?(:group) + subject.group + else + nil + end + + if group && group.has_projects_accessible_to?(nil) + [:read_group] + else + [] + end end end @@ -125,6 +138,8 @@ class Ability project_report_rules + [ :write_merge_request, :write_wiki, + :modify_issue, + :admin_issue, :push_code ] end @@ -169,7 +184,7 @@ class Ability def group_abilities user, group rules = [] - if group.users.include?(user) || user.admin? + if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? rules << :read_group end @@ -217,5 +232,19 @@ class Ability end end end + + def users_group_abilities(user, subject) + rules = [] + target_user = subject.user + group = subject.group + can_manage = group_abilities(user, group).include?(:manage_group) + if can_manage && (user != target_user) + rules << :modify + end + if !group.last_owner?(user) && (can_manage || (user == target_user)) + rules << :destroy + end + rules + end end end diff --git a/app/models/commit.rb b/app/models/commit.rb index dd1f9801878..c313aeb7572 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -16,29 +16,31 @@ class Commit DIFF_HARD_LIMIT_FILES = 500 DIFF_HARD_LIMIT_LINES = 10000 - def self.decorate(commits) - commits.map { |c| self.new(c) } - end + class << self + def decorate(commits) + commits.map { |c| self.new(c) } + end - # Calculate number of lines to render for diffs - def self.diff_line_count(diffs) - diffs.reduce(0){|sum, d| sum + d.diff.lines.count} - end + # Calculate number of lines to render for diffs + def diff_line_count(diffs) + diffs.reduce(0){|sum, d| sum + d.diff.lines.count} + end - def self.diff_suppress?(diffs, line_count = nil) - # optimize - check file count first - return true if diffs.size > DIFF_SAFE_FILES + def diff_suppress?(diffs, line_count = nil) + # optimize - check file count first + return true if diffs.size > DIFF_SAFE_FILES - line_count ||= Commit::diff_line_count(diffs) - line_count > DIFF_SAFE_LINES - end + line_count ||= Commit::diff_line_count(diffs) + line_count > DIFF_SAFE_LINES + end - def self.diff_force_suppress?(diffs, line_count = nil) - # optimize - check file count first - return true if diffs.size > DIFF_HARD_LIMIT_FILES + def diff_force_suppress?(diffs, line_count = nil) + # optimize - check file count first + return true if diffs.size > DIFF_HARD_LIMIT_FILES - line_count ||= Commit::diff_line_count(diffs) - line_count > DIFF_HARD_LIMIT_LINES + line_count ||= Commit::diff_line_count(diffs) + line_count > DIFF_HARD_LIMIT_LINES + end end attr_accessor :raw @@ -97,14 +99,16 @@ class Commit # # cut off, ellipses (`&hellp;`) are prepended to the commit message. def description - description = safe_message + title_end = safe_message.index(/\n/) + @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100) + "…".html_safe << safe_message[80..-1] + else + safe_message.split(/\n/, 2)[1].try(:chomp) + end + end - title_end = description.index(/\n/) - if (!title_end && description.length > 100) || (title_end && title_end > 100) - "…".html_safe << description[80..-1] - else - description.split(/\n/, 2)[1].try(:chomp) - end + def description? + description.present? end # Regular expression that identifies commit message clauses that trigger issue closing. diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 58bf621f91b..75989888bfa 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -23,7 +23,8 @@ module Issuable scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :unassigned, -> { where("assignee_id IS NULL") } scope :of_projects, ->(ids) { where(project_id: ids) } - + scope :opened, -> { with_state(:opened, :reopened) } + scope :closed, -> { with_state(:closed) } delegate :name, :email, @@ -45,6 +46,18 @@ module Issuable def search(query) where("title like :query", query: "%#{query}%") end + + def sort(method) + case method.to_s + when 'newest' then reorder("#{table_name}.created_at DESC") + when 'oldest' then reorder("#{table_name}.created_at ASC") + when 'recently_updated' then reorder("#{table_name}.updated_at DESC") + when 'last_updated' then reorder("#{table_name}.updated_at ASC") + when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC") + when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC") + else reorder("#{table_name}.created_at DESC") + end + end end def today? diff --git a/app/models/email.rb b/app/models/email.rb new file mode 100644 index 00000000000..22e71e4f107 --- /dev/null +++ b/app/models/email.rb @@ -0,0 +1,33 @@ +# == Schema Information +# +# Table name: emails +# +# id :integer not null, primary key +# user_id :integer not null +# email :string not null +# created_at :datetime not null +class Email < ActiveRecord::Base + attr_accessible :email, :user_id + + # + # Relations + # + belongs_to :user + + # + # Validations + # + validates :user_id, presence: true + validates :email, presence: true, email: { strict_mode: true }, uniqueness: true + validate :unique_email, if: ->(email) { email.email_changed? } + + before_validation :cleanup_email + + def cleanup_email + self.email = self.email.downcase.strip + end + + def unique_email + self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email) + end +end
\ No newline at end of file diff --git a/app/models/event.rb b/app/models/event.rb index ddb863c1be2..d43d6eb682f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -56,11 +56,13 @@ class Event < ActiveRecord::Base end def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads') + commit = project.repository.commit(ref.target) + if action.to_s == 'add' before = '00000000' - after = ref.commit.id + after = commit.id else - before = ref.commit.id + before = commit.id after = '00000000' end diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb index 7ebaaff61cb..613a7110d12 100644 --- a/app/models/gollum_wiki.rb +++ b/app/models/gollum_wiki.rb @@ -1,4 +1,5 @@ class GollumWiki + include Gitlab::ShellAdapter MARKUPS = { "Markdown" => :markdown, @@ -113,10 +114,6 @@ class GollumWiki "#{@user.username} #{action} page: #{title}" end - def gitlab_shell - @gitlab_shell ||= Gitlab::Shell.new - end - def path_to_repo @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") end diff --git a/app/models/group.rb b/app/models/group.rb index 0b64d5b4f7f..0d4d5f4e836 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -12,10 +12,26 @@ # description :string(255) default(""), not null # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class Group < Namespace has_many :users_groups, dependent: :destroy has_many :users, through: :users_groups + attr_accessible :avatar + + validate :avatar_type, if: ->(user) { user.avatar_changed? } + validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + + mount_uploader :avatar, AttachmentUploader + + def self.accessible_to(user) + accessible_ids = Project.accessible_to(user).pluck(:namespace_id) + accessible_ids += user.groups.pluck(:id) if user + where(id: accessible_ids) + end + def human_name name end @@ -50,4 +66,10 @@ class Group < Namespace def members users_groups end + + def avatar_type + unless self.avatar.image? + self.errors.add :avatar, "only images allowed" + end + end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 6580c5004af..a8dc6e5fd85 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -28,8 +28,6 @@ class Issue < ActiveRecord::Base scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } - scope :opened, -> { with_state(:opened, :reopened) } - scope :closed, -> { with_state(:closed) } attr_accessible :title, :assignee_id, :position, :description, :milestone_id, :label_list, :author_id_of_changes, @@ -50,9 +48,7 @@ class Issue < ActiveRecord::Base end state :opened - state :reopened - state :closed end diff --git a/app/models/key.rb b/app/models/key.rb index 79f7bbd2590..29a76f53f3d 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -53,7 +53,7 @@ class Key < ActiveRecord::Base Tempfile.open('gitlab_key_file') do |file| file.puts key file.rewind - cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp') + cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp') end if cmd_status.zero? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e59aee8b445..4774cbcf3aa 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -31,6 +31,13 @@ class MergeRequest < ActiveRecord::Base belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" + has_one :merge_request_diff, dependent: :destroy + + after_create :create_merge_request_diff + after_update :update_merge_request_diff + + delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil + attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description attr_accessor :should_remove_source_branch @@ -45,20 +52,26 @@ class MergeRequest < ActiveRecord::Base end event :merge do - transition [:reopened, :opened] => :merged + transition [:reopened, :opened, :locked] => :merged end event :reopen do transition closed: :reopened end - state :opened + event :lock do + transition [:reopened, :opened] => :locked + end - state :reopened + event :unlock do + transition locked: :reopened + end + state :opened + state :reopened state :closed - state :merged + state :locked end state_machine :merge_status, initial: :unchecked do @@ -75,15 +88,10 @@ class MergeRequest < ActiveRecord::Base end state :unchecked - state :can_be_merged - state :cannot_be_merged end - serialize :st_commits - serialize :st_diffs - validates :source_project, presence: true, unless: :allow_broken validates :source_branch, presence: true validates :target_project, presence: true @@ -92,8 +100,6 @@ class MergeRequest < ActiveRecord::Base scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) } - scope :opened, -> { with_state(:opened) } - scope :closed, -> { with_state(:closed) } scope :merged, -> { with_state(:merged) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -105,7 +111,7 @@ class MergeRequest < ActiveRecord::Base scope :closed, -> { with_states(:closed, :merged) } def validate_branches - if target_project==source_project && target_branch == source_branch + if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" end @@ -119,9 +125,17 @@ class MergeRequest < ActiveRecord::Base end end + def update_merge_request_diff + if source_branch_changed? || target_branch_changed? + reload_code + mark_as_unchecked + end + end + def reload_code - self.reloaded_commits - self.reloaded_diffs + if merge_request_diff && opened? + merge_request_diff.reload_content + end end def check_if_can_be_merged @@ -132,42 +146,6 @@ class MergeRequest < ActiveRecord::Base end end - def diffs - @diffs ||= (load_diffs(st_diffs) || []) - end - - def reloaded_diffs - if opened? && unmerged_diffs.any? - self.st_diffs = dump_diffs(unmerged_diffs) - self.save - end - end - - def broken_diffs? - diffs == broken_diffs - rescue - true - end - - def valid_diffs? - !broken_diffs? - end - - def unmerged_diffs - diffs = if for_fork? - Gitlab::Satellite::MergeAction.new(author, self).diffs_between_satellite - else - Gitlab::Git::Diff.between(target_project.repository, source_branch, target_branch) - end - - diffs ||= [] - diffs - end - - def last_commit - commits.first - end - def merge_event self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last end @@ -176,56 +154,19 @@ class MergeRequest < ActiveRecord::Base self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end - def commits - load_commits(st_commits || []) + def automerge!(current_user, commit_message = nil) + MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) end - def probably_merged? - unmerged_commits.empty? && - commits.any? && opened? - end - - def reloaded_commits - if opened? && unmerged_commits.any? - self.st_commits = dump_commits(unmerged_commits) - save - - end - commits - end - - def unmerged_commits - if for_fork? - commits = Gitlab::Satellite::MergeAction.new(self.author, self).commits_between - else - commits = target_project.repository.commits_between(self.target_branch, self.source_branch) - end - - if commits.present? - commits = Commit.decorate(commits). - sort_by(&:created_at). - reverse - end - commits - end - - def merge!(user_id) - self.author_id_of_changes = user_id - self.merge - end - - def automerge!(current_user) - if Gitlab::Satellite::MergeAction.new(current_user, self).merge! && self.unmerged_commits.empty? - self.merge!(current_user.id) - true - end - rescue - mark_as_unmergeable - false + def open? + opened? || reopened? end def mr_and_commit_notes - commit_ids = commits.map(&:id) + # Fetch comments only from last 100 commits + commits_for_notes_limit = 100 + commit_ids = commits.last(commits_for_notes_limit).map(&:id) + project.notes.where( "(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))", mr_id: id, @@ -247,10 +188,6 @@ class MergeRequest < ActiveRecord::Base Gitlab::Satellite::MergeAction.new(current_user, self).format_patch end - def last_commit_short_sha - @last_commit_short_sha ||= last_commit.sha[0..10] - end - def for_fork? target_project != source_project end @@ -293,6 +230,14 @@ class MergeRequest < ActiveRecord::Base end end + def source_project_namespace + if source_project && source_project.namespace + source_project.namespace.path + else + "(removed)" + end + end + def source_branch_exists? return false unless self.source_project @@ -319,33 +264,32 @@ class MergeRequest < ActiveRecord::Base update_all(updated_at: Time.now) end - private - - def dump_commits(commits) - commits.map(&:to_hash) + def merge_commit_message + message = "Merge branch '#{source_branch}' into '#{target_branch}'" + message << "\n\n" + message << title.to_s + message << "\n\n" + message << description.to_s + message end - def load_commits(array) - array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } - end - - def dump_diffs(diffs) - if diffs == broken_diffs - broken_diffs - elsif diffs.respond_to?(:map) - diffs.map(&:to_hash) + # Return array of possible target branches + # dependes on target project of MR + def target_branches + if target_project.nil? + [] + else + target_project.repository.branch_names end end - def load_diffs(raw) - if raw == broken_diffs - broken_diffs - elsif raw.respond_to?(:map) - raw.map { |hash| Gitlab::Git::Diff.new(hash) } + # Return array of possible source branches + # dependes on source project of MR + def source_branches + if source_project.nil? + [] + else + source_project.repository.branch_names end end - - def broken_diffs - [Gitlab::Git::Diff::BROKEN_DIFF] - end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb new file mode 100644 index 00000000000..99afffc1db0 --- /dev/null +++ b/app/models/merge_request_diff.rb @@ -0,0 +1,155 @@ +require Rails.root.join("app/models/commit") + +class MergeRequestDiff < ActiveRecord::Base + # Prevent store of diff + # if commits amount more then 200 + COMMITS_SAFE_SIZE = 200 + + attr_reader :commits, :diffs + + belongs_to :merge_request + + attr_accessible :state, :st_commits, :st_diffs + + delegate :target_branch, :source_branch, to: :merge_request, prefix: nil + + state_machine :state, initial: :empty do + state :collected + state :timeout + state :overflow_commits_safe_size + state :overflow_diff_files_limit + state :overflow_diff_lines_limit + end + + serialize :st_commits + serialize :st_diffs + + after_create :reload_content + + def reload_content + reload_commits + reload_diffs + end + + def diffs + @diffs ||= (load_diffs(st_diffs) || []) + end + + def commits + @commits ||= load_commits(st_commits || []) + end + + def last_commit + commits.first + end + + def last_commit_short_sha + @last_commit_short_sha ||= last_commit.sha[0..10] + end + + private + + def dump_commits(commits) + commits.map(&:to_hash) + end + + def load_commits(array) + array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash)) } + end + + def dump_diffs(diffs) + if diffs.respond_to?(:map) + diffs.map(&:to_hash) + end + end + + def load_diffs(raw) + if raw.respond_to?(:map) + raw.map { |hash| Gitlab::Git::Diff.new(hash) } + end + end + + # Collect array of Git::Commit objects + # between target and source branches + def unmerged_commits + commits = if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between + else + repository.commits_between(target_branch, source_branch) + end + + if commits.present? + commits = Commit.decorate(commits). + sort_by(&:created_at). + reverse + end + + commits + end + + # Reload all commits related to current merge request from repo + # and save it as array of hashes in st_commits db field + def reload_commits + commit_objects = unmerged_commits + + if commit_objects.present? + self.st_commits = dump_commits(commit_objects) + end + + save + end + + # Reload diffs between branches related to current merge request from repo + # and save it as array of hashes in st_diffs db field + def reload_diffs + new_diffs = [] + + if commits.size.zero? + self.state = :empty + elsif commits.size > COMMITS_SAFE_SIZE + self.state = :overflow_commits_safe_size + else + new_diffs = unmerged_diffs + end + + if new_diffs.any? + if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES + self.state = :overflow_diff_files_limit + new_diffs = [] + end + + if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES + self.state = :overflow_diff_lines_limit + new_diffs = [] + end + end + + if new_diffs.present? + new_diffs = dump_commits(new_diffs) + self.state = :collected + end + + self.st_diffs = new_diffs + self.save + end + + # Collect array of Git::Diff objects + # between target and source branches + def unmerged_diffs + diffs = if merge_request.for_fork? + Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite + else + Gitlab::Git::Diff.between(repository, source_branch, target_branch) + end + + diffs ||= [] + diffs + rescue Gitlab::Git::Diff::TimeoutError => ex + self.state = :timeout + diffs = [] + end + + def repository + merge_request.target_project.repository + end +end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 8f837c72ff5..468c93bd426 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -10,6 +10,7 @@ # updated_at :datetime not null # type :string(255) # description :string(255) default(""), not null +# avatar :string(255) # class Namespace < ActiveRecord::Base @@ -26,7 +27,7 @@ class Namespace < ActiveRecord::Base format: { with: Gitlab::Regex.name_regex, message: "only letters, digits, spaces & '_' '-' '.' allowed." } validates :description, length: { within: 0..255 } - validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, + validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.path_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } @@ -46,6 +47,14 @@ class Namespace < ActiveRecord::Base def self.global_id 'GLN' end + + def projects_accessible_to(user) + projects.accessible_to(user) + end + + def has_projects_accessible_to?(user) + projects_accessible_to(user).present? + end def to_param path diff --git a/app/models/note.rb b/app/models/note.rb index 67755f44148..48c03c9d587 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -72,14 +72,20 @@ class Note < ActiveRecord::Base # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+. def create_cross_reference_note(noteable, mentioner, author, project) - create({ - noteable: noteable, - commit_id: (noteable.sha if noteable.respond_to? :sha), + note_options = { project: project, author: author, note: "_mentioned in #{mentioner.gfm_reference}_", system: true - }, without_protection: true) + } + + if noteable.kind_of?(Commit) + note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id) + else + note_options.merge!(noteable: noteable) + end + + create(note_options, without_protection: true) end def create_assignee_change_note(noteable, project, author, assignee) @@ -123,8 +129,8 @@ class Note < ActiveRecord::Base def commit_author @commit_author ||= - project.users.find_by_email(noteable.author_email) || - project.users.find_by_name(noteable.author_name) + project.users.find_by(email: noteable.author_email) || + project.users.find_by(name: noteable.author_name) rescue nil end diff --git a/app/models/notification.rb b/app/models/notification.rb index ff6a18d6a51..b0f8ed6a4ec 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -9,12 +9,23 @@ class Notification attr_accessor :target - def self.notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH] - end - - def self.project_notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] + class << self + def notification_levels + [N_DISABLED, N_PARTICIPATING, N_WATCH] + end + + def options_with_labels + { + disabled: N_DISABLED, + participating: N_PARTICIPATING, + watch: N_WATCH, + global: N_GLOBAL + } + end + + def project_notification_levels + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] + end end def initialize(target) @@ -36,4 +47,8 @@ class Notification def global? target.notification_level == N_GLOBAL end + + def level + target.notification_level + end end diff --git a/app/models/project.rb b/app/models/project.rb index a55f7a65b0b..47dc8a1fdb0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -28,6 +28,9 @@ class Project < ActiveRecord::Base include Gitlab::VisibilityLevel extend Enumerize + default_value_for :imported, false + default_value_for :archived, false + ActsAsTaggableOn.strict_case_match = true attr_accessible :name, :path, :description, :issues_tracker, :label_list, @@ -53,6 +56,7 @@ class Project < ActiveRecord::Base has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy has_one :assembla_service, dependent: :destroy + has_one :gemnasium_service, dependent: :destroy has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" has_one :forked_from_project, through: :forked_project_link @@ -114,18 +118,35 @@ class Project < ActiveRecord::Base scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } - scope :public_only, -> { where(visibility_level: PUBLIC) } - scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } + + scope :public_only, -> { where(visibility_level: Project::PUBLIC) } + scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :non_archived, -> { where(archived: false) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab class << self + def public_and_internal_levels + [Project::PUBLIC, Project::INTERNAL] + end + def abandoned where('projects.last_activity_at < ?', 6.months.ago) end + def publicish(user) + visibility_levels = [Project::PUBLIC] + visibility_levels += [Project::INTERNAL] if user + where(visibility_level: visibility_levels) + end + + def accessible_to(user) + accessible_ids = publicish(user).pluck(:id) + accessible_ids += user.authorized_projects.pluck(:id) if user + where(id: accessible_ids) + end + def with_push includes(:events).where('events.action = ?', Event::PUSHED) end @@ -138,13 +159,17 @@ class Project < ActiveRecord::Base joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") end + def search_by_title query + where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") + end + def find_with_namespace(id) if id.include?("/") id = id.split("/") - namespace = Namespace.find_by_path(id.first) + namespace = Namespace.find_by(path: id.first) return nil unless namespace - where(namespace_id: namespace.id).find_by_path(id.second) + where(namespace_id: namespace.id).find_by(path: id.second) else where(path: id, namespace_id: nil).last end @@ -201,6 +226,10 @@ class Project < ActiveRecord::Base [Gitlab.config.gitlab.url, path_with_namespace].join("/") end + def web_url_without_protocol + web_url.split("://")[1] + end + def build_commit_note(commit) notes.new(commit_id: commit.id, noteable_type: "Commit") end @@ -248,7 +277,7 @@ class Project < ActiveRecord::Base end def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push) + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium) end def gitlab_ci? @@ -270,9 +299,7 @@ class Project < ActiveRecord::Base end def send_move_instructions - team.members.each do |user| - Notify.delay.project_was_moved_email(self.id, user.id) - end + NotificationService.new.project_was_moved(self) end def owner @@ -290,7 +317,7 @@ class Project < ActiveRecord::Base # Get Team Member record by user id def team_member_by_id(user_id) - users_projects.find_by_user_id(user_id) + users_projects.find_by(user_id: user_id) end def name_with_namespace @@ -344,7 +371,7 @@ class Project < ActiveRecord::Base # Close merge requests mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - mrs.each { |merge_request| merge_request.merge!(user.id) } + mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) } true end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 66ecf394784..2a2c5172916 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -13,9 +13,12 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class AssemblaService < Service + attr_accessible :subdomain + include HTTParty validates :token, presence: true, if: :activated? @@ -34,12 +37,13 @@ class AssemblaService < Service def fields [ - { type: 'text', name: 'token', placeholder: '' } + { type: 'text', name: 'token', placeholder: '' }, + { type: 'text', name: 'subdomain', placeholder: '' } ] end def execute(push) - url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}" + url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}" AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' }) end end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index fb2a49fd586..f9247e054c7 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class CampfireService < Service diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 2a46eff7846..0a453166342 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class EmailsOnPushService < Service diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index f72d9fa9015..2603a1f67a4 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # require "flowdock-git-hook" diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb new file mode 100644 index 00000000000..0b8e7bad353 --- /dev/null +++ b/app/models/project_services/gemnasium_service.rb @@ -0,0 +1,54 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# token :string(255) +# project_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# active :boolean default(FALSE), not null +# project_url :string(255) +# subdomain :string(255) +# room :string(255) +# api_key :string(255) +# + +require "gemnasium/gitlab_service" + +class GemnasiumService < Service + validates :token, :api_key, presence: true, if: :activated? + + def title + 'Gemnasium' + end + + def description + 'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.' + end + + def to_param + 'gemnasium' + end + + def fields + [ + { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ' }, + { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com' } + ] + end + + def execute(push_data) + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + Gemnasium::GitlabService.execute( + ref: push_data[:ref], + before: push_data[:before], + after: push_data[:after], + token: token, + api_key: api_key, + repo: repo_path + ) + end +end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 7f5380a4551..017cd9eeaab 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class GitlabCiService < Service diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index ea2169fb168..3cee047a32a 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class HipchatService < Service @@ -40,7 +41,7 @@ class HipchatService < Service end def execute(push_data) - gate[room].send('Gitlab', create_message(push_data)) + gate[room].send('GitLab', create_message(push_data)) end private @@ -61,7 +62,7 @@ class HipchatService < Service elsif after =~ /000000/ message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n" else - message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " + message << "pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" for commit in push[:commits] do diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index c5b1b9ab8d3..877b9a77404 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -13,6 +13,7 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # class PivotaltrackerService < Service diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 5630f280aea..eca13e56061 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -22,22 +22,22 @@ class ProjectTeam end def find(user_id) - user = project.users.find_by_id(user_id) + user = project.users.find_by(id: user_id) if group - user ||= group.users.find_by_id(user_id) + user ||= group.users.find_by(id: user_id) end user end def find_tm(user_id) - tm = project.users_projects.find_by_user_id(user_id) + tm = project.users_projects.find_by(user_id: user_id) # If user is not in project members # we should check for group membership if group && !tm - tm = group.users_groups.find_by_user_id(user_id) + tm = group.users_groups.find_by(user_id: user_id) end tm diff --git a/app/models/repository.rb b/app/models/repository.rb index 1255b814533..35ec84f1651 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -57,7 +57,7 @@ class Repository def recent_branches(limit = 20) branches.sort do |a, b| - b.commit.committed_date <=> a.commit.committed_date + commit(b.target).committed_date <=> commit(a.target).committed_date end[0..limit] end @@ -134,6 +134,7 @@ class Repository Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:graph_log)) Rails.cache.delete(cache_key(:readme)) + Rails.cache.delete(cache_key(:contribution_guide)) end def graph_log @@ -163,7 +164,55 @@ class Repository def readme Rails.cache.fetch(cache_key(:readme)) do - Tree.new(self, self.root_ref).readme + tree(:head).readme end end + + def contribution_guide + Rails.cache.fetch(cache_key(:contribution_guide)) do + tree(:head).contribution_guide + end + end + + def head_commit + commit(self.root_ref) + end + + def tree(sha = :head, path = nil) + if sha == :head + sha = head_commit.sha + end + + Tree.new(self, sha, path) + end + + def blob_at_branch(branch_name, path) + last_commit = commit(branch_name) + + if last_commit + blob_at(last_commit.sha, path) + else + nil + end + end + + # Returns url for submodule + # + # Ex. + # @repository.submodule_url_for('master', 'rack') + # # => git@localhost:rack.git + # + def submodule_url_for(ref, path) + if submodules(ref).any? + submodule = submodules(ref)[path] + + if submodule + submodule['url'] + end + end + end + + def last_commit_for_path(sha, path) + commits(sha, path, 1).last + end end diff --git a/app/models/service.rb b/app/models/service.rb index 540aaad1ce5..f7e440dcc81 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -13,12 +13,15 @@ # project_url :string(255) # subdomain :string(255) # room :string(255) +# api_key :string(255) # # To add new service you should build a class inherited from Service # and implement a set of methods class Service < ActiveRecord::Base - attr_accessible :title, :token, :type, :active + default_value_for :active, false + + attr_accessible :title, :token, :type, :active, :api_key belongs_to :project has_one :service_hook diff --git a/app/models/tree.rb b/app/models/tree.rb index ed06cb1a128..ac2183be44b 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,5 +1,5 @@ class Tree - attr_accessor :entries, :readme + attr_accessor :entries, :readme, :contribution_guide def initialize(repository, sha, path = '/') path = '/' if path.blank? @@ -10,6 +10,11 @@ class Tree readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) end + + if contribution_tree = @entries.find(&:contributing?) + contribution_path = path == '/' ? contribution_tree.name : File.join(path, contribution_tree.name) + @contribution_guide = Gitlab::Git::Blob.find(git_repo, sha, contribution_path) + end end def trees @@ -23,4 +28,8 @@ class Tree def submodules @entries.select(&:submodule?) end + + def sorted_entries + trees + blobs + submodules + end end diff --git a/app/models/user.rb b/app/models/user.rb index f2cd554f9c3..855fe58ffe8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -41,7 +41,8 @@ # confirmed_at :datetime # confirmation_sent_at :datetime # unconfirmed_email :string(255) -# hide_no_ssh_key :boolean default(FALSE), not null +# hide_no_ssh_key :boolean default(FALSE) +# website_url :string(255) default(""), not null # require 'carrierwave/orm/activerecord' @@ -52,7 +53,7 @@ class User < ActiveRecord::Base :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, - :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password, + :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, as: [:default, :admin] @@ -77,6 +78,7 @@ class User < ActiveRecord::Base # Profile has_many :keys, dependent: :destroy + has_many :emails, dependent: :destroy # Groups has_many :users_groups, dependent: :destroy @@ -103,11 +105,11 @@ class User < ActiveRecord::Base # Validations # validates :name, presence: true - validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }, uniqueness: true + validates :email, presence: true, email: {strict_mode: true}, uniqueness: true validates :bio, length: { maximum: 255 }, allow_blank: true validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} - validates :username, presence: true, uniqueness: true, + validates :username, presence: true, uniqueness: { case_sensitive: false }, exclusion: { in: Gitlab::Blacklist.path }, format: { with: Gitlab::Regex.username_regex, message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } @@ -115,6 +117,7 @@ class User < ActiveRecord::Base validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? } + validate :unique_email, if: ->(user) { user.email_changed? } validates :avatar, file_size: { maximum: 100.kilobytes.to_i } before_validation :generate_password, on: :create @@ -182,6 +185,13 @@ class User < ActiveRecord::Base where(conditions).first end end + + def find_for_commit(email, name) + # Prefer email match over name match + User.where(email: email).first || + User.joins(:emails).where(emails: { email: email }).first || + User.where(name: name).first + end def filter filter_name case filter_name @@ -238,7 +248,7 @@ class User < ActiveRecord::Base def namespace_uniq namespace_name = self.username - if Namespace.find_by_path(namespace_name) + if Namespace.find_by(path: namespace_name) self.errors.add :username, "already exist" end end @@ -249,6 +259,10 @@ class User < ActiveRecord::Base end end + def unique_email + self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email) + end + # Groups user has access to def authorized_groups @authorized_groups ||= begin @@ -382,7 +396,7 @@ class User < ActiveRecord::Base end def created_by - User.find_by_id(created_by_id) if created_by_id + User.find_by(id: created_by_id) if created_by_id end def sanitize_attrs @@ -424,4 +438,18 @@ class User < ActiveRecord::Base order('id DESC').limit(1000). update_all(updated_at: Time.now) end + + def full_website_url + return "http://#{website_url}" if website_url !~ /^https?:\/\// + + website_url + end + + def short_website_url + website_url.gsub(/https?:\/\//, '') + end + + def all_ssh_keys + keys.map(&:key) + end end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index c0aa3734917..45a795391a2 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -17,6 +17,10 @@ class WebHook < ActiveRecord::Base include HTTParty + default_value_for :push_events, true + default_value_for :issues_events, false + default_value_for :merge_requests_events, false + attr_accessible :url # HTTParty timeout @@ -28,7 +32,7 @@ class WebHook < ActiveRecord::Base def execute(data) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? - WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }) + WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -38,6 +42,7 @@ class WebHook < ActiveRecord::Base WebHook.post(post_url, body: data.to_json, headers: {"Content-Type" => "application/json"}, + verify: false, basic_auth: auth) end end |
