diff options
Diffstat (limited to 'app/models')
79 files changed, 0 insertions, 8390 deletions
diff --git a/app/models/.gitkeep b/app/models/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 --- a/app/models/.gitkeep +++ /dev/null diff --git a/app/models/ability.rb b/app/models/ability.rb deleted file mode 100644 index 85a15596f8d..00000000000 --- a/app/models/ability.rb +++ /dev/null @@ -1,276 +0,0 @@ -class Ability - class << self - def allowed(user, subject) - return not_auth_abilities(user, subject) if user.nil? - return [] unless user.kind_of?(User) - return [] if user.blocked? - - case subject.class.name - when "Project" then project_abilities(user, subject) - when "Issue" then issue_abilities(user, subject) - when "Note" then note_abilities(user, subject) - when "ProjectSnippet" then project_snippet_abilities(user, subject) - when "PersonalSnippet" then personal_snippet_abilities(user, subject) - when "MergeRequest" then merge_request_abilities(user, subject) - when "Group" then group_abilities(user, subject) - when "Namespace" then namespace_abilities(user, subject) - when "GroupMember" then group_member_abilities(user, subject) - else [] - end.concat(global_abilities(user)) - end - - # List of possible abilities - # for non-authenticated user - def not_auth_abilities(user, subject) - project = if subject.kind_of?(Project) - subject - elsif subject.respond_to?(:project) - subject.project - else - nil - end - - if project && project.public? - [ - :read_project, - :read_wiki, - :read_issue, - :read_milestone, - :read_project_snippet, - :read_project_member, - :read_merge_request, - :read_note, - :download_code - ] - else - group = if subject.kind_of?(Group) - subject - elsif subject.respond_to?(:group) - subject.group - else - nil - end - - if group && group.public_profile? - [:read_group] - else - [] - end - end - end - - def global_abilities(user) - rules = [] - rules << :create_group if user.can_create_group - rules - end - - def project_abilities(user, project) - rules = [] - key = "/user/#{user.id}/project/#{project.id}" - RequestStore.store[key] ||= begin - team = project.team - - # Rules based on role in project - if team.master?(user) - rules.push(*project_master_rules) - - elsif team.developer?(user) - rules.push(*project_dev_rules) - - elsif team.reporter?(user) - rules.push(*project_report_rules) - - elsif team.guest?(user) - rules.push(*project_guest_rules) - end - - if project.public? || project.internal? - rules.push(*public_project_rules) - end - - if project.owner == user || user.admin? - rules.push(*project_admin_rules) - end - - if project.group && project.group.has_owner?(user) - rules.push(*project_admin_rules) - end - - if project.archived? - rules -= project_archived_rules - end - - rules - end - end - - def public_project_rules - project_guest_rules + [ - :download_code, - :fork_project - ] - end - - def project_guest_rules - [ - :read_project, - :read_wiki, - :read_issue, - :read_milestone, - :read_project_snippet, - :read_project_member, - :read_merge_request, - :read_note, - :write_project, - :write_issue, - :write_note - ] - end - - def project_report_rules - project_guest_rules + [ - :download_code, - :fork_project, - :write_project_snippet - ] - end - - def project_dev_rules - project_report_rules + [ - :write_merge_request, - :write_wiki, - :modify_issue, - :admin_issue, - :admin_label, - :push_code - ] - end - - def project_archived_rules - [ - :write_merge_request, - :push_code, - :push_code_to_protected_branches, - :modify_merge_request, - :admin_merge_request - ] - end - - def project_master_rules - project_dev_rules + [ - :push_code_to_protected_branches, - :modify_issue, - :modify_project_snippet, - :modify_merge_request, - :admin_issue, - :admin_milestone, - :admin_project_snippet, - :admin_project_member, - :admin_merge_request, - :admin_note, - :admin_wiki, - :admin_project - ] - end - - def project_admin_rules - project_master_rules + [ - :change_namespace, - :change_visibility_level, - :rename_project, - :remove_project, - :archive_project - ] - end - - def group_abilities(user, group) - rules = [] - - if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? - rules << :read_group - end - - # Only group masters and group owners can create new projects in group - if group.has_master?(user) || group.has_owner?(user) || user.admin? - rules.push(*[ - :create_projects, - ]) - end - - # Only group owner and administrators can admin group - if group.has_owner?(user) || user.admin? - rules.push(*[ - :admin_group, - :admin_namespace - ]) - end - - rules.flatten - end - - def namespace_abilities(user, namespace) - rules = [] - - # Only namespace owner and administrators can admin it - if namespace.owner == user || user.admin? - rules.push(*[ - :create_projects, - :admin_namespace - ]) - end - - rules.flatten - end - - [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name| - define_method "#{name}_abilities" do |user, subject| - if subject.author == user || user.is_admin? - rules = [ - :"read_#{name}", - :"write_#{name}", - :"modify_#{name}", - :"admin_#{name}" - ] - rules.push(:change_visibility_level) if subject.is_a?(Snippet) - rules - elsif subject.respond_to?(:assignee) && subject.assignee == user - [ - :"read_#{name}", - :"write_#{name}", - :"modify_#{name}", - ] - else - if subject.respond_to?(:project) - project_abilities(user, subject.project) - else - [] - end - end - end - end - - def group_member_abilities(user, subject) - rules = [] - target_user = subject.user - group = subject.group - can_manage = group_abilities(user, group).include?(:admin_group) - if can_manage && (user != target_user) - rules << :modify_group_member - rules << :destroy_group_member - end - if !group.last_owner?(user) && (can_manage || (user == target_user)) - rules << :destroy_group_member - end - rules - end - - def abilities - @abilities ||= begin - abilities = Six.new - abilities << self - abilities - end - end - end -end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb deleted file mode 100644 index 0d8365c4ff2..00000000000 --- a/app/models/application_setting.rb +++ /dev/null @@ -1,61 +0,0 @@ -# == Schema Information -# -# Table name: application_settings -# -# id :integer not null, primary key -# default_projects_limit :integer -# default_branch_protection :integer -# signup_enabled :boolean -# signin_enabled :boolean -# gravatar_enabled :boolean -# twitter_sharing_enabled :boolean -# sign_in_text :text -# created_at :datetime -# updated_at :datetime -# home_page_url :string(255) -# default_branch_protection :integer default(2) -# twitter_sharing_enabled :boolean default(TRUE) -# restricted_visibility_levels :text -# max_attachment_size :integer default(10) -# - -class ApplicationSetting < ActiveRecord::Base - serialize :restricted_visibility_levels - - validates :home_page_url, - allow_blank: true, - format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, - if: :home_page_url_column_exist - - validates_each :restricted_visibility_levels do |record, attr, value| - unless value.nil? - value.each do |level| - unless Gitlab::VisibilityLevel.options.has_value?(level) - record.errors.add(attr, "'#{level}' is not a valid visibility level") - end - end - end - end - - def self.current - ApplicationSetting.last - end - - def self.create_from_defaults - create( - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_branch_protection: Settings.gitlab['default_branch_protection'], - signup_enabled: Settings.gitlab['signup_enabled'], - signin_enabled: Settings.gitlab['signin_enabled'], - twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'], - gravatar_enabled: Settings.gravatar['enabled'], - sign_in_text: Settings.extra['sign_in_text'], - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - max_attachment_size: Settings.gitlab['max_attachment_size'] - ) - end - - def home_page_url_column_exist - ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) - end -end diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb deleted file mode 100644 index 05f5e979695..00000000000 --- a/app/models/broadcast_message.rb +++ /dev/null @@ -1,29 +0,0 @@ -# == Schema Information -# -# Table name: broadcast_messages -# -# id :integer not null, primary key -# message :text not null -# starts_at :datetime -# ends_at :datetime -# alert_type :integer -# created_at :datetime -# updated_at :datetime -# color :string(255) -# font :string(255) -# - -class BroadcastMessage < ActiveRecord::Base - include Sortable - - validates :message, presence: true - validates :starts_at, presence: true - validates :ends_at, presence: true - - validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true - validates :font, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true - - def self.current - where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last - end -end diff --git a/app/models/commit.rb b/app/models/commit.rb deleted file mode 100644 index 006fa62c8f9..00000000000 --- a/app/models/commit.rb +++ /dev/null @@ -1,155 +0,0 @@ -class Commit - include ActiveModel::Conversion - include StaticModel - extend ActiveModel::Naming - include Mentionable - - attr_mentionable :safe_message - - # Safe amount of changes (files and lines) in one commit to render - # Used to prevent 500 error on huge commits by suppressing diff - # - # User can force display of diff above this size - DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES) - DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES) - - # Commits above this size will not be rendered in HTML - DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES) - DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES) - - class << self - def decorate(commits) - commits.map do |commit| - if commit.kind_of?(Commit) - commit - else - self.new(commit) - end - end - 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 - - # Truncate sha to 8 characters - def truncate_sha(sha) - sha[0..7] - end - end - - attr_accessor :raw - - def initialize(raw_commit) - raise "Nil as raw commit passed" unless raw_commit - - @raw = raw_commit - end - - def id - @raw.id - end - - def diff_line_count - @diff_line_count ||= Commit::diff_line_count(self.diffs) - @diff_line_count - end - - # Returns a string describing the commit for use in a link title - # - # Example - # - # "Commit: Alex Denisov - Project git clone panel" - def link_title - "Commit: #{author_name} - #{title}" - end - - # Returns the commits title. - # - # Usually, the commit title is the first line of the commit message. - # In case this first line is longer than 100 characters, it is cut off - # after 80 characters and ellipses (`&hellp;`) are appended. - def title - title = safe_message - - return no_commit_message if title.blank? - - title_end = title.index("\n") - if (!title_end && title.length > 100) || (title_end && title_end > 100) - title[0..79] << "…" - else - title.split("\n", 2).first - end - end - - # Returns the commits description - # - # cut off, ellipses (`&hellp;`) are prepended to the commit message. - def description - title_end = safe_message.index("\n") - @description ||= - if (!title_end && safe_message.length > 100) || (title_end && title_end > 100) - "…" << safe_message[80..-1] - else - safe_message.split("\n", 2)[1].try(:chomp) - end - end - - def description? - description.present? - end - - def hook_attrs(project) - path_with_namespace = project.path_with_namespace - - { - id: id, - message: safe_message, - timestamp: committed_date.xmlschema, - url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}", - author: { - name: author_name, - email: author_email - } - } - end - - # Discover issues should be closed when this commit is pushed to a project's - # default branch. - def closes_issues(project, current_user = self.committer) - Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) - end - - # Mentionable override. - def gfm_reference - "commit #{id}" - end - - def author - User.find_for_commit(author_email, author_name) - end - - def committer - User.find_for_commit(committer_email, committer_name) - end - - def method_missing(m, *args, &block) - @raw.send(m, *args, &block) - end - - def respond_to?(method) - return true if @raw.respond_to?(method) - - super - end - - # Truncate sha to 8 characters - def short_id - @raw.short_id(7) - end - - def parents - @parents ||= Commit.decorate(super) - end -end diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb deleted file mode 100644 index 821ed54fb98..00000000000 --- a/app/models/concerns/internal_id.rb +++ /dev/null @@ -1,17 +0,0 @@ -module InternalId - extend ActiveSupport::Concern - - included do - validate :set_iid, on: :create - validates :iid, presence: true, numericality: true - end - - def set_iid - max_iid = project.send(self.class.name.tableize).maximum(:iid) - self.iid = max_iid.to_i + 1 - end - - def to_param - iid.to_s - end -end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb deleted file mode 100644 index 478134dff68..00000000000 --- a/app/models/concerns/issuable.rb +++ /dev/null @@ -1,189 +0,0 @@ -# == Issuable concern -# -# Contains common functionality shared between Issues and MergeRequests -# -# Used by Issue, MergeRequest -# -module Issuable - extend ActiveSupport::Concern - include Mentionable - - included do - belongs_to :author, class_name: "User" - belongs_to :assignee, class_name: "User" - belongs_to :milestone - has_many :notes, as: :noteable, dependent: :destroy - has_many :label_links, as: :target, dependent: :destroy - has_many :labels, through: :label_links - has_many :subscriptions, dependent: :destroy, as: :subscribable - - validates :author, presence: true - validates :title, presence: true, length: { within: 0..255 } - - scope :authored, ->(user) { where(author_id: user) } - scope :assigned_to, ->(u) { where(assignee_id: u.id)} - scope :recent, -> { order("created_at DESC") } - 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 :only_opened, -> { with_state(:opened) } - scope :only_reopened, -> { with_state(:reopened) } - scope :closed, -> { with_state(:closed) } - scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } - scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } - - delegate :name, - :email, - to: :author, - prefix: true - - delegate :name, - :email, - to: :assignee, - allow_nil: true, - prefix: true - - attr_mentionable :title, :description - end - - module ClassMethods - def search(query) - where("LOWER(title) like :query", query: "%#{query.downcase}%") - end - - def full_search(query) - where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%") - end - - def sort(method) - case method.to_s - when 'milestone_due_asc' then order_milestone_due_asc - when 'milestone_due_desc' then order_milestone_due_desc - else - order_by(method) - end - 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 - - # - # Votes - # - - # Return the number of -1 comments (downvotes) - def downvotes - filter_superceded_votes(notes.select(&:downvote?), notes).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 - filter_superceded_votes(notes.select(&:upvote?), notes).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 - - # Return all users participating on the discussion - def participants(current_user = self.author) - users = [] - users << author - users << assignee if is_assigned? - mentions = [] - mentions << self.mentioned_users(current_user) - - notes.each do |note| - users << note.author - mentions << note.mentioned_users(current_user) - end - - users.concat(mentions.reduce([], :|)).uniq - end - - def subscribed?(user) - subscription = subscriptions.find_by_user_id(user.id) - - if subscription - return subscription.subscribed - end - - participants(user).include?(user) - end - - def toggle_subscription(user) - subscriptions. - find_or_initialize_by(user_id: user.id). - update(subscribed: !subscribed?(user)) - end - - def to_hook_data(user) - { - object_kind: self.class.name.underscore, - user: user.hook_attrs, - object_attributes: hook_attrs - } - end - - def label_names - labels.order('title ASC').pluck(:title) - end - - def remove_labels - labels.delete_all - end - - def add_labels_by_names(label_names) - label_names.each do |label_name| - label = project.labels.create_with(color: Label::DEFAULT_COLOR). - find_or_create_by(title: label_name.strip) - self.labels << label - end - end - - private - - def filter_superceded_votes(votes, notes) - filteredvotes = [] + votes - - votes.each do |vote| - if vote.superceded?(notes) - filteredvotes.delete(vote) - end - end - - filteredvotes - end -end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb deleted file mode 100644 index b7882a2bb16..00000000000 --- a/app/models/concerns/mentionable.rb +++ /dev/null @@ -1,89 +0,0 @@ -# == Mentionable concern -# -# Contains functionality related to objects that can mention Users, Issues, MergeRequests, or Commits by -# GFM references. -# -# Used by Issue, Note, MergeRequest, and Commit. -# -module Mentionable - extend ActiveSupport::Concern - - module ClassMethods - # Indicate which attributes of the Mentionable to search for GFM references. - def attr_mentionable(*attrs) - mentionable_attrs.concat(attrs.map(&:to_s)) - end - - # Accessor for attributes marked mentionable. - def mentionable_attrs - @mentionable_attrs ||= [] - end - end - - # Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must - # be overridden if this model object can be referenced directly by GFM notation. - def gfm_reference - raise NotImplementedError.new("#{self.class} does not implement #gfm_reference") - end - - # Construct a String that contains possible GFM references. - def mentionable_text - self.class.mentionable_attrs.map { |attr| send(attr) || '' }.join - end - - # The GFM reference to this Mentionable, which shouldn't be included in its #references. - def local_reference - self - end - - # Determine whether or not a cross-reference Note has already been created between this Mentionable and - # the specified target. - def has_mentioned?(target) - Note.cross_reference_exists?(target, local_reference) - end - - def mentioned_users(current_user = nil) - return [] if mentionable_text.blank? - - ext = Gitlab::ReferenceExtractor.new(self.project, current_user) - ext.analyze(mentionable_text) - ext.users.uniq - end - - # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def references(p = project, current_user = self.author, text = mentionable_text) - return [] if text.blank? - - ext = Gitlab::ReferenceExtractor.new(p, current_user) - ext.analyze(text) - - (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference] - end - - # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. - def create_cross_references!(p = project, a = author, without = []) - refs = references(p) - without - refs.each do |ref| - Note.create_cross_reference_note(ref, local_reference, a, p) - end - end - - # If the mentionable_text field is about to change, locate any *added* references and create cross references for - # them. Invoke from an observer's #before_save implementation. - def notice_added_references(p = project, a = author) - ch = changed_attributes - original, mentionable_changed = "", false - self.class.mentionable_attrs.each do |attr| - if ch[attr] - original << ch[attr] - mentionable_changed = true - end - end - - # Only proceed if the saved changes actually include a chance to an attr_mentionable field. - return unless mentionable_changed - - preexisting = references(p, self.author, original) - create_cross_references!(p, a, preexisting) - end -end diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb deleted file mode 100644 index d7dcd97911d..00000000000 --- a/app/models/concerns/notifiable.rb +++ /dev/null @@ -1,15 +0,0 @@ -# == Notifiable concern -# -# Contains notification functionality -# -module Notifiable - extend ActiveSupport::Concern - - included do - validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true - end - - def notification - @notification ||= Notification.new(self) - end -end diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb deleted file mode 100644 index 0ad2654867d..00000000000 --- a/app/models/concerns/sortable.rb +++ /dev/null @@ -1,35 +0,0 @@ -# == Sortable concern -# -# Set default scope for ordering objects -# -module Sortable - extend ActiveSupport::Concern - - included do - # By default all models should be ordered - # by created_at field starting from newest - default_scope { order(created_at: :desc, id: :desc) } - - scope :order_created_desc, -> { reorder(created_at: :desc, id: :desc) } - scope :order_created_asc, -> { reorder(created_at: :asc, id: :asc) } - scope :order_updated_desc, -> { reorder(updated_at: :desc, id: :desc) } - scope :order_updated_asc, -> { reorder(updated_at: :asc, id: :asc) } - scope :order_name_asc, -> { reorder(name: :asc) } - scope :order_name_desc, -> { reorder(name: :desc) } - end - - module ClassMethods - def order_by(method) - case method.to_s - when 'name_asc' then order_name_asc - when 'name_desc' then order_name_desc - when 'updated_asc' then order_updated_asc - when 'updated_desc' then order_updated_desc - when 'created_asc' then order_created_asc - when 'created_desc' then order_created_desc - else - all - end - end - end -end diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb deleted file mode 100644 index bbb3b301a9f..00000000000 --- a/app/models/concerns/taskable.rb +++ /dev/null @@ -1,51 +0,0 @@ -# Contains functionality for objects that can have task lists in their -# descriptions. Task list items can be added with Markdown like "* [x] Fix -# bugs". -# -# Used by MergeRequest and Issue -module Taskable - TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze - TASK_PATTERN_HTML = /^<li>(?<p_tag>\s*<p>)?\[(?<checked>[ xX])\]/.freeze - - # Change the state of a task list item for this Taskable. Edit the object's - # description by finding the nth task item and changing its checkbox - # placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false. - # Note: task numbering starts with 1 - def update_nth_task(n, checked) - index = 0 - check_char = checked ? 'x' : ' ' - - # Do this instead of using #gsub! so that ActiveRecord detects that a field - # has changed. - self.description = self.description.gsub(TASK_PATTERN_MD) do |match| - index += 1 - case index - when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]" - else match - end - end - - save - end - - # Return true if this object's description has any task list items. - def tasks? - description && description.match(TASK_PATTERN_MD) - end - - # Return a string that describes the current state of this Taskable's task - # list items, e.g. "20 tasks (12 done, 8 unfinished)" - def task_status - return nil unless description - - num_tasks = 0 - num_done = 0 - - description.scan(TASK_PATTERN_MD) do - num_tasks += 1 - num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' ' - end - - "#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)" - end -end diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb deleted file mode 100644 index 9b88ec1cc38..00000000000 --- a/app/models/concerns/token_authenticatable.rb +++ /dev/null @@ -1,31 +0,0 @@ -module TokenAuthenticatable - extend ActiveSupport::Concern - - module ClassMethods - def find_by_authentication_token(authentication_token = nil) - if authentication_token - where(authentication_token: authentication_token).first - end - end - end - - def ensure_authentication_token - if authentication_token.blank? - self.authentication_token = generate_authentication_token - end - end - - def reset_authentication_token! - self.authentication_token = generate_authentication_token - save - end - - private - - def generate_authentication_token - loop do - token = Devise.friendly_token - break token unless self.class.unscoped.where(authentication_token: token).first - end - end -end diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb deleted file mode 100644 index 85d52d558cd..00000000000 --- a/app/models/deploy_key.rb +++ /dev/null @@ -1,38 +0,0 @@ -# == Schema Information -# -# Table name: keys -# -# id :integer not null, primary key -# user_id :integer -# created_at :datetime -# updated_at :datetime -# key :text -# public :boolean default(FALSE) -# title :string(255) -# type :string(255) -# fingerprint :string(255) -# - -class DeployKey < Key - has_many :deploy_keys_projects, dependent: :destroy - has_many :projects, through: :deploy_keys_projects - - scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } - scope :are_public, -> { where(public: true) } - - def private? - !public? - end - - def orphaned? - self.deploy_keys_projects.length == 0 - end - - def almost_orphaned? - self.deploy_keys_projects.length == 1 - end - - def destroyed_when_orphaned? - self.private? - end -end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb deleted file mode 100644 index 18db521741f..00000000000 --- a/app/models/deploy_keys_project.rb +++ /dev/null @@ -1,29 +0,0 @@ -# == Schema Information -# -# Table name: deploy_keys_projects -# -# id :integer not null, primary key -# deploy_key_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - -class DeployKeysProject < ActiveRecord::Base - belongs_to :project - belongs_to :deploy_key - - validates :deploy_key_id, presence: true - validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" } - validates :project_id, presence: true - - after_destroy :destroy_orphaned_deploy_key - - private - - def destroy_orphaned_deploy_key - return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned? - - self.deploy_key.destroy - end -end diff --git a/app/models/diff_line.rb b/app/models/diff_line.rb deleted file mode 100644 index ad37945874a..00000000000 --- a/app/models/diff_line.rb +++ /dev/null @@ -1,3 +0,0 @@ -class DiffLine - attr_accessor :type, :content, :num, :code -end diff --git a/app/models/email.rb b/app/models/email.rb deleted file mode 100644 index 556b0e9586e..00000000000 --- a/app/models/email.rb +++ /dev/null @@ -1,35 +0,0 @@ -# == Schema Information -# -# Table name: emails -# -# id :integer not null, primary key -# user_id :integer not null -# email :string(255) not null -# created_at :datetime -# updated_at :datetime -# - -class Email < ActiveRecord::Base - include Sortable - - belongs_to :user - - validates :user_id, presence: true - validates :email, presence: true, email: { strict_mode: true }, uniqueness: true - validate :unique_email, if: ->(email) { email.email_changed? } - - after_create :notify - 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 - - def notify - NotificationService.new.new_email(self) - end -end diff --git a/app/models/event.rb b/app/models/event.rb deleted file mode 100644 index c9a88ffa8e0..00000000000 --- a/app/models/event.rb +++ /dev/null @@ -1,330 +0,0 @@ -# == Schema Information -# -# Table name: events -# -# id :integer not null, primary key -# target_type :string(255) -# target_id :integer -# title :string(255) -# data :text -# project_id :integer -# created_at :datetime -# updated_at :datetime -# action :integer -# author_id :integer -# - -class Event < ActiveRecord::Base - include Sortable - default_scope { where.not(author_id: nil) } - - CREATED = 1 - UPDATED = 2 - CLOSED = 3 - REOPENED = 4 - PUSHED = 5 - COMMENTED = 6 - MERGED = 7 - JOINED = 8 # User joined project - LEFT = 9 # User left project - - delegate :name, :email, to: :author, prefix: true, allow_nil: true - delegate :title, to: :issue, prefix: true, allow_nil: true - delegate :title, to: :merge_request, prefix: true, allow_nil: true - delegate :title, to: :note, prefix: true, allow_nil: true - - belongs_to :author, class_name: "User" - belongs_to :project - belongs_to :target, polymorphic: true - - # For Hash only - serialize :data - - # Callbacks - after_create :reset_project_activity - - # Scopes - scope :recent, -> { order("created_at DESC") } - scope :code_push, -> { where(action: PUSHED) } - scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent } - scope :with_associations, -> { includes(project: :namespace) } - - class << self - def reset_event_cache_for(target) - Event.where(target_id: target.id, target_type: target.class.to_s). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - - def contributions - where("action = ? OR (target_type in (?) AND action in (?))", - Event::PUSHED, ["MergeRequest", "Issue"], - [Event::CREATED, Event::CLOSED, Event::MERGED]) - end - end - - def proper? - if push? - true - elsif membership_changed? - true - elsif created_project? - true - else - (issue? || merge_request? || note? || milestone?) && target - end - end - - def project_name - if project - project.name_with_namespace - else - "(deleted project)" - end - end - - def target_title - target.title if target && target.respond_to?(:title) - end - - def created? - action == CREATED - end - - def push? - action == PUSHED && valid_push? - end - - def merged? - action == MERGED - end - - def closed? - action == CLOSED - end - - def reopened? - action == REOPENED - end - - def joined? - action == JOINED - end - - def left? - action == LEFT - end - - def commented? - action == COMMENTED - end - - def membership_changed? - joined? || left? - end - - def created_project? - created? && !target - end - - def created_target? - created? && target - end - - def milestone? - target_type == "Milestone" - end - - def note? - target_type == "Note" - end - - def issue? - target_type == "Issue" - end - - def merge_request? - target_type == "MergeRequest" - end - - def milestone - target if milestone? - end - - def issue - target if issue? - end - - def merge_request - target if merge_request? - end - - def note - target if note? - end - - def action_name - if push? - if new_ref? - "pushed new" - elsif rm_ref? - "deleted" - else - "pushed to" - end - elsif closed? - "closed" - elsif merged? - "accepted" - elsif joined? - 'joined' - elsif left? - 'left' - elsif commented? - "commented on" - elsif created_project? - if project.import? - "imported" - else - "created" - end - else - "opened" - end - end - - def valid_push? - data[:ref] && ref_name.present? - rescue - false - end - - def tag? - Gitlab::Git.tag_ref?(data[:ref]) - end - - def branch? - Gitlab::Git.branch_ref?(data[:ref]) - end - - def new_ref? - Gitlab::Git.blank_ref?(commit_from) - end - - def rm_ref? - Gitlab::Git.blank_ref?(commit_to) - 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 ||= Gitlab::Git.ref_name(data[:ref]) - end - - def tag_name - @tag_name ||= Gitlab::Git.ref_name(data[:ref]) - end - - # Max 20 commits from push DESC - def commits - @commits ||= (data[:commits] || []).reverse - end - - def commits_count - data[:total_commits_count] || commits.count || 0 - end - - def ref_type - tag? ? "tag" : "branch" - end - - def push_with_commits? - !commits.empty? && commit_from && commit_to - end - - def last_push_to_non_root? - branch? && project.default_branch != branch_name - end - - def note_commit_id - target.commit_id - end - - def target_iid - target.respond_to?(:iid) ? target.iid : target_id - end - - def note_short_commit_id - Commit.truncate_sha(note_commit_id) - end - - def note_commit? - target.noteable_type == "Commit" - end - - def note_project_snippet? - target.noteable_type == "Snippet" - 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 note_target_iid - if note_target.respond_to?(:iid) - note_target.iid - else - note_target_id - end.to_s - end - - def note_target_type - if target.noteable_type.present? - target.noteable_type.titleize - else - "Wall" - end.downcase - end - - def body? - if push? - push_with_commits? - elsif note? - true - else - target.respond_to? :title - end - end - - def reset_project_activity - if project - project.update_column(:last_activity_at, self.created_at) - end - end -end diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb deleted file mode 100644 index 50efcb32f1b..00000000000 --- a/app/models/external_issue.rb +++ /dev/null @@ -1,25 +0,0 @@ -class ExternalIssue - def initialize(issue_identifier, project) - @issue_identifier, @project = issue_identifier, project - end - - def to_s - @issue_identifier.to_s - end - - def id - @issue_identifier.to_s - end - - def iid - @issue_identifier.to_s - end - - def ==(other) - other.is_a?(self.class) && (to_s == other.to_s) - end - - def project - @project - end -end diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb deleted file mode 100644 index 9b0c6263a96..00000000000 --- a/app/models/forked_project_link.rb +++ /dev/null @@ -1,15 +0,0 @@ -# == Schema Information -# -# Table name: forked_project_links -# -# id :integer not null, primary key -# forked_to_project_id :integer not null -# forked_from_project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - -class ForkedProjectLink < ActiveRecord::Base - belongs_to :forked_to_project, class_name: Project - belongs_to :forked_from_project, class_name: Project -end diff --git a/app/models/group.rb b/app/models/group.rb deleted file mode 100644 index 1386a9eccc9..00000000000 --- a/app/models/group.rb +++ /dev/null @@ -1,100 +0,0 @@ -# == Schema Information -# -# Table name: namespaces -# -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) -# - -require 'carrierwave/orm/activerecord' -require 'file_size_validator' - -class Group < Namespace - has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' - has_many :users, through: :group_members - - validate :avatar_type, if: ->(user) { user.avatar_changed? } - validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - - mount_uploader :avatar, AvatarUploader - - after_create :post_create_hook - after_destroy :post_destroy_hook - - class << self - def search(query) - where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%") - end - - def sort(method) - order_by(method) - end - end - - def human_name - name - end - - def owners - @owners ||= group_members.owners.map(&:user) - end - - def add_users(user_ids, access_level, current_user = nil) - user_ids.each do |user_id| - Member.add_user(self.group_members, user_id, access_level, current_user) - end - end - - def add_user(user, access_level, current_user = nil) - add_users([user], access_level, current_user) - end - - def add_owner(user, current_user = nil) - self.add_user(user, Gitlab::Access::OWNER, current_user) - end - - def has_owner?(user) - owners.include?(user) - end - - def has_master?(user) - members.masters.where(user_id: user).any? - end - - def last_owner?(user) - has_owner?(user) && owners.size == 1 - end - - def members - group_members - end - - def avatar_type - unless self.avatar.image? - self.errors.add :avatar, "only images allowed" - end - end - - def public_profile? - projects.public_only.any? - end - - def post_create_hook - system_hook_service.execute_hooks_for(self, :create) - end - - def post_destroy_hook - system_hook_service.execute_hooks_for(self, :destroy) - end - - def system_hook_service - SystemHooksService.new - end -end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb deleted file mode 100644 index 7e4f16ebf16..00000000000 --- a/app/models/group_milestone.rb +++ /dev/null @@ -1,95 +0,0 @@ -class GroupMilestone - - def initialize(title, milestones) - @title = title - @milestones = milestones - end - - def title - @title - end - - def safe_title - @title.parameterize - end - - def milestones - @milestones - end - - def projects - milestones.map { |milestone| milestone.project } - end - - def issue_count - milestones.map { |milestone| milestone.issues.count }.sum - end - - def merge_requests_count - milestones.map { |milestone| milestone.merge_requests.count }.sum - end - - def open_items_count - milestones.map { |milestone| milestone.open_items_count }.sum - end - - def closed_items_count - milestones.map { |milestone| milestone.closed_items_count }.sum - end - - def total_items_count - milestones.map { |milestone| milestone.total_items_count }.sum - end - - def percent_complete - ((closed_items_count * 100) / total_items_count).abs - rescue ZeroDivisionError - 100 - end - - def state - state = milestones.map { |milestone| milestone.state } - - if state.count('closed') == state.size - 'closed' - else - 'active' - end - end - - def active? - state == 'active' - end - - def closed? - state == 'closed' - end - - def issues - @group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) - end - - def merge_requests - @group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) - end - - def participants - @group_participants ||= milestones.map(&:participants).flatten.compact.uniq - end - - def opened_issues - issues.values_at("opened", "reopened").compact.flatten - end - - def closed_issues - issues['closed'] - end - - def opened_merge_requests - merge_requests.values_at("opened", "reopened").compact.flatten - end - - def closed_merge_requests - merge_requests.values_at("closed", "merged", "locked").compact.flatten - end -end diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb deleted file mode 100644 index 21867a9316c..00000000000 --- a/app/models/hooks/project_hook.rb +++ /dev/null @@ -1,25 +0,0 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# - -class ProjectHook < WebHook - belongs_to :project - - scope :push_hooks, -> { where(push_events: true) } - scope :tag_push_hooks, -> { where(tag_push_events: true) } - scope :issue_hooks, -> { where(issues_events: true) } - scope :merge_request_hooks, -> { where(merge_requests_events: true) } -end diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb deleted file mode 100644 index 2e11239c40b..00000000000 --- a/app/models/hooks/service_hook.rb +++ /dev/null @@ -1,20 +0,0 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# - -class ServiceHook < WebHook - belongs_to :service -end diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb deleted file mode 100644 index ee32b49bc66..00000000000 --- a/app/models/hooks/system_hook.rb +++ /dev/null @@ -1,19 +0,0 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# - -class SystemHook < WebHook -end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb deleted file mode 100644 index 315d96af1b9..00000000000 --- a/app/models/hooks/web_hook.rb +++ /dev/null @@ -1,60 +0,0 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# - -class WebHook < ActiveRecord::Base - include Sortable - include HTTParty - - default_value_for :push_events, true - default_value_for :issues_events, false - default_value_for :merge_requests_events, false - default_value_for :tag_push_events, false - - # HTTParty timeout - default_timeout Gitlab.config.gitlab.webhook_timeout - - validates :url, presence: true, - format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } - - 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" }, - verify: false) - else - post_url = url.gsub("#{parsed_url.userinfo}@", "") - auth = { - username: URI.decode(parsed_url.user), - password: URI.decode(parsed_url.password), - } - WebHook.post(post_url, - body: data.to_json, - headers: { "Content-Type" => "application/json" }, - verify: false, - basic_auth: auth) - end - rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e - logger.error("WebHook Error => #{e}") - false - end - - def async_execute(data) - Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data) - end -end diff --git a/app/models/identity.rb b/app/models/identity.rb deleted file mode 100644 index 756d19adec7..00000000000 --- a/app/models/identity.rb +++ /dev/null @@ -1,19 +0,0 @@ -# == Schema Information -# -# Table name: identities -# -# id :integer not null, primary key -# extern_uid :string(255) -# provider :string(255) -# user_id :integer -# created_at :datetime -# updated_at :datetime -# - -class Identity < ActiveRecord::Base - include Sortable - belongs_to :user - - validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } - validates :user_id, uniqueness: { scope: :provider } -end diff --git a/app/models/issue.rb b/app/models/issue.rb deleted file mode 100644 index 6e102051387..00000000000 --- a/app/models/issue.rb +++ /dev/null @@ -1,78 +0,0 @@ -# == Schema Information -# -# Table name: issues -# -# id :integer not null, primary key -# title :string(255) -# assignee_id :integer -# author_id :integer -# project_id :integer -# created_at :datetime -# updated_at :datetime -# position :integer default(0) -# branch_name :string(255) -# description :text -# milestone_id :integer -# state :string(255) -# iid :integer -# - -require 'carrierwave/orm/activerecord' -require 'file_size_validator' - -class Issue < ActiveRecord::Base - include Issuable - include InternalId - include Taskable - include Sortable - - ActsAsTaggableOn.strict_case_match = true - - belongs_to :project - validates :project, presence: true - - scope :of_group, ->(group) { where(project_id: group.project_ids) } - scope :cared, ->(user) { where(assignee_id: user) } - scope :open_for, ->(user) { opened.assigned_to(user) } - - state_machine :state, initial: :opened do - event :close do - transition [:reopened, :opened] => :closed - end - - event :reopen do - transition closed: :reopened - end - - state :opened - state :reopened - state :closed - end - - def hook_attrs - attributes - end - - # Mentionable overrides. - - def gfm_reference - "issue ##{iid}" - end - - # Reset issue events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when an issue is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - - # To allow polymorphism with MergeRequest. - def source_project - project - end -end diff --git a/app/models/key.rb b/app/models/key.rb deleted file mode 100644 index 016eee86992..00000000000 --- a/app/models/key.rb +++ /dev/null @@ -1,86 +0,0 @@ -# == Schema Information -# -# Table name: keys -# -# id :integer not null, primary key -# user_id :integer -# created_at :datetime -# updated_at :datetime -# key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) -# - -require 'digest/md5' - -class Key < ActiveRecord::Base - include Sortable - - belongs_to :user - - before_validation :strip_white_space, :generate_fingerprint - - validates :title, presence: true, length: { within: 0..255 } - validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true - validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } - - delegate :name, :email, to: :user, prefix: true - - after_create :add_to_shell - after_create :notify_user - after_create :post_create_hook - after_destroy :remove_from_shell - after_destroy :post_destroy_hook - - def strip_white_space - self.key = key.strip unless key.blank? - end - - # projects that has this key - def projects - user.authorized_projects - end - - def shell_id - "key-#{id}" - end - - def add_to_shell - GitlabShellWorker.perform_async( - :add_key, - shell_id, - key - ) - end - - def notify_user - NotificationService.new.new_key(self) - end - - def post_create_hook - SystemHooksService.new.execute_hooks_for(self, :create) - end - - def remove_from_shell - GitlabShellWorker.perform_async( - :remove_key, - shell_id, - key, - ) - end - - def post_destroy_hook - SystemHooksService.new.execute_hooks_for(self, :destroy) - end - - private - - def generate_fingerprint - self.fingerprint = nil - - return unless self.key.present? - - self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint - end -end diff --git a/app/models/label_link.rb b/app/models/label_link.rb deleted file mode 100644 index b94c9c777af..00000000000 --- a/app/models/label_link.rb +++ /dev/null @@ -1,19 +0,0 @@ -# == Schema Information -# -# Table name: label_links -# -# id :integer not null, primary key -# label_id :integer -# target_id :integer -# target_type :string(255) -# created_at :datetime -# updated_at :datetime -# - -class LabelLink < ActiveRecord::Base - belongs_to :target, polymorphic: true - belongs_to :label - - validates :target, presence: true - validates :label, presence: true -end diff --git a/app/models/member.rb b/app/models/member.rb deleted file mode 100644 index d151c7b2390..00000000000 --- a/app/models/member.rb +++ /dev/null @@ -1,172 +0,0 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string(255) not null -# user_id :integer not null -# notification_level :integer not null -# type :string(255) -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string -# invite_token :string -# invite_accepted_at :datetime -# - -class Member < ActiveRecord::Base - include Sortable - include Notifiable - include Gitlab::Access - - attr_accessor :raw_invite_token - - belongs_to :created_by, class_name: "User" - belongs_to :user - belongs_to :source, polymorphic: true - - validates :user, presence: true, unless: :invite? - validates :source, presence: true - validates :user_id, uniqueness: { scope: [:source_type, :source_id], - message: "already exists in source", - allow_nil: true } - validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true - validates :invite_email, presence: { if: :invite? }, - email: { strict_mode: true, allow_nil: true }, - uniqueness: { scope: [:source_type, :source_id], allow_nil: true } - - scope :invite, -> { where(user_id: nil) } - scope :non_invite, -> { where("user_id IS NOT NULL") } - scope :guests, -> { where(access_level: GUEST) } - scope :reporters, -> { where(access_level: REPORTER) } - scope :developers, -> { where(access_level: DEVELOPER) } - scope :masters, -> { where(access_level: MASTER) } - scope :owners, -> { where(access_level: OWNER) } - - before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } - after_create :send_invite, if: :invite? - after_create :post_create_hook, unless: :invite? - after_update :post_update_hook, unless: :invite? - after_destroy :post_destroy_hook, unless: :invite? - - delegate :name, :username, :email, to: :user, prefix: true - - class << self - def find_by_invite_token(invite_token) - invite_token = Devise.token_generator.digest(self, :invite_token, invite_token) - find_by(invite_token: invite_token) - end - - # This method is used to find users that have been entered into the "Add members" field. - # These can be the User objects directly, their IDs, their emails, or new emails to be invited. - def user_for_id(user_id) - return user_id if user_id.is_a?(User) - - user = User.find_by(id: user_id) - user ||= User.find_by(email: user_id) - user ||= user_id - user - end - - def add_user(members, user_id, access_level, current_user = nil) - user = user_for_id(user_id) - - # `user` can be either a User object or an email to be invited - if user.is_a?(User) - member = members.find_or_initialize_by(user_id: user.id) - else - member = members.build - member.invite_email = user - end - - member.created_by ||= current_user - member.access_level = access_level - - member.save - end - end - - def invite? - self.invite_token.present? - end - - def accept_invite!(new_user) - return false unless invite? - - self.invite_token = nil - self.invite_accepted_at = Time.now.utc - - self.user = new_user - - saved = self.save - - after_accept_invite if saved - - saved - end - - def decline_invite! - return false unless invite? - - destroyed = self.destroy - - after_decline_invite if destroyed - - destroyed - end - - def generate_invite_token - raw, enc = Devise.token_generator.generate(self.class, :invite_token) - @raw_invite_token = raw - self.invite_token = enc - end - - def generate_invite_token! - generate_invite_token && save(validate: false) - end - - def resend_invite - return unless invite? - - generate_invite_token! unless @raw_invite_token - - send_invite - end - - private - - def send_invite - # override in subclass - end - - def post_create_hook - system_hook_service.execute_hooks_for(self, :create) - end - - def post_update_hook - # override in subclass - end - - def post_destroy_hook - system_hook_service.execute_hooks_for(self, :destroy) - end - - def after_accept_invite - post_create_hook - end - - def after_decline_invite - # override in subclass - end - - def system_hook_service - SystemHooksService.new - end - - def notification_service - NotificationService.new - end -end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb deleted file mode 100644 index 84c91372b3f..00000000000 --- a/app/models/members/group_member.rb +++ /dev/null @@ -1,75 +0,0 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string(255) not null -# user_id :integer not null -# notification_level :integer not null -# type :string(255) -# created_at :datetime -# updated_at :datetime -# - -class GroupMember < Member - SOURCE_TYPE = 'Namespace' - - belongs_to :group, class_name: 'Group', foreign_key: 'source_id' - - # Make sure group member points only to group as it source - default_value_for :source_type, SOURCE_TYPE - default_value_for :notification_level, Notification::N_GLOBAL - validates_format_of :source_type, with: /\ANamespace\z/ - default_scope { where(source_type: SOURCE_TYPE) } - - scope :with_group, ->(group) { where(source_id: group.id) } - scope :with_user, ->(user) { where(user_id: user.id) } - - def self.access_level_roles - Gitlab::Access.options_with_owner - end - - def group - source - end - - def access_field - access_level - end - - private - - def send_invite - notification_service.invite_group_member(self, @raw_invite_token) - - super - end - - def post_create_hook - notification_service.new_group_member(self) - - super - end - - def post_update_hook - if access_level_changed? - notification_service.update_group_member(self) - end - - super - end - - def after_accept_invite - notification_service.accept_group_invite(self) - - super - end - - def after_decline_invite - notification_service.decline_group_invite(self) - - super - end -end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb deleted file mode 100644 index 0a3b4d2182b..00000000000 --- a/app/models/members/project_member.rb +++ /dev/null @@ -1,165 +0,0 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string(255) not null -# user_id :integer not null -# notification_level :integer not null -# type :string(255) -# created_at :datetime -# updated_at :datetime -# - -class ProjectMember < Member - SOURCE_TYPE = 'Project' - - include Gitlab::ShellAdapter - - belongs_to :project, class_name: 'Project', foreign_key: 'source_id' - - - # Make sure project member points only to project as it source - default_value_for :source_type, SOURCE_TYPE - default_value_for :notification_level, Notification::N_GLOBAL - validates_format_of :source_type, with: /\AProject\z/ - default_scope { where(source_type: SOURCE_TYPE) } - - scope :in_project, ->(project) { where(source_id: project.id) } - scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) } - scope :with_user, ->(user) { where(user_id: user.id) } - - class << self - - # 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, - # ProjectMember::MASTER - # ) - # - # add_users_into_projects( - # project_ids, - # user_ids, - # :master - # ) - # - def add_users_into_projects(project_ids, user_ids, access, current_user = nil) - access_level = if roles_hash.has_key?(access) - roles_hash[access] - elsif roles_hash.values.include?(access.to_i) - access - else - raise "Non valid access" - end - - users = user_ids.map { |user_id| Member.user_for_id(user_id) } - - ProjectMember.transaction do - project_ids.each do |project_id| - project = Project.find(project_id) - - users.each do |user| - Member.add_user(project.project_members, user, access_level, current_user) - end - end - end - - true - rescue - false - end - - def truncate_teams(project_ids) - ProjectMember.transaction do - members = ProjectMember.where(source_id: project_ids) - - members.each do |member| - member.destroy - end - end - - true - rescue - false - end - - def truncate_team(project) - truncate_teams [project.id] - end - - def roles_hash - Gitlab::Access.sym_options - end - - def access_roles - Gitlab::Access.options - end - end - - def access_field - access_level - end - - def project - source - end - - def owner? - project.owner == user - end - - private - - def send_invite - notification_service.invite_project_member(self, @raw_invite_token) - - super - end - - def post_create_hook - unless owner? - event_service.join_project(self.project, self.user) - notification_service.new_project_member(self) - end - - super - end - - def post_update_hook - if access_level_changed? - notification_service.update_project_member(self) - end - - super - end - - def post_destroy_hook - event_service.leave_project(self.project, self.user) - - super - end - - def after_accept_invite - notification_service.accept_project_invite(self) - - super - end - - def after_decline_invite - notification_service.decline_project_invite(self) - - super - end - - def event_service - EventCreateService.new - end -end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb deleted file mode 100644 index 9c9e2762507..00000000000 --- a/app/models/merge_request.rb +++ /dev/null @@ -1,368 +0,0 @@ -# == Schema Information -# -# Table name: merge_requests -# -# id :integer not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null -# source_project_id :integer not null -# author_id :integer -# assignee_id :integer -# title :string(255) -# created_at :datetime -# updated_at :datetime -# milestone_id :integer -# state :string(255) -# merge_status :string(255) -# target_project_id :integer not null -# iid :integer -# description :text -# position :integer default(0) -# locked_at :datetime -# - -require Rails.root.join("app/models/commit") -require Rails.root.join("lib/static_model") - -class MergeRequest < ActiveRecord::Base - include Issuable - include Taskable - include InternalId - include Sortable - - 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_accessor :should_remove_source_branch - - # When this attribute is true some MR validation is ignored - # It allows us to close or modify broken merge requests - attr_accessor :allow_broken - - # Temporary fields to store compare vars - # when creating new merge request - attr_accessor :can_be_created, :compare_failed, - :compare_commits, :compare_diffs - - state_machine :state, initial: :opened do - event :close do - transition [:reopened, :opened] => :closed - end - - event :merge do - transition [:reopened, :opened, :locked] => :merged - end - - event :reopen do - transition closed: :reopened - end - - event :lock_mr do - transition [:reopened, :opened] => :locked - end - - event :unlock_mr do - transition locked: :reopened - end - - after_transition any => :locked do |merge_request, transition| - merge_request.locked_at = Time.now - merge_request.save - end - - after_transition locked: (any - :locked) do |merge_request, transition| - merge_request.locked_at = nil - merge_request.save - end - - state :opened - state :reopened - state :closed - state :merged - state :locked - end - - state_machine :merge_status, initial: :unchecked do - event :mark_as_unchecked do - transition [:can_be_merged, :cannot_be_merged] => :unchecked - end - - event :mark_as_mergeable do - transition [:unchecked, :cannot_be_merged] => :can_be_merged - end - - event :mark_as_unmergeable do - transition [:unchecked, :can_be_merged] => :cannot_be_merged - end - - state :unchecked - state :can_be_merged - state :cannot_be_merged - - around_transition do |merge_request, transition, block| - merge_request.record_timestamps = false - begin - block.call - ensure - merge_request.record_timestamps = true - end - end - end - - validates :source_project, presence: true, unless: :allow_broken - validates :source_branch, presence: true - validates :target_project, presence: true - validates :target_branch, presence: true - validate :validate_branches - validate :validate_fork - - 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 :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) } - scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } - scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } - scope :of_projects, ->(ids) { where(target_project_id: ids) } - # Closed scope for merge request should return - # both merged and closed mr's - scope :closed, -> { with_states(:closed, :merged) } - scope :declined, -> { with_states(:closed) } - - def validate_branches - 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 - - if opened? || reopened? - similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened - similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id - if similar_mrs.any? - errors.add :validate_branches, - "Cannot Create: This merge request already exists: #{ - similar_mrs.pluck(:title) - }" - end - end - end - - def validate_fork - return true unless target_project && source_project - - if target_project == source_project - true - else - # If source and target projects are different - # we should check if source project is actually a fork of target project - if source_project.forked_from?(target_project) - true - else - errors.add :validate_fork, - 'Source project is not a fork of target project' - end - end - end - - def update_merge_request_diff - if source_branch_changed? || target_branch_changed? - reload_code - mark_as_unchecked - end - end - - def reload_code - if merge_request_diff && open? - merge_request_diff.reload_content - end - end - - def check_if_can_be_merged - if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged? - mark_as_mergeable - else - mark_as_unmergeable - end - end - - def merge_event - self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last - end - - def closed_event - self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last - end - - def automerge!(current_user, commit_message = nil) - MergeRequests::AutoMergeService. - new(target_project, current_user). - execute(self, commit_message) - end - - def open? - opened? || reopened? - end - - def mr_and_commit_notes - # 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, - commit_ids: commit_ids - ) - end - - # Returns the raw diff for this merge request - # - # see "git diff" - def to_diff(current_user) - Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite - end - - # Returns the commit as a series of email patches. - # - # see "git format-patch" - def to_patch(current_user) - Gitlab::Satellite::MergeAction.new(current_user, self).format_patch - end - - def hook_attrs - attrs = { - source: source_project.hook_attrs, - target: target_project.hook_attrs, - last_commit: nil - } - - unless last_commit.nil? - attrs.merge!(last_commit: last_commit.hook_attrs(source_project)) - end - - attributes.merge!(attrs) - end - - def for_fork? - target_project != source_project - end - - def project - target_project - end - - # Return the set of issues that will be closed if this merge request is accepted. - def closes_issues(current_user = self.author) - if target_branch == project.default_branch - issues = commits.flat_map { |c| c.closes_issues(project, current_user) } - issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). - closed_by_message(description)) - issues.uniq.sort_by(&:id) - else - [] - end - end - - # Mentionable override. - def gfm_reference - "merge request !#{iid}" - end - - def target_project_path - if target_project - target_project.path_with_namespace - else - "(removed)" - end - end - - def source_project_path - if source_project - source_project.path_with_namespace - else - "(removed)" - end - end - - def source_project_namespace - if source_project && source_project.namespace - source_project.namespace.path - else - "(removed)" - end - end - - def target_project_namespace - if target_project && target_project.namespace - target_project.namespace.path - else - "(removed)" - end - end - - def source_branch_exists? - return false unless self.source_project - - self.source_project.repository.branch_names.include?(self.source_branch) - end - - def target_branch_exists? - return false unless self.target_project - - self.target_project.repository.branch_names.include?(self.target_branch) - end - - # Reset merge request events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a merge request is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - - 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 << "\n\n" - message << "See merge request !#{iid}" - message - end - - # Return array of possible target branches - # depends on target project of MR - def target_branches - if target_project.nil? - [] - else - target_project.repository.branch_names - end - end - - # Return array of possible source branches - # depends on source project of MR - def source_branches - if source_project.nil? - [] - else - source_project.repository.branch_names - end - end - - def locked_long_ago? - return false unless locked? - - locked_at.nil? || locked_at < (Time.now - 1.day) - end -end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb deleted file mode 100644 index acac1ca4cf7..00000000000 --- a/app/models/merge_request_diff.rb +++ /dev/null @@ -1,171 +0,0 @@ -# == Schema Information -# -# Table name: merge_request_diffs -# -# id :integer not null, primary key -# state :string(255) -# st_commits :text -# st_diffs :text -# merge_request_id :integer not null -# created_at :datetime -# updated_at :datetime -# - -require Rails.root.join("app/models/commit") - -class MergeRequestDiff < ActiveRecord::Base - include Sortable - - # Prevent store of diff - # if commits amount more then 200 - COMMITS_SAFE_SIZE = 200 - - attr_reader :commits, :diffs - - belongs_to :merge_request - - 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.short_id - 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 = compare_result.commits - - 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 = compare_result.diffs - diffs ||= [] - diffs - rescue Gitlab::Git::Diff::TimeoutError => ex - self.state = :timeout - diffs = [] - end - - def repository - merge_request.target_project.repository - end - - private - - def compare_result - @compare_result ||= CompareService.new.execute( - merge_request.author, - merge_request.source_project, - merge_request.source_branch, - merge_request.target_project, - merge_request.target_branch, - ) - end -end diff --git a/app/models/milestone.rb b/app/models/milestone.rb deleted file mode 100644 index 9bbb2bafb98..00000000000 --- a/app/models/milestone.rb +++ /dev/null @@ -1,93 +0,0 @@ -# == Schema Information -# -# Table name: milestones -# -# id :integer not null, primary key -# title :string(255) not null -# project_id :integer not null -# description :text -# due_date :date -# created_at :datetime -# updated_at :datetime -# state :string(255) -# iid :integer -# - -class Milestone < ActiveRecord::Base - include InternalId - include Sortable - - belongs_to :project - has_many :issues - has_many :merge_requests - has_many :participants, through: :issues, source: :assignee - - scope :active, -> { with_state(:active) } - scope :closed, -> { with_state(:closed) } - scope :of_projects, ->(ids) { where(project_id: ids) } - - validates :title, presence: true - validates :project, presence: true - - state_machine :state, initial: :active do - event :close do - transition active: :closed - end - - event :activate do - transition closed: :active - end - - state :closed - - state :active - end - - def expired? - if due_date - due_date.past? - else - false - end - end - - def open_items_count - self.issues.opened.count + self.merge_requests.opened.count - end - - def closed_items_count - self.issues.closed.count + self.merge_requests.closed.count - end - - def total_items_count - self.issues.count + self.merge_requests.count - end - - def percent_complete - ((closed_items_count * 100) / total_items_count).abs - rescue ZeroDivisionError - 100 - end - - def expires_at - 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? - active? && issues.opened.count.zero? - end - - def is_empty? - total_items_count.zero? - end - - def author_id - nil - end -end diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb deleted file mode 100644 index 8417f200e36..00000000000 --- a/app/models/network/commit.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Network - class Commit - include ActionView::Helpers::TagHelper - - attr_accessor :time, :spaces, :parent_spaces - - def initialize(raw_commit) - @commit = raw_commit - @time = -1 - @spaces = [] - @parent_spaces = [] - end - - def method_missing(m, *args, &block) - @commit.send(m, *args, &block) - end - - def space - if @spaces.size > 0 - @spaces.first - else - 0 - end - end - - def parents(map) - @commit.parents.map do |p| - if map.include?(p.id) - map[p.id] - end - end - .compact - end - end -end diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb deleted file mode 100644 index f4e90125373..00000000000 --- a/app/models/network/graph.rb +++ /dev/null @@ -1,267 +0,0 @@ -module Network - class Graph - attr_reader :days, :commits, :map, :notes, :repo - - def self.max_count - @max_count ||= 650 - end - - def initialize(project, ref, commit, filter_ref) - @project = project - @ref = ref - @commit = commit - @filter_ref = filter_ref - @repo = project.repository - - @commits = collect_commits - @days = index_commits - @notes = collect_notes - end - - protected - - def collect_notes - h = Hash.new(0) - @project.notes.where('noteable_type = ?' ,"Commit").group('notes.commit_id').select('notes.commit_id, count(notes.id) as note_count').each do |item| - h[item.commit_id] = item.note_count.to_i - end - h - end - - # Get commits from repository - # - def collect_commits - find_commits(count_to_display_commit_in_center).map do |commit| - # Decorate with app/model/network/commit.rb - Network::Commit.new(commit) - end - end - - # Method is adding time and space on the - # list of commits. As well as returns date list - # correlated with time set on commits. - # - # @return [Array<TimeDate>] list of commit dates correlated with time on commits - def index_commits - days = [] - @map = {} - @reserved = {} - - @commits.each_with_index do |c,i| - c.time = i - days[i] = c.committed_date - @map[c.id] = c - @reserved[i] = [] - end - - commits_sort_by_ref.each do |commit| - place_chain(commit) - end - - # find parent spaces for not overlap lines - @commits.each do |c| - c.parent_spaces.concat(find_free_parent_spaces(c)) - end - - days - end - - # Skip count that the target commit is displayed in center. - def count_to_display_commit_in_center - offset = -1 - skip = 0 - while offset == -1 - tmp_commits = find_commits(skip) - if tmp_commits.size > 0 - index = tmp_commits.index do |c| - c.id == @commit.id - end - - if index - # Find the target commit - offset = index + skip - else - skip += self.class.max_count - end - else - # Can't find the target commit in the repo. - offset = 0 - end - end - - if self.class.max_count / 2 < offset then - # get max index that commit is displayed in the center. - offset - self.class.max_count / 2 - else - 0 - end - end - - def find_commits(skip = 0) - opts = { - max_count: self.class.max_count, - skip: skip - } - - opts[:ref] = @commit.id if @filter_ref - - @repo.find_commits(opts) - end - - def commits_sort_by_ref - @commits.sort do |a,b| - if include_ref?(a) - -1 - elsif include_ref?(b) - 1 - else - b.committed_date <=> a.committed_date - end - end - end - - def include_ref?(commit) - commit.ref_names(@repo).include?(@ref) - end - - def find_free_parent_spaces(commit) - spaces = [] - - commit.parents(@map).each do |parent| - range = commit.time..parent.time - - space = if commit.space >= parent.space then - find_free_parent_space(range, parent.space, -1, commit.space) - else - find_free_parent_space(range, commit.space, -1, parent.space) - end - - mark_reserved(range, space) - spaces << space - end - - spaces - end - - def find_free_parent_space(range, space_base, space_step, space_default) - if is_overlap?(range, space_default) then - find_free_space(range, space_step, space_base, space_default) - else - space_default - end - end - - def is_overlap?(range, overlap_space) - range.each do |i| - if i != range.first && - i != range.last && - @commits[i].spaces.include?(overlap_space) then - - return true; - end - end - - false - end - - # Add space mark on commit and its parents - # - # @param [::Commit] the commit object. - def place_chain(commit, parent_time = nil) - leaves = take_left_leaves(commit) - if leaves.empty? - return - end - - time_range = leaves.first.time..leaves.last.time - space_base = get_space_base(leaves) - space = find_free_space(time_range, 2, space_base) - leaves.each do |l| - l.spaces << space - end - - # and mark it as reserved - if parent_time.nil? - min_time = leaves.first.time - else - min_time = parent_time + 1 - end - - max_time = leaves.last.time - leaves.last.parents(@map).each do |parent| - if max_time < parent.time - max_time = parent.time - end - end - mark_reserved(min_time..max_time, space) - - # Visit branching chains - leaves.each do |l| - parents = l.parents(@map).select{|p| p.space.zero?} - for p in parents - place_chain(p, l.time) - end - end - end - - def get_space_base(leaves) - space_base = 1 - parents = leaves.last.parents(@map) - if parents.size > 0 - if parents.first.space > 0 - space_base = parents.first.space - end - end - space_base - end - - def mark_reserved(time_range, space) - for day in time_range - @reserved[day].push(space) - end - end - - def find_free_space(time_range, space_step, space_base = 1, space_default = nil) - space_default ||= space_base - - reserved = [] - for day in time_range - reserved.push(*@reserved[day]) - end - reserved.uniq! - - space = space_default - while reserved.include?(space) do - space += space_step - if space < space_base then - space_step *= -1 - space = space_base + space_step - end - end - - space - end - - # Takes most left subtree branch of commits - # which don't have space mark yet. - # - # @param [::Commit] the commit object. - # - # @return [Array<Network::Commit>] list of branch commits - def take_left_leaves(raw_commit) - commit = @map[raw_commit.id] - leaves = [] - leaves.push(commit) if commit.space.zero? - - while true - return leaves if commit.parents(@map).count.zero? - - commit = commit.parents(@map).first - - return leaves unless commit.space.zero? - - leaves.push(commit) - end - end - end -end diff --git a/app/models/note.rb b/app/models/note.rb deleted file mode 100644 index 2cf3fac2def..00000000000 --- a/app/models/note.rb +++ /dev/null @@ -1,609 +0,0 @@ -# == Schema Information -# -# Table name: notes -# -# id :integer not null, primary key -# note :text -# noteable_type :string(255) -# author_id :integer -# created_at :datetime -# updated_at :datetime -# project_id :integer -# attachment :string(255) -# line_code :string(255) -# commit_id :string(255) -# noteable_id :integer -# system :boolean default(FALSE), not null -# st_diff :text -# - -require 'carrierwave/orm/activerecord' -require 'file_size_validator' - -class Note < ActiveRecord::Base - include Mentionable - include Gitlab::CurrentSettings - - default_value_for :system, false - - attr_mentionable :note - - belongs_to :project - belongs_to :noteable, polymorphic: true - belongs_to :author, class_name: "User" - - delegate :name, to: :project, prefix: true - delegate :name, :email, to: :author, prefix: true - - validates :note, :project, presence: true - validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true - # Attachments are deprecated and are handled by Markdown uploader - validates :attachment, file_size: { maximum: :max_attachment_size } - - validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } - validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } - - mount_uploader :attachment, AttachmentUploader - - # Scopes - 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: [nil, '']) } - scope :system, ->{ where(system: true) } - scope :user, ->{ where(system: false) } - 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) } - - serialize :st_diff - before_create :set_diff, if: ->(n) { n.line_code.present? } - after_update :set_references - - class << self - def create_status_change_note(noteable, project, author, status, source) - body = "Status changed to #{status}#{' by ' + source.gfm_reference if source}" - - create( - noteable: noteable, - project: project, - author: author, - note: body, - system: true - ) - end - - # +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) - gfm_reference = mentioner_gfm_ref(noteable, mentioner, project) - - note_options = { - project: project, - author: author, - note: cross_reference_note_content(gfm_reference), - system: 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) unless cross_reference_disallowed?(noteable, mentioner) - end - - def create_milestone_change_note(noteable, project, author, milestone) - body = if milestone.nil? - 'Milestone removed' - else - "Milestone changed to #{milestone.title}" - end - - create( - noteable: noteable, - project: project, - author: author, - note: body, - system: true - ) - end - - def create_assignee_change_note(noteable, project, author, assignee) - body = assignee.nil? ? 'Assignee removed' : "Reassigned to @#{assignee.username}" - - create({ - noteable: noteable, - project: project, - author: author, - note: body, - system: true - }) - end - - def create_labels_change_note(noteable, project, author, added_labels, removed_labels) - labels_count = added_labels.count + removed_labels.count - added_labels = added_labels.map{ |label| "~#{label.id}" }.join(' ') - removed_labels = removed_labels.map{ |label| "~#{label.id}" }.join(' ') - message = '' - - if added_labels.present? - message << "added #{added_labels}" - end - - if added_labels.present? && removed_labels.present? - message << ' and ' - end - - if removed_labels.present? - message << "removed #{removed_labels}" - end - - message << ' ' << 'label'.pluralize(labels_count) - body = "#{message.capitalize}" - - create( - noteable: noteable, - project: project, - author: author, - note: body, - system: true - ) - end - - def create_new_commits_note(merge_request, project, author, new_commits, existing_commits = [], oldrev = nil) - total_count = new_commits.length + existing_commits.length - commits_text = ActionController::Base.helpers.pluralize(total_count, 'commit') - body = "Added #{commits_text}:\n\n" - - if existing_commits.length > 0 - commit_ids = - if existing_commits.length == 1 - existing_commits.first.short_id - else - if oldrev - "#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}" - else - "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}" - end - end - - commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit') - - branch = - if merge_request.for_fork? - "#{merge_request.target_project_namespace}:#{merge_request.target_branch}" - else - merge_request.target_branch - end - - message = "* #{commit_ids} - #{commits_text} from branch `#{branch}`" - body << message - body << "\n" - end - - new_commits.each do |commit| - message = "* #{commit.short_id} - #{commit.title}" - body << message - body << "\n" - end - - create( - noteable: merge_request, - project: project, - author: author, - note: body, - system: true - ) - end - - def discussions_from_notes(notes) - discussion_ids = [] - discussions = [] - - notes.each do |note| - next if discussion_ids.include?(note.discussion_id) - - # don't group notes for the main target - if !note.for_diff_line? && note.noteable_type == "MergeRequest" - discussions << [note] - else - discussions << notes.select do |other_note| - note.discussion_id == other_note.discussion_id - end - discussion_ids << note.discussion_id - end - end - - discussions - end - - def build_discussion_id(type, id, line_code) - [:discussion, type.try(:underscore), id, line_code].join("-").to_sym - end - - # Determine if cross reference note should be created. - # eg. mentioning a commit in MR comments which exists inside a MR - # should not create "mentioned in" note. - def cross_reference_disallowed?(noteable, mentioner) - if mentioner.kind_of?(MergeRequest) - mentioner.commits.map(&:id).include? noteable.id - end - end - - # Determine whether or not a cross-reference note already exists. - def cross_reference_exists?(noteable, mentioner) - gfm_reference = mentioner_gfm_ref(noteable, mentioner) - notes = if noteable.is_a?(Commit) - where(commit_id: noteable.id) - else - where(noteable_id: noteable.id) - end - - notes.where('note like ?', cross_reference_note_pattern(gfm_reference)). - system.any? - end - - def search(query) - where("note like :query", query: "%#{query}%") - end - - def cross_reference_note_prefix - 'mentioned in ' - end - - private - - def cross_reference_note_content(gfm_reference) - cross_reference_note_prefix + "#{gfm_reference}" - end - - def cross_reference_note_pattern(gfm_reference) - # Older cross reference notes contained underscores for emphasis - "%" + cross_reference_note_content(gfm_reference) + "%" - end - - # Prepend the mentioner's namespaced project path to the GFM reference for - # cross-project references. For same-project references, return the - # unmodified GFM reference. - def mentioner_gfm_ref(noteable, mentioner, project = nil) - if mentioner.is_a?(Commit) - if project.nil? - return mentioner.gfm_reference.sub('commit ', 'commit %') - else - mentioning_project = project - end - else - mentioning_project = mentioner.project - end - - noteable_project_id = noteable_project_id(noteable, mentioning_project) - - full_gfm_reference(mentioning_project, noteable_project_id, mentioner) - end - - # Return the ID of the project that +noteable+ belongs to, or nil if - # +noteable+ is a commit and is not part of the project that owns - # +mentioner+. - def noteable_project_id(noteable, mentioning_project) - if noteable.is_a?(Commit) - if mentioning_project.repository.commit(noteable.id) - # The noteable commit belongs to the mentioner's project - mentioning_project.id - else - nil - end - else - noteable.project.id - end - end - - # Return the +mentioner+ GFM reference. If the mentioner and noteable - # projects are not the same, add the mentioning project's path to the - # returned value. - def full_gfm_reference(mentioning_project, noteable_project_id, mentioner) - if mentioning_project.id == noteable_project_id - mentioner.gfm_reference - else - if mentioner.is_a?(Commit) - mentioner.gfm_reference.sub( - /(commit )/, - "\\1#{mentioning_project.path_with_namespace}@" - ) - else - mentioner.gfm_reference.sub( - /(issue |merge request )/, - "\\1#{mentioning_project.path_with_namespace}" - ) - end - end - end - end - - def max_attachment_size - current_application_settings.max_attachment_size.megabytes.to_i - end - - def commit_author - @commit_author ||= - project.team.users.find_by(email: noteable.author_email) || - project.team.users.find_by(name: noteable.author_name) - rescue - nil - end - - def cross_reference? - note.start_with?(self.class.cross_reference_note_prefix) - end - - def find_diff - return nil unless noteable && noteable.diffs.present? - - @diff ||= noteable.diffs.find do |d| - Digest::SHA1.hexdigest(d.new_path) == diff_file_index if d.new_path - end - end - - def hook_attrs - attributes - end - - def set_diff - # First lets find notes with same diff - # before iterating over all mr diffs - diff = diff_for_line_code unless for_merge_request? - diff ||= find_diff - - self.st_diff = diff.to_hash if diff - end - - def diff - @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map) - end - - def diff_for_line_code - Note.where(noteable_id: noteable_id, noteable_type: noteable_type, line_code: line_code).last.try(:diff) - end - - # Check if such line of code exists in merge request diff - # If exists - its active discussion - # If not - its outdated diff - def active? - return true unless self.diff - return false unless noteable - - noteable.diffs.each do |mr_diff| - next unless mr_diff.new_path == self.diff.new_path - - lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a) - - lines.each do |line| - if line.text == diff_line - return true - end - end - end - - false - end - - def outdated? - !active? - end - - def diff_file_index - line_code.split('_')[0] if line_code - end - - def diff_file_name - diff.new_path if diff - end - - def file_path - if diff.new_path.present? - diff.new_path - elsif diff.old_path.present? - diff.old_path - end - end - - def diff_old_line - line_code.split('_')[1].to_i if line_code - end - - def diff_new_line - line_code.split('_')[2].to_i if line_code - end - - def generate_line_code(line) - Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) - end - - def diff_line - return @diff_line if @diff_line - - if diff - diff_lines.each do |line| - if generate_line_code(line) == self.line_code - @diff_line = line.text - end - end - end - - @diff_line - end - - def diff_line_type - return @diff_line_type if @diff_line_type - - if diff - diff_lines.each do |line| - if generate_line_code(line) == self.line_code - @diff_line_type = line.type - end - end - end - - @diff_line_type - end - - def truncated_diff_lines - max_number_of_lines = 16 - prev_match_line = nil - prev_lines = [] - - diff_lines.each do |line| - if line.type == "match" - prev_lines.clear - prev_match_line = line - else - prev_lines << line - - break if generate_line_code(line) == self.line_code - - prev_lines.shift if prev_lines.length >= max_number_of_lines - end - end - - prev_lines - end - - def diff_lines - @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) - end - - def discussion_id - @discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code) - end - - # Returns true if this is a downvote note, - # otherwise false is returned - def downvote? - votable? && (note.start_with?('-1') || - note.start_with?(':-1:') || - note.start_with?(':thumbsdown:') || - note.start_with?(':thumbs_down_sign:') - ) - end - - def for_commit? - noteable_type == "Commit" - end - - def for_commit_diff_line? - for_commit? && for_diff_line? - end - - def for_diff_line? - line_code.present? - end - - def for_issue? - noteable_type == "Issue" - end - - def for_merge_request? - noteable_type == "MergeRequest" - end - - def for_merge_request_diff_line? - for_merge_request? && for_diff_line? - end - - def for_project_snippet? - noteable_type == "Snippet" - end - - # override to return commits, which are not active record - def noteable - if for_commit? - project.repository.commit(commit_id) - else - super - end - # Temp fix to prevent app crash - # if note commit id doesn't exist - rescue - nil - end - - # Returns true if this is an upvote note, - # otherwise false is returned - def upvote? - votable? && (note.start_with?('+1') || - note.start_with?(':+1:') || - note.start_with?(':thumbsup:') || - note.start_with?(':thumbs_up_sign:') - ) - end - - def superceded?(notes) - return false unless vote? - - notes.each do |note| - next if note == self - - if note.vote? && - self[:author_id] == note[:author_id] && - self[:created_at] <= note[:created_at] - return true - end - end - - false - end - - def vote? - upvote? || downvote? - end - - def votable? - for_issue? || (for_merge_request? && !for_diff_line?) - end - - # Mentionable override. - def gfm_reference - noteable.gfm_reference - end - - # Mentionable override. - def local_reference - noteable - end - - def noteable_type_name - if noteable_type.present? - noteable_type.downcase - end - end - - # FIXME: Hack for polymorphic associations with STI - # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations - def noteable_type=(sType) - super(sType.to_s.classify.constantize.base_class.to_s) - end - - # Reset notes events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a note is updated - # * when a note is removed - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - - def set_references - notice_added_references(project, author) - end - - def editable? - !read_attribute(:system) - end -end diff --git a/app/models/notification.rb b/app/models/notification.rb deleted file mode 100644 index 1395274173d..00000000000 --- a/app/models/notification.rb +++ /dev/null @@ -1,60 +0,0 @@ -class Notification - # - # Notification levels - # - N_DISABLED = 0 - N_PARTICIPATING = 1 - N_WATCH = 2 - N_GLOBAL = 3 - N_MENTION = 4 - - attr_accessor :target - - class << self - def notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION] - end - - def options_with_labels - { - disabled: N_DISABLED, - participating: N_PARTICIPATING, - watch: N_WATCH, - mention: N_MENTION, - global: N_GLOBAL - } - end - - def project_notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION] - end - end - - def initialize(target) - @target = target - end - - def disabled? - target.notification_level == N_DISABLED - end - - def participating? - target.notification_level == N_PARTICIPATING - end - - def watch? - target.notification_level == N_WATCH - end - - def global? - target.notification_level == N_GLOBAL - end - - def mention? - target.notification_level == N_MENTION - end - - def level - target.notification_level - end -end diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb deleted file mode 100644 index 9cee3b70cb3..00000000000 --- a/app/models/personal_snippet.rb +++ /dev/null @@ -1,19 +0,0 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# type :string(255) -# visibility_level :integer default(0), not null -# - -class PersonalSnippet < Snippet -end diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb deleted file mode 100644 index e6e16058d41..00000000000 --- a/app/models/project_services/asana_service.rb +++ /dev/null @@ -1,127 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# -require 'asana' - -class AsanaService < Service - prop_accessor :api_key, :restrict_to_branch - validates :api_key, presence: true, if: :activated? - - def title - 'Asana' - end - - def description - 'Asana - Teamwork without email' - end - - def help - 'This service adds commit messages as comments to Asana tasks. -Once enabled, commit messages are checked for Asana task URLs -(for example, `https://app.asana.com/0/123456/987654`) or task IDs -starting with # (for example, `#987654`). Every task ID found will -get the commit comment added to it. - -You can also close a task with a message containing: `fix #123456`. - -You can find your Api Keys here: -http://developer.asana.com/documentation/#api_keys' - end - - def to_param - 'asana' - end - - def fields - [ - { - type: 'text', - name: 'api_key', - placeholder: 'User API token. User must have access to task, -all comments will be attributed to this user.' - }, - { - type: 'text', - name: 'restrict_to_branch', - placeholder: 'Comma-separated list of branches which will be -automatically inspected. Leave blank to include all branches.' - } - ] - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - Asana.configure do |client| - client.api_key = api_key - end - - user = data[:user_name] - branch = Gitlab::Git.ref_name(data[:ref]) - - branch_restriction = restrict_to_branch.to_s - - # check the branch restriction is poplulated and branch is not included - if branch_restriction.length > 0 && branch_restriction.index(branch).nil? - return - end - - project_name = project.name_with_namespace - push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name - - data[:commits].each do |commit| - check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg) - end - end - - def check_commit(message, push_msg) - task_list = [] - close_list = [] - - message.split("\n").each do |line| - # look for a task ID or a full Asana url - task_list.concat(line.scan(/#(\d+)/)) - task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/)) - # look for a word starting with 'fix' followed by a task ID - close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i)) - end - - # post commit to every taskid found - task_list.each do |taskid| - task = Asana::Task.find(taskid[0]) - - if task - task.create_story(text: push_msg + ' ' + message) - end - end - - # close all tasks that had 'fix(ed/es/ing) #:id' in them - close_list.each do |taskid| - task = Asana::Task.find(taskid.last) - - if task - task.modify(completed: true) - end - end - end -end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb deleted file mode 100644 index fb7e0c0fb0d..00000000000 --- a/app/models/project_services/assembla_service.rb +++ /dev/null @@ -1,56 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class AssemblaService < Service - include HTTParty - - prop_accessor :token, :subdomain - validates :token, presence: true, if: :activated? - - def title - 'Assembla' - end - - def description - 'Project Management Software (Source Commits Endpoint)' - end - - def to_param - 'assembla' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: '' }, - { type: 'text', name: 'subdomain', placeholder: '' } - ] - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}" - AssemblaService.post(url, body: { payload: data }.to_json, headers: { 'Content-Type' => 'application/json' }) - end -end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb deleted file mode 100644 index d8aedbd2ab4..00000000000 --- a/app/models/project_services/bamboo_service.rb +++ /dev/null @@ -1,137 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class BambooService < CiService - include HTTParty - - prop_accessor :bamboo_url, :build_key, :username, :password - - validates :bamboo_url, - presence: true, - format: { with: /\A#{URI.regexp}\z/ }, - if: :activated? - validates :build_key, presence: true, if: :activated? - validates :username, - presence: true, - if: ->(service) { service.password? }, - if: :activated? - validates :password, - presence: true, - if: ->(service) { service.username? }, - if: :activated? - - attr_accessor :response - - after_save :compose_service_hook, if: :activated? - - def compose_service_hook - hook = service_hook || build_service_hook - hook.save - end - - def title - 'Atlassian Bamboo CI' - end - - def description - 'A continuous integration and build server' - end - - def help - 'You must set up automatic revision labeling and a repository trigger in Bamboo.' - end - - def to_param - 'bamboo' - end - - def fields - [ - { type: 'text', name: 'bamboo_url', - placeholder: 'Bamboo root URL like https://bamboo.example.com' }, - { type: 'text', name: 'build_key', - placeholder: 'Bamboo build plan key like KEY' }, - { type: 'text', name: 'username', - placeholder: 'A user with API access, if applicable' }, - { type: 'password', name: 'password' }, - ] - end - - def supported_events - %w(push) - end - - def build_info(sha) - url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") - - if username.blank? && password.blank? - @response = HTTParty.get(parsed_url.to_s, verify: false) - else - get_url = "#{url}&os_authType=basic" - auth = { - username: username, - password: password, - } - @response = HTTParty.get(get_url, verify: false, basic_auth: auth) - end - end - - def build_page(sha, ref) - build_info(sha) if @response.nil? || !@response.code - - if @response.code != 200 || @response['results']['results']['size'] == '0' - # If actual build link can't be determined, send user to build summary page. - "#{bamboo_url}/browse/#{build_key}" - else - # If actual build link is available, go to build result page. - result_key = @response['results']['results']['result']['planResultKey']['key'] - "#{bamboo_url}/browse/#{result_key}" - end - end - - def commit_status(sha, ref) - build_info(sha) if @response.nil? || !@response.code - return :error unless @response.code == 200 || @response.code == 404 - - status = if @response.code == 404 || @response['results']['results']['size'] == '0' - 'Pending' - else - @response['results']['results']['result']['buildState'] - end - - if status.include?('Success') - 'success' - elsif status.include?('Failed') - 'failed' - elsif status.include?('Pending') - 'pending' - else - :error - end - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - # Bamboo requires a GET and does not take any data. - self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", - verify: false) - end -end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb deleted file mode 100644 index a714bc82246..00000000000 --- a/app/models/project_services/buildkite_service.rb +++ /dev/null @@ -1,135 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -require "addressable/uri" - -class BuildkiteService < CiService - ENDPOINT = "https://buildkite.com" - - prop_accessor :project_url, :token - - validates :project_url, presence: true, if: :activated? - validates :token, presence: true, if: :activated? - - after_save :compose_service_hook, if: :activated? - - def webhook_url - "#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}" - end - - def compose_service_hook - hook = service_hook || build_service_hook - hook.url = webhook_url - hook.save - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - service_hook.execute(data) - end - - def commit_status(sha, ref) - response = HTTParty.get(commit_status_path(sha), verify: false) - - if response.code == 200 && response['status'] - response['status'] - else - :error - end - end - - def commit_status_path(sha) - "#{buildkite_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}" - end - - def build_page(sha, ref) - "#{project_url}/builds?commit=#{sha}" - end - - def builds_path - "#{project_url}/builds?branch=#{project.default_branch}" - end - - def status_img_path - "#{buildkite_endpoint('badge')}/#{status_token}.svg" - end - - def title - 'Buildkite' - end - - def description - 'Continuous integration and deployments' - end - - def to_param - 'buildkite' - end - - def fields - [ - { type: 'text', - name: 'token', - placeholder: 'Buildkite project GitLab token' }, - - { type: 'text', - name: 'project_url', - placeholder: "#{ENDPOINT}/example/project" } - ] - end - - private - - def webhook_token - token_parts.first - end - - def status_token - token_parts.second - end - - def token_parts - if token.present? - token.split(':') - else - [] - end - end - - def buildkite_endpoint(subdomain = nil) - if subdomain.present? - uri = Addressable::URI.parse(ENDPOINT) - new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}" - - if uri.port.present? - "#{new_endpoint}:#{uri.port}" - else - new_endpoint - end - else - ENDPOINT - end - end -end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb deleted file mode 100644 index e591afdda64..00000000000 --- a/app/models/project_services/campfire_service.rb +++ /dev/null @@ -1,86 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class CampfireService < Service - prop_accessor :token, :subdomain, :room - validates :token, presence: true, if: :activated? - - def title - 'Campfire' - end - - def description - 'Simple web-based real-time group chat' - end - - def to_param - 'campfire' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: '' }, - { type: 'text', name: 'subdomain', placeholder: '' }, - { type: 'text', name: 'room', placeholder: '' } - ] - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - room = gate.find_room_by_name(self.room) - return true unless room - - message = build_message(data) - - room.speak(message) - end - - private - - def gate - @gate ||= Tinder::Campfire.new(subdomain, token: token) - end - - def build_message(push) - ref = Gitlab::Git.ref_name(push[:ref]) - before = push[:before] - after = push[:after] - - message = "" - message << "[#{project.name_with_namespace}] " - message << "#{push[:user_name]} " - - if Gitlab::Git.blank_ref?(before) - message << "pushed new branch #{ref} \n" - elsif Gitlab::Git.blank_ref?(after) - message << "removed branch #{ref} \n" - else - message << "pushed #{push[:total_commits_count]} commits to #{ref}. " - message << "#{project.web_url}/compare/#{before}...#{after}" - end - - message - end -end diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb deleted file mode 100644 index 1a36e439245..00000000000 --- a/app/models/project_services/ci_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -# Base class for CI services -# List methods you need to implement to get your CI service -# working with GitLab Merge Requests -class CiService < Service - def category - :ci - end - - def supported_events - %w(push) - end - - # Return complete url to build page - # - # Ex. - # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c - # - def build_page(sha, ref) - # implement inside child - end - - # Return string with build status or :error symbol - # - # Allowed states: 'success', 'failed', 'running', 'pending' - # - # - # Ex. - # @service.commit_status('13be4ac') - # # => 'success' - # - # @service.commit_status('2abe4ac') - # # => 'running' - # - # - def commit_status(sha, ref) - # implement inside child - end -end diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb deleted file mode 100644 index 8d25f627870..00000000000 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -class CustomIssueTrackerService < IssueTrackerService - - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url - - def title - if self.properties && self.properties['title'].present? - self.properties['title'] - else - 'Custom Issue Tracker' - end - end - - def description - if self.properties && self.properties['description'].present? - self.properties['description'] - else - 'Custom issue tracker' - end - end - - def to_param - 'custom_issue_tracker' - end - - def fields - [ - { type: 'text', name: 'title', placeholder: title }, - { type: 'text', name: 'description', placeholder: description }, - { type: 'text', name: 'project_url', placeholder: 'Project url' }, - { type: 'text', name: 'issues_url', placeholder: 'Issue url' }, - { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } - ] - end - - def initialize_properties - self.properties = {} if properties.nil? - end -end diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb deleted file mode 100644 index 6f6e5950aab..00000000000 --- a/app/models/project_services/emails_on_push_service.rb +++ /dev/null @@ -1,72 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -class EmailsOnPushService < Service - prop_accessor :send_from_committer_email - prop_accessor :disable_diffs - prop_accessor :recipients - validates :recipients, presence: true, if: :activated? - - def title - 'Emails on push' - end - - def description - 'Email the commits and diff of each push to a list of recipients.' - end - - def to_param - 'emails_on_push' - end - - def supported_events - %w(push tag_push) - end - - def execute(push_data) - return unless supported_events.include?(push_data[:object_kind]) - - EmailsOnPushWorker.perform_async( - project_id, - recipients, - push_data, - send_from_committer_email: send_from_committer_email?, - disable_diffs: disable_diffs? - ) - end - - def send_from_committer_email? - self.send_from_committer_email == "1" - end - - def disable_diffs? - self.disable_diffs == "1" - end - - def fields - domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ") - [ - { type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer", - help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." }, - { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs", - help: "Don't include possibly sensitive code diffs in notification body." }, - { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, - ] - end -end diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb deleted file mode 100644 index a199d0e86f2..00000000000 --- a/app/models/project_services/external_wiki_service.rb +++ /dev/null @@ -1,48 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# - -class ExternalWikiService < Service - include HTTParty - - prop_accessor :external_wiki_url - validates :external_wiki_url, - presence: true, - format: { with: /\A#{URI.regexp}\z/ }, - if: :activated? - - def title - 'External Wiki' - end - - def description - 'Replaces the link to the internal wiki with a link to an external wiki.' - end - - def to_param - 'external_wiki' - end - - def fields - [ - { type: 'text', name: 'external_wiki_url', placeholder: 'The URL of the external Wiki' }, - ] - end - - def execute(_data) - @response = HTTParty.get(properties['external_wiki_url'], verify: true) rescue nil - if @response !=200 - nil - end - end -end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb deleted file mode 100644 index 99e361dd6ed..00000000000 --- a/app/models/project_services/flowdock_service.rb +++ /dev/null @@ -1,62 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -require "flowdock-git-hook" - -class FlowdockService < Service - prop_accessor :token - validates :token, presence: true, if: :activated? - - def title - 'Flowdock' - end - - def description - 'Flowdock is a collaboration web app for technical teams.' - end - - def to_param - 'flowdock' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: '' } - ] - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - Flowdock::Git.post( - data[:ref], - data[:before], - data[:after], - token: token, - repo: project.repository.path_to_repo, - repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", - commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", - diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", - ) - end -end diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb deleted file mode 100644 index 4e75bdfc953..00000000000 --- a/app/models/project_services/gemnasium_service.rb +++ /dev/null @@ -1,61 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -require "gemnasium/gitlab_service" - -class GemnasiumService < Service - prop_accessor :token, :api_key - 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 supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - Gemnasium::GitlabService.execute( - ref: data[:ref], - before: data[:before], - after: data[:after], - token: token, - api_key: api_key, - repo: project.repository.path_to_repo - ) - end -end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb deleted file mode 100644 index 0f9838a575d..00000000000 --- a/app/models/project_services/gitlab_ci_service.rb +++ /dev/null @@ -1,128 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -class GitlabCiService < CiService - API_PREFIX = "api/v1" - - prop_accessor :project_url, :token - validates :project_url, presence: true, if: :activated? - validates :token, presence: true, if: :activated? - - after_save :compose_service_hook, if: :activated? - - def compose_service_hook - hook = service_hook || build_service_hook - hook.url = [project_url, "/build", "?token=#{token}"].join("") - hook.save - end - - def supported_events - %w(push tag_push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - service_hook.execute(data) - end - - def commit_status_path(sha, ref) - project_url + "/refs/#{ref}/commits/#{sha}/status.json?token=#{token}" - end - - def get_ci_build(sha, ref) - @ci_builds ||= {} - @ci_builds[sha] ||= HTTParty.get(commit_status_path(sha, ref), verify: false) - end - - def commit_status(sha, ref) - response = get_ci_build(sha, ref) - - if response.code == 200 and response["status"] - response["status"] - else - :error - end - end - - def fork_registration(new_project, private_token) - params = { - id: new_project.id, - name_with_namespace: new_project.name_with_namespace, - web_url: new_project.web_url, - default_branch: new_project.default_branch, - ssh_url_to_repo: new_project.ssh_url_to_repo - } - - HTTParty.post( - fork_registration_path, - body: { - project_id: project.id, - project_token: token, - private_token: private_token, - data: params }, - verify: false - ) - end - - def commit_coverage(sha, ref) - response = get_ci_build(sha, ref) - - if response.code == 200 and response["coverage"] - response["coverage"] - end - end - - def build_page(sha, ref) - project_url + "/refs/#{ref}/commits/#{sha}" - end - - def builds_path - project_url + "?ref=" + project.default_branch - end - - def status_img_path - project_url + "/status.png?ref=" + project.default_branch - end - - def title - 'GitLab CI' - end - - def description - 'Continuous integration server from GitLab' - end - - def to_param - 'gitlab_ci' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' }, - { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' } - ] - end - - private - - def fork_registration_path - project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks") - end -end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb deleted file mode 100644 index 5f0553f3b0b..00000000000 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ /dev/null @@ -1,62 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class GitlabIssueTrackerService < IssueTrackerService - include Rails.application.routes.url_helpers - - default_url_options[:host] = Gitlab.config.gitlab.host - default_url_options[:protocol] = Gitlab.config.gitlab.protocol - default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? - default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root - - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url - - def default? - true - end - - def to_param - 'gitlab' - end - - def project_url - namespace_project_issues_url(project.namespace, project) - end - - def new_issue_url - new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project) - end - - def issue_url(iid) - namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid) - end - - def project_path - namespace_project_issues_path(project.namespace, project) - end - - def new_issue_path - new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project) - end - - def issue_path(iid) - namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid) - end -end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb deleted file mode 100644 index d264a56ebdf..00000000000 --- a/app/models/project_services/hipchat_service.rb +++ /dev/null @@ -1,238 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -class HipchatService < Service - MAX_COMMITS = 3 - - prop_accessor :token, :room, :server - validates :token, presence: true, if: :activated? - - def title - 'HipChat' - end - - def description - 'Private group chat and IM' - end - - def to_param - 'hipchat' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: 'Room token' }, - { type: 'text', name: 'room', placeholder: 'Room name or ID' }, - { type: 'text', name: 'server', - placeholder: 'Leave blank for default. https://hipchat.example.com' } - ] - end - - def supported_events - %w(push issue merge_request note tag_push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - gate[room].send('GitLab', create_message(data)) - end - - private - - def gate - options = { api_version: 'v2' } - options[:server_url] = server unless server.blank? - @gate ||= HipChat::Client.new(token, options) - end - - def create_message(data) - object_kind = data[:object_kind] - - message = \ - case object_kind - when "push", "tag_push" - create_push_message(data) - when "issue" - create_issue_message(data) unless is_update?(data) - when "merge_request" - create_merge_request_message(data) unless is_update?(data) - when "note" - create_note_message(data) - end - end - - def create_push_message(push) - ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch' - ref = Gitlab::Git.ref_name(push[:ref]) - - before = push[:before] - after = push[:after] - - message = "" - message << "#{push[:user_name]} " - if Gitlab::Git.blank_ref?(before) - message << "pushed new #{ref_type} <a href=\""\ - "#{project_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"\ - " to #{project_link}\n" - elsif Gitlab::Git.blank_ref?(after) - message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n" - else - message << "pushed to #{ref_type} <a href=\""\ - "#{project.web_url}/commits/#{URI.escape(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>)" - - push[:commits].take(MAX_COMMITS).each do |commit| - message << "<br /> - #{commit[:message].lines.first} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)" - end - - if push[:commits].count > MAX_COMMITS - message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits" - end - end - - message - end - - def format_body(body) - if body - body = body.truncate(200, separator: ' ', omission: '...') - end - - "<pre>#{body}</pre>" - end - - def create_issue_message(data) - user_name = data[:user][:name] - - obj_attr = data[:object_attributes] - obj_attr = HashWithIndifferentAccess.new(obj_attr) - title = obj_attr[:title] - state = obj_attr[:state] - issue_iid = obj_attr[:iid] - issue_url = obj_attr[:url] - description = obj_attr[:description] - - issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>" - message = "#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>" - - if description - description = format_body(description) - message << description - end - - message - end - - def create_merge_request_message(data) - user_name = data[:user][:name] - - obj_attr = data[:object_attributes] - obj_attr = HashWithIndifferentAccess.new(obj_attr) - merge_request_id = obj_attr[:iid] - source_branch = obj_attr[:source_branch] - target_branch = obj_attr[:target_branch] - state = obj_attr[:state] - description = obj_attr[:description] - title = obj_attr[:title] - - merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" - merge_request_link = "<a href=\"#{merge_request_url}\">merge request ##{merge_request_id}</a>" - message = "#{user_name} #{state} #{merge_request_link} in " \ - "#{project_link}: <b>#{title}</b>" - - if description - description = format_body(description) - message << description - end - - message - end - - def format_title(title) - "<b>" + title.lines.first.chomp + "</b>" - end - - def create_note_message(data) - data = HashWithIndifferentAccess.new(data) - user_name = data[:user][:name] - - repo_attr = HashWithIndifferentAccess.new(data[:repository]) - - obj_attr = HashWithIndifferentAccess.new(data[:object_attributes]) - note = obj_attr[:note] - note_url = obj_attr[:url] - noteable_type = obj_attr[:noteable_type] - - case noteable_type - when "Commit" - commit_attr = HashWithIndifferentAccess.new(data[:commit]) - subject_desc = commit_attr[:id] - subject_desc = Commit.truncate_sha(subject_desc) - subject_type = "commit" - title = format_title(commit_attr[:message]) - when "Issue" - subj_attr = HashWithIndifferentAccess.new(data[:issue]) - subject_id = subj_attr[:iid] - subject_desc = "##{subject_id}" - subject_type = "issue" - title = format_title(subj_attr[:title]) - when "MergeRequest" - subj_attr = HashWithIndifferentAccess.new(data[:merge_request]) - subject_id = subj_attr[:iid] - subject_desc = "##{subject_id}" - subject_type = "merge request" - title = format_title(subj_attr[:title]) - when "Snippet" - subj_attr = HashWithIndifferentAccess.new(data[:snippet]) - subject_id = subj_attr[:id] - subject_desc = "##{subject_id}" - subject_type = "snippet" - title = format_title(subj_attr[:title]) - end - - subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>" - message = "#{user_name} commented on #{subject_html} in #{project_link}: " - message << title - - if note - note = format_body(note) - message << note - end - - message - end - - def project_name - project.name_with_namespace.gsub(/\s/, '') - end - - def project_url - project.web_url - end - - def project_link - "<a href=\"#{project_url}\">#{project_name}</a>" - end - - def is_update?(data) - data[:object_attributes][:action] == 'update' - end -end diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb deleted file mode 100644 index e9e1e276e7d..00000000000 --- a/app/models/project_services/irker_service.rb +++ /dev/null @@ -1,163 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -require 'uri' - -class IrkerService < Service - prop_accessor :colorize_messages, :recipients, :channels - validates :recipients, presence: true, if: :activated? - validate :check_recipients_count, if: :activated? - - before_validation :get_channels - after_initialize :initialize_settings - - # Writer for RSpec tests - attr_writer :settings - - def initialize_settings - # See the documentation (doc/project_services/irker.md) for possible values - # here - @settings ||= { - server_ip: 'localhost', - server_port: 6659, - max_channels: 3, - default_irc_uri: nil - } - end - - def title - 'Irker (IRC gateway)' - end - - def description - 'Send IRC messages, on update, to a list of recipients through an Irker '\ - 'gateway.' - end - - def help - msg = 'Recipients have to be specified with a full URI: '\ - 'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\ - 'the channel to be a nickname instead, append ",isnick" to the channel '\ - 'name; if the channel is protected by a secret password, append '\ - '"?key=secretpassword" to the URI.' - - unless @settings[:default_irc].nil? - msg += ' Note that a default IRC URI is provided by this service\'s '\ - "administrator: #{default_irc}. You can thus just give a channel name." - end - msg - end - - def to_param - 'irker' - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - IrkerWorker.perform_async(project_id, channels, - colorize_messages, data, @settings) - end - - def fields - [ - { type: 'textarea', name: 'recipients', - placeholder: 'Recipients/channels separated by whitespaces' }, - { type: 'checkbox', name: 'colorize_messages' }, - ] - end - - private - - def check_recipients_count - return true if recipients.nil? || recipients.empty? - - if recipients.split(/\s+/).count > max_chans - errors.add(:recipients, "are limited to #{max_chans}") - end - end - - def max_chans - @settings[:max_channels] - end - - def get_channels - return true unless :activated? - return true if recipients.nil? || recipients.empty? - - map_recipients - - errors.add(:recipients, 'are all invalid') if channels.empty? - true - end - - def map_recipients - self.channels = recipients.split(/\s+/).map do |recipient| - format_channel default_irc_uri, recipient - end - channels.reject! &:nil? - end - - def default_irc_uri - default_irc = @settings[:default_irc_uri] - if !(default_irc.nil? || default_irc[-1] == '/') - default_irc += '/' - end - default_irc - end - - def format_channel(default_irc, recipient) - cnt = 0 - url = nil - - # Try to parse the chan as a full URI - begin - uri = URI.parse(recipient) - raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0 - rescue URI::InvalidURIError - unless default_irc.nil? - cnt += 1 - recipient = "#{default_irc}#{recipient}" - retry if cnt == 1 - end - else - url = consider_uri uri - end - url - end - - def consider_uri(uri) - # Authorize both irc://domain.com/#chan and irc://domain.com/chan - if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? - # Do not authorize irc://domain.com/ - if uri.fragment.nil? && uri.path.length > 1 - uri.to_s - else - # Authorize irc://domain.com/smthg#chan - # The irker daemon will deal with it by concatenating smthg and - # chan, thus sending messages on #smthgchan - uri.to_s - end - end - end -end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb deleted file mode 100644 index c8ab9d63b74..00000000000 --- a/app/models/project_services/issue_tracker_service.rb +++ /dev/null @@ -1,125 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class IssueTrackerService < Service - - validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? - - def category - :issue_tracker - end - - def default? - false - end - - def issue_url(iid) - self.issues_url.gsub(':id', iid.to_s) - end - - def project_path - project_url - end - - def new_issue_path - new_issue_url - end - - def issue_path(iid) - issue_url(iid) - end - - def fields - [ - { type: 'text', name: 'description', placeholder: description }, - { type: 'text', name: 'project_url', placeholder: 'Project url' }, - { type: 'text', name: 'issues_url', placeholder: 'Issue url' }, - { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } - ] - end - - def initialize_properties - if properties.nil? - if enabled_in_gitlab_config - self.properties = { - title: issues_tracker['title'], - project_url: add_issues_tracker_id(issues_tracker['project_url']), - issues_url: add_issues_tracker_id(issues_tracker['issues_url']), - new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url']) - } - else - self.properties = {} - end - end - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again." - result = false - - begin - url = URI.parse(self.project_url) - - if url.host && url.port - http = Net::HTTP.start(url.host, url.port, { open_timeout: 5, read_timeout: 5 }) - response = http.head("/") - - if response - message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}" - result = true - end - end - rescue Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error - message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}" - end - Rails.logger.info(message) - result - end - - private - - def enabled_in_gitlab_config - Gitlab.config.issues_tracker && - Gitlab.config.issues_tracker.values.any? && - issues_tracker - end - - def issues_tracker - Gitlab.config.issues_tracker[to_param] - end - - def add_issues_tracker_id(url) - if self.project - id = self.project.issues_tracker_id - - if id - url = url.gsub(":issues_tracker_id", id) - end - end - - url - end -end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb deleted file mode 100644 index fcd9dc2f336..00000000000 --- a/app/models/project_services/jira_service.rb +++ /dev/null @@ -1,58 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class JiraService < IssueTrackerService - include Rails.application.routes.url_helpers - - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url - - def help - issue_tracker_link = help_page_path("integration", "external-issue-tracker") - - line1 = "Setting `project_url`, `issues_url` and `new_issue_url` will "\ - "allow a user to easily navigate to the Jira issue tracker. "\ - "See the [integration doc](#{issue_tracker_link}) for details." - - line2 = 'Support for referencing commits and automatic closing of Jira issues directly ' \ - 'from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)' - - [line1, line2].join("\n\n") - end - - def title - if self.properties && self.properties['title'].present? - self.properties['title'] - else - 'JIRA' - end - end - - def description - if self.properties && self.properties['description'].present? - self.properties['description'] - else - 'Jira issue tracker' - end - end - - def to_param - 'jira' - end -end diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb deleted file mode 100644 index ade9ee97873..00000000000 --- a/app/models/project_services/pivotaltracker_service.rb +++ /dev/null @@ -1,72 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class PivotaltrackerService < Service - include HTTParty - - prop_accessor :token - validates :token, presence: true, if: :activated? - - def title - 'PivotalTracker' - end - - def description - 'Project Management Software (Source Commits Endpoint)' - end - - def to_param - 'pivotaltracker' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: '' } - ] - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - url = 'https://www.pivotaltracker.com/services/v5/source_commits' - data[:commits].each do |commit| - message = { - 'source_commit' => { - 'commit_id' => commit[:id], - 'author' => commit[:author][:name], - 'url' => commit[:url], - 'message' => commit[:message] - } - } - PivotaltrackerService.post( - url, - body: message.to_json, - headers: { - 'Content-Type' => 'application/json', - 'X-TrackerToken' => token - } - ) - end - end -end diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb deleted file mode 100644 index 53edf522e9a..00000000000 --- a/app/models/project_services/pushover_service.rb +++ /dev/null @@ -1,125 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class PushoverService < Service - include HTTParty - base_uri 'https://api.pushover.net/1' - - prop_accessor :api_key, :user_key, :device, :priority, :sound - validates :api_key, :user_key, :priority, presence: true, if: :activated? - - def title - 'Pushover' - end - - def description - 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.' - end - - def to_param - 'pushover' - end - - def fields - [ - { type: 'text', name: 'api_key', placeholder: 'Your application key' }, - { type: 'text', name: 'user_key', placeholder: 'Your user key' }, - { type: 'text', name: 'device', placeholder: 'Leave blank for all active devices' }, - { type: 'select', name: 'priority', choices: - [ - ['Lowest Priority', -2], - ['Low Priority', -1], - ['Normal Priority', 0], - ['High Priority', 1] - ], - default_choice: 0 - }, - { type: 'select', name: 'sound', choices: - [ - ['Device default sound', nil], - ['Pushover (default)', 'pushover'], - ['Bike', 'bike'], - ['Bugle', 'bugle'], - ['Cash Register', 'cashregister'], - ['Classical', 'classical'], - ['Cosmic', 'cosmic'], - ['Falling', 'falling'], - ['Gamelan', 'gamelan'], - ['Incoming', 'incoming'], - ['Intermission', 'intermission'], - ['Magic', 'magic'], - ['Mechanical', 'mechanical'], - ['Piano Bar', 'pianobar'], - ['Siren', 'siren'], - ['Space Alarm', 'spacealarm'], - ['Tug Boat', 'tugboat'], - ['Alien Alarm (long)', 'alien'], - ['Climb (long)', 'climb'], - ['Persistent (long)', 'persistent'], - ['Pushover Echo (long)', 'echo'], - ['Up Down (long)', 'updown'], - ['None (silent)', 'none'] - ] - }, - ] - end - - def supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - ref = Gitlab::Git.ref_name(data[:ref]) - before = data[:before] - after = data[:after] - - if Gitlab::Git.blank_ref?(before) - message = "#{data[:user_name]} pushed new branch \"#{ref}\"." - elsif Gitlab::Git.blank_ref?(after) - message = "#{data[:user_name]} deleted branch \"#{ref}\"." - else - message = "#{data[:user_name]} push to branch \"#{ref}\"." - end - - if data[:total_commits_count] > 0 - message << "\nTotal commits count: #{data[:total_commits_count]}" - end - - pushover_data = { - token: api_key, - user: user_key, - device: device, - priority: priority, - title: "#{project.name_with_namespace}", - message: message, - url: data[:repository][:homepage], - url_title: "See project #{project.name_with_namespace}" - } - - # Sound parameter MUST NOT be sent to API if not selected - if sound - pushover_data.merge!(sound: sound) - end - - PushoverService.post('/messages.json', body: pushover_data) - end -end diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb deleted file mode 100644 index dd9ba97ee1f..00000000000 --- a/app/models/project_services/redmine_service.rb +++ /dev/null @@ -1,44 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class RedmineService < IssueTrackerService - - prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url - - def title - if self.properties && self.properties['title'].present? - self.properties['title'] - else - 'Redmine' - end - end - - def description - if self.properties && self.properties['description'].present? - self.properties['description'] - else - 'Redmine issue tracker' - end - end - - def to_param - 'redmine' - end -end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb deleted file mode 100644 index 36d9874edd3..00000000000 --- a/app/models/project_services/slack_service.rb +++ /dev/null @@ -1,105 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class SlackService < Service - prop_accessor :webhook, :username, :channel - validates :webhook, presence: true, if: :activated? - - def title - 'Slack' - end - - def description - 'A team communication tool for the 21st century' - end - - def to_param - 'slack' - end - - def fields - [ - { type: 'text', name: 'webhook', - placeholder: 'https://hooks.slack.com/services/...' }, - { type: 'text', name: 'username', placeholder: 'username' }, - { type: 'text', name: 'channel', placeholder: '#channel' } - ] - end - - def supported_events - %w(push issue merge_request note tag_push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - return unless webhook.present? - - object_kind = data[:object_kind] - - data = data.merge( - project_url: project_url, - project_name: project_name - ) - - # WebHook events often have an 'update' event that follows a 'open' or - # 'close' action. Ignore update events for now to prevent duplicate - # messages from arriving. - - message = \ - case object_kind - when "push", "tag_push" - PushMessage.new(data) - when "issue" - IssueMessage.new(data) unless is_update?(data) - when "merge_request" - MergeMessage.new(data) unless is_update?(data) - when "note" - NoteMessage.new(data) - end - - opt = {} - opt[:channel] = channel if channel - opt[:username] = username if username - - if message - notifier = Slack::Notifier.new(webhook, opt) - notifier.ping(message.pretext, attachments: message.attachments) - end - end - - private - - def project_name - project.name_with_namespace.gsub(/\s/, '') - end - - def project_url - project.web_url - end - - def is_update?(data) - data[:object_attributes][:action] == 'update' - end -end - -require "slack_service/issue_message" -require "slack_service/push_message" -require "slack_service/merge_message" -require "slack_service/note_message" diff --git a/app/models/project_services/slack_service/base_message.rb b/app/models/project_services/slack_service/base_message.rb deleted file mode 100644 index aa00d6061a1..00000000000 --- a/app/models/project_services/slack_service/base_message.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'slack-notifier' - -class SlackService - class BaseMessage - def initialize(params) - raise NotImplementedError - end - - def pretext - format(message) - end - - def attachments - raise NotImplementedError - end - - private - - def message - raise NotImplementedError - end - - def format(string) - Slack::Notifier::LinkFormatter.format(string) - end - - def attachment_color - '#345' - end - end -end diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb deleted file mode 100644 index 5af24a80609..00000000000 --- a/app/models/project_services/slack_service/issue_message.rb +++ /dev/null @@ -1,56 +0,0 @@ -class SlackService - class IssueMessage < BaseMessage - attr_reader :user_name - attr_reader :title - attr_reader :project_name - attr_reader :project_url - attr_reader :issue_iid - attr_reader :issue_url - attr_reader :action - attr_reader :state - attr_reader :description - - def initialize(params) - @user_name = params[:user][:name] - @project_name = params[:project_name] - @project_url = params[:project_url] - - obj_attr = params[:object_attributes] - obj_attr = HashWithIndifferentAccess.new(obj_attr) - @title = obj_attr[:title] - @issue_iid = obj_attr[:iid] - @issue_url = obj_attr[:url] - @action = obj_attr[:action] - @state = obj_attr[:state] - @description = obj_attr[:description] - end - - def attachments - return [] unless opened_issue? - - description_message - end - - private - - def message - "#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*" - end - - def opened_issue? - action == "open" - end - - def description_message - [{ text: format(description), color: attachment_color }] - end - - def project_link - "[#{project_name}](#{project_url})" - end - - def issue_link - "[issue ##{issue_iid}](#{issue_url})" - end - end -end diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb deleted file mode 100644 index e792c258f73..00000000000 --- a/app/models/project_services/slack_service/merge_message.rb +++ /dev/null @@ -1,60 +0,0 @@ -class SlackService - class MergeMessage < BaseMessage - attr_reader :user_name - attr_reader :project_name - attr_reader :project_url - attr_reader :merge_request_id - attr_reader :source_branch - attr_reader :target_branch - attr_reader :state - attr_reader :title - - def initialize(params) - @user_name = params[:user][:name] - @project_name = params[:project_name] - @project_url = params[:project_url] - - obj_attr = params[:object_attributes] - obj_attr = HashWithIndifferentAccess.new(obj_attr) - @merge_request_id = obj_attr[:iid] - @source_branch = obj_attr[:source_branch] - @target_branch = obj_attr[:target_branch] - @state = obj_attr[:state] - @title = format_title(obj_attr[:title]) - end - - def pretext - format(message) - end - - def attachments - [] - end - - private - - def format_title(title) - '*' + title.lines.first.chomp + '*' - end - - def message - merge_request_message - end - - def project_link - "[#{project_name}](#{project_url})" - end - - def merge_request_message - "#{user_name} #{state} #{merge_request_link} in #{project_link}: #{title}" - end - - def merge_request_link - "[merge request ##{merge_request_id}](#{merge_request_url})" - end - - def merge_request_url - "#{project_url}/merge_requests/#{merge_request_id}" - end - end -end diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb deleted file mode 100644 index 074478b292d..00000000000 --- a/app/models/project_services/slack_service/note_message.rb +++ /dev/null @@ -1,82 +0,0 @@ -class SlackService - class NoteMessage < BaseMessage - attr_reader :message - attr_reader :user_name - attr_reader :project_name - attr_reader :project_link - attr_reader :note - attr_reader :note_url - attr_reader :title - - def initialize(params) - params = HashWithIndifferentAccess.new(params) - @user_name = params[:user][:name] - @project_name = params[:project_name] - @project_url = params[:project_url] - - obj_attr = params[:object_attributes] - obj_attr = HashWithIndifferentAccess.new(obj_attr) - @note = obj_attr[:note] - @note_url = obj_attr[:url] - noteable_type = obj_attr[:noteable_type] - - case noteable_type - when "Commit" - create_commit_note(HashWithIndifferentAccess.new(params[:commit])) - when "Issue" - create_issue_note(HashWithIndifferentAccess.new(params[:issue])) - when "MergeRequest" - create_merge_note(HashWithIndifferentAccess.new(params[:merge_request])) - when "Snippet" - create_snippet_note(HashWithIndifferentAccess.new(params[:snippet])) - end - end - - def attachments - description_message - end - - private - - def format_title(title) - title.lines.first.chomp - end - - def create_commit_note(commit) - commit_sha = commit[:id] - commit_sha = Commit.truncate_sha(commit_sha) - commit_link = "[commit #{commit_sha}](#{@note_url})" - title = format_title(commit[:message]) - @message = "#{@user_name} commented on #{commit_link} in #{project_link}: *#{title}*" - end - - def create_issue_note(issue) - issue_iid = issue[:iid] - note_link = "[issue ##{issue_iid}](#{@note_url})" - title = format_title(issue[:title]) - @message = "#{@user_name} commented on #{note_link} in #{project_link}: *#{title}*" - end - - def create_merge_note(merge_request) - merge_request_id = merge_request[:iid] - merge_request_link = "[merge request ##{merge_request_id}](#{@note_url})" - title = format_title(merge_request[:title]) - @message = "#{@user_name} commented on #{merge_request_link} in #{project_link}: *#{title}*" - end - - def create_snippet_note(snippet) - snippet_id = snippet[:id] - snippet_link = "[snippet ##{snippet_id}](#{@note_url})" - title = format_title(snippet[:title]) - @message = "#{@user_name} commented on #{snippet_link} in #{project_link}: *#{title}*" - end - - def description_message - [{ text: format(@note), color: attachment_color }] - end - - def project_link - "[#{@project_name}](#{@project_url})" - end - end -end diff --git a/app/models/project_services/slack_service/push_message.rb b/app/models/project_services/slack_service/push_message.rb deleted file mode 100644 index b26f3e9ddce..00000000000 --- a/app/models/project_services/slack_service/push_message.rb +++ /dev/null @@ -1,110 +0,0 @@ -class SlackService - class PushMessage < BaseMessage - attr_reader :after - attr_reader :before - attr_reader :commits - attr_reader :project_name - attr_reader :project_url - attr_reader :ref - attr_reader :ref_type - attr_reader :user_name - - def initialize(params) - @after = params[:after] - @before = params[:before] - @commits = params.fetch(:commits, []) - @project_name = params[:project_name] - @project_url = params[:project_url] - @ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch' - @ref = Gitlab::Git.ref_name(params[:ref]) - @user_name = params[:user_name] - end - - def pretext - format(message) - end - - def attachments - return [] if new_branch? || removed_branch? - - commit_message_attachments - end - - private - - def message - if new_branch? - new_branch_message - elsif removed_branch? - removed_branch_message - else - push_message - end - end - - def format(string) - Slack::Notifier::LinkFormatter.format(string) - end - - def new_branch_message - "#{user_name} pushed new #{ref_type} #{branch_link} to #{project_link}" - end - - def removed_branch_message - "#{user_name} removed #{ref_type} #{ref} from #{project_link}" - end - - def push_message - "#{user_name} pushed to #{ref_type} #{branch_link} of #{project_link} (#{compare_link})" - end - - def commit_messages - commits.map { |commit| compose_commit_message(commit) }.join("\n") - end - - def commit_message_attachments - [{ text: format(commit_messages), color: attachment_color }] - end - - def compose_commit_message(commit) - author = commit[:author][:name] - id = Commit.truncate_sha(commit[:id]) - message = commit[:message] - url = commit[:url] - - "[#{id}](#{url}): #{message} - #{author}" - end - - def new_branch? - Gitlab::Git.blank_ref?(before) - end - - def removed_branch? - Gitlab::Git.blank_ref?(after) - end - - def branch_url - "#{project_url}/commits/#{ref}" - end - - def compare_url - "#{project_url}/compare/#{before}...#{after}" - end - - def branch_link - "[#{ref}](#{branch_url})" - end - - def project_link - "[#{project_name}](#{project_url})" - end - - def compare_link - "[Compare changes](#{compare_url})" - end - - def attachment_color - '#345' - end - end -end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb deleted file mode 100644 index 3c002a1634b..00000000000 --- a/app/models/project_services/teamcity_service.rb +++ /dev/null @@ -1,145 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - -class TeamcityService < CiService - include HTTParty - - prop_accessor :teamcity_url, :build_type, :username, :password - - validates :teamcity_url, - presence: true, - format: { with: /\A#{URI.regexp}\z/ }, if: :activated? - validates :build_type, presence: true, if: :activated? - validates :username, - presence: true, - if: ->(service) { service.password? }, if: :activated? - validates :password, - presence: true, - if: ->(service) { service.username? }, if: :activated? - - attr_accessor :response - - after_save :compose_service_hook, if: :activated? - - def compose_service_hook - hook = service_hook || build_service_hook - hook.save - end - - def title - 'JetBrains TeamCity CI' - end - - def description - 'A continuous integration and build server' - end - - def help - 'The build configuration in Teamcity must use the build format '\ - 'number %build.vcs.number% '\ - 'you will also want to configure monitoring of all branches so merge '\ - 'requests build, that setting is in the vsc root advanced settings.' - end - - def to_param - 'teamcity' - end - - def supported_events - %w(push) - end - - def fields - [ - { type: 'text', name: 'teamcity_url', - placeholder: 'TeamCity root URL like https://teamcity.example.com' }, - { type: 'text', name: 'build_type', - placeholder: 'Build configuration ID' }, - { type: 'text', name: 'username', - placeholder: 'A user with permissions to trigger a manual build' }, - { type: 'password', name: 'password' }, - ] - end - - def build_info(sha) - url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ - "branch:unspecified:any,number:#{sha}") - auth = { - username: username, - password: password, - } - @response = HTTParty.get("#{url}", verify: false, basic_auth: auth) - end - - def build_page(sha, ref) - build_info(sha) if @response.nil? || !@response.code - - if @response.code != 200 - # If actual build link can't be determined, - # send user to build summary page. - "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}" - else - # If actual build link is available, go to build result page. - built_id = @response['build']['id'] - "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ - "&buildTypeId=#{build_type}" - end - end - - def commit_status(sha, ref) - build_info(sha) if @response.nil? || !@response.code - return :error unless @response.code == 200 || @response.code == 404 - - status = if @response.code == 404 - 'Pending' - else - @response['build']['status'] - end - - if status.include?('SUCCESS') - 'success' - elsif status.include?('FAILURE') - 'failed' - elsif status.include?('Pending') - 'pending' - else - :error - end - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - auth = { - username: username, - password: password, - } - - branch = Gitlab::Git.ref_name(data[:ref]) - - self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", - body: "<build branchName=\"#{branch}\">"\ - "<buildType id=\"#{build_type}\"/>"\ - '</build>', - headers: { 'Content-type' => 'application/xml' }, - basic_auth: auth - ) - end -end diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb deleted file mode 100644 index 9e2c1b0e18e..00000000000 --- a/app/models/project_snippet.rb +++ /dev/null @@ -1,28 +0,0 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# type :string(255) -# visibility_level :integer default(0), not null -# - -class ProjectSnippet < Snippet - belongs_to :project - belongs_to :author, class_name: "User" - - validates :project, presence: true - - # Scopes - scope :fresh, -> { order("created_at DESC") } - scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } - scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } -end diff --git a/app/models/project_team.rb b/app/models/project_team.rb deleted file mode 100644 index 56e49af2324..00000000000 --- a/app/models/project_team.rb +++ /dev/null @@ -1,169 +0,0 @@ -class ProjectTeam - attr_accessor :project - - def initialize(project) - @project = project - end - - # Shortcut to add users - # - # Use: - # @team << [@user, :master] - # @team << [@users, :master] - # - def <<(args) - users, access, current_user = *args - - if users.respond_to?(:each) - add_users(users, access, current_user) - else - add_user(users, access, current_user) - end - end - - def find(user_id) - user = project.users.find_by(id: user_id) - - if group - user ||= group.users.find_by(id: user_id) - end - - user - end - - def find_member(user_id) - member = project.project_members.find_by(user_id: user_id) - - # If user is not in project members - # we should check for group membership - if group && !member - member = group.group_members.find_by(user_id: user_id) - end - - member - end - - def add_users(users, access, current_user = nil) - ProjectMember.add_users_into_projects( - [project.id], - users, - access, - current_user - ) - end - - def add_user(user, access, current_user = nil) - add_users([user], access, current_user) - end - - # Remove all users from project team - def truncate - ProjectMember.truncate_team(project) - end - - def users - members - end - - def members - @members ||= fetch_members - end - - def guests - @guests ||= fetch_members(:guests) - end - - def reporters - @reporters ||= fetch_members(:reporters) - end - - def developers - @developers ||= fetch_members(:developers) - end - - def masters - @masters ||= fetch_members(:masters) - end - - def import(source_project, current_user = nil) - target_project = project - - source_members = source_project.project_members.to_a - target_user_ids = target_project.project_members.pluck(:user_id) - - source_members.reject! do |member| - # Skip if user already present in team - !member.invite? && target_user_ids.include?(member.user_id) - end - - source_members.map! do |member| - new_member = member.dup - new_member.id = nil - new_member.source = target_project - new_member.created_by = current_user - new_member - end - - ProjectMember.transaction do - source_members.each do |member| - member.save - end - end - - true - rescue - false - end - - def guest?(user) - max_member_access(user.id) == Gitlab::Access::GUEST - end - - def reporter?(user) - max_member_access(user.id) == Gitlab::Access::REPORTER - end - - def developer?(user) - max_member_access(user.id) == Gitlab::Access::DEVELOPER - end - - def master?(user) - max_member_access(user.id) == Gitlab::Access::MASTER - end - - def member?(user_id) - !!find_member(user_id) - end - - def max_member_access(user_id) - access = [] - access << project.project_members.find_by(user_id: user_id).try(:access_field) - - if group - access << group.group_members.find_by(user_id: user_id).try(:access_field) - end - - access.compact.max - end - - private - - def fetch_members(level = nil) - project_members = project.project_members - group_members = group ? group.group_members : [] - - if level - project_members = project_members.send(level) - group_members = group_members.send(level) if group - end - - user_ids = project_members.pluck(:user_id) - user_ids.push(*group_members.pluck(:user_id)) if group - - User.where(id: user_ids) - end - - def group - project.group - end -end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb deleted file mode 100644 index 772c868d9cd..00000000000 --- a/app/models/project_wiki.rb +++ /dev/null @@ -1,149 +0,0 @@ -class ProjectWiki - include Gitlab::ShellAdapter - - MARKUPS = { - 'Markdown' => :markdown, - 'RDoc' => :rdoc, - 'AsciiDoc' => :asciidoc - } unless defined?(MARKUPS) - - class CouldNotCreateWikiError < StandardError; end - - # Returns a string describing what went wrong after - # an operation fails. - attr_reader :error_message - - def initialize(project, user = nil) - @project = project - @user = user - end - - def path - @project.path + '.wiki' - end - - def path_with_namespace - @project.path_with_namespace + ".wiki" - end - - def url_to_repo - gitlab_shell.url_to_repo(path_with_namespace) - end - - def ssh_url_to_repo - url_to_repo - end - - def http_url_to_repo - [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') - end - - # Returns the Gollum::Wiki object. - def wiki - @wiki ||= begin - Gollum::Wiki.new(path_to_repo) - rescue Gollum::NoSuchPathError - create_repo! - end - end - - def empty? - pages.empty? - end - - # Returns an Array of Gitlab WikiPage instances or an - # empty Array if this Wiki has no pages. - def pages - wiki.pages.map { |page| WikiPage.new(self, page, true) } - end - - # Finds a page within the repository based on a tile - # or slug. - # - # title - The human readable or parameterized title of - # the page. - # - # Returns an initialized WikiPage instance or nil - def find_page(title, version = nil) - page_title, page_dir = page_title_and_dir(title) - if page = wiki.page(page_title, version, page_dir) - WikiPage.new(self, page, true) - else - nil - end - end - - def find_file(name, version = nil, try_on_disk = true) - version = wiki.ref if version.nil? # Gollum::Wiki#file ? - if wiki_file = wiki.file(name, version, try_on_disk) - wiki_file - else - nil - end - end - - def create_page(title, content, format = :markdown, message = nil) - commit = commit_details(:created, message, title) - - wiki.write_page(title, format, content, commit) - rescue Gollum::DuplicatePageError => e - @error_message = "Duplicate page: #{e.message}" - return false - end - - def update_page(page, content, format = :markdown, message = nil) - commit = commit_details(:updated, message, page.title) - - wiki.update_page(page, page.name, format, content, commit) - end - - def delete_page(page, message = nil) - wiki.delete_page(page, commit_details(:deleted, message, page.title)) - end - - def page_title_and_dir(title) - title_array = title.split("/") - title = title_array.pop - [title, title_array.join("/")] - end - - def search_files(query) - repository.search_files(query, default_branch) - end - - def repository - Repository.new(path_with_namespace, default_branch) - end - - def default_branch - wiki.class.default_ref - end - - private - - def create_repo! - if init_repo(path_with_namespace) - Gollum::Wiki.new(path_to_repo) - else - raise CouldNotCreateWikiError - end - end - - def init_repo(path_with_namespace) - gitlab_shell.add_repository(path_with_namespace) - end - - def commit_details(action, message = nil, title = nil) - commit_message = message || default_message(action, title) - - { email: @user.email, name: @user.name, message: commit_message } - end - - def default_message(action, title) - "#{@user.username} #{action} page: #{title}" - end - - def path_to_repo - @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") - end -end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb deleted file mode 100644 index 97207ba1272..00000000000 --- a/app/models/protected_branch.rb +++ /dev/null @@ -1,23 +0,0 @@ -# == Schema Information -# -# Table name: protected_branches -# -# id :integer not null, primary key -# project_id :integer not null -# name :string(255) not null -# created_at :datetime -# updated_at :datetime -# developers_can_push :boolean default(FALSE), not null -# - -class ProtectedBranch < ActiveRecord::Base - include Gitlab::ShellAdapter - - belongs_to :project - validates :name, presence: true - validates :project, presence: true - - def commit - project.repository.commit(self.name) - end -end diff --git a/app/models/repository.rb b/app/models/repository.rb deleted file mode 100644 index 263a436d521..00000000000 --- a/app/models/repository.rb +++ /dev/null @@ -1,377 +0,0 @@ -class Repository - include Gitlab::ShellAdapter - - attr_accessor :raw_repository, :path_with_namespace - - def initialize(path_with_namespace, default_branch = nil) - @path_with_namespace = path_with_namespace - @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace - rescue Gitlab::Git::Repository::NoRepository - nil - end - - # Return absolute path to repository - def path_to_repo - @path_to_repo ||= File.expand_path( - File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git") - ) - end - - def exists? - raw_repository - end - - def empty? - raw_repository.empty? - end - - def commit(id = 'HEAD') - return nil unless raw_repository - commit = Gitlab::Git::Commit.find(raw_repository, id) - commit = Commit.new(commit) if commit - commit - rescue Rugged::OdbError - nil - end - - def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) - commits = Gitlab::Git::Commit.where( - repo: raw_repository, - ref: ref, - path: path, - limit: limit, - offset: offset, - ) - commits = Commit.decorate(commits) if commits.present? - commits - end - - def commits_between(from, to) - commits = Gitlab::Git::Commit.between(raw_repository, from, to) - commits = Commit.decorate(commits) if commits.present? - commits - end - - def find_branch(name) - branches.find { |branch| branch.name == name } - end - - def find_tag(name) - tags.find { |tag| tag.name == name } - end - - def add_branch(branch_name, ref) - cache.expire(:branch_names) - @branches = nil - - gitlab_shell.add_branch(path_with_namespace, branch_name, ref) - end - - def add_tag(tag_name, ref, message = nil) - cache.expire(:tag_names) - @tags = nil - - gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) - end - - def rm_branch(branch_name) - cache.expire(:branch_names) - @branches = nil - - gitlab_shell.rm_branch(path_with_namespace, branch_name) - end - - def rm_tag(tag_name) - cache.expire(:tag_names) - @tags = nil - - gitlab_shell.rm_tag(path_with_namespace, tag_name) - end - - def round_commit_count - if commit_count > 10000 - '10000+' - elsif commit_count > 5000 - '5000+' - elsif commit_count > 1000 - '1000+' - else - commit_count - end - end - - def branch_names - cache.fetch(:branch_names) { raw_repository.branch_names } - end - - def tag_names - cache.fetch(:tag_names) { raw_repository.tag_names } - end - - def commit_count - cache.fetch(:commit_count) do - begin - raw_repository.commit_count(self.root_ref) - rescue - 0 - end - end - end - - # Return repo size in megabytes - # Cached in redis - def size - cache.fetch(:size) { raw_repository.size } - end - - def expire_cache - %i(size branch_names tag_names commit_count graph_log - readme version contribution_guide changelog license).each do |key| - cache.expire(key) - end - end - - def graph_log - cache.fetch(:graph_log) do - commits = raw_repository.log(limit: 6000, skip_merges: true, - ref: root_ref) - - commits.map do |rugged_commit| - commit = Gitlab::Git::Commit.new(rugged_commit) - - { - author_name: commit.author_name, - author_email: commit.author_email, - additions: commit.stats.additions, - deletions: commit.stats.deletions, - } - end - end - end - - def lookup_cache - @lookup_cache ||= {} - end - - def method_missing(m, *args, &block) - if m == :lookup && !block_given? - lookup_cache[m] ||= {} - lookup_cache[m][args.join(":")] ||= raw_repository.send(m, *args, &block) - else - raw_repository.send(m, *args, &block) - end - end - - def respond_to?(method) - return true if raw_repository.respond_to?(method) - - super - end - - def blob_at(sha, path) - Gitlab::Git::Blob.find(self, sha, path) - end - - def blob_by_oid(oid) - Gitlab::Git::Blob.raw(self, oid) - end - - def readme - cache.fetch(:readme) { tree(:head).readme } - end - - def version - cache.fetch(:version) do - tree(:head).blobs.find do |file| - file.name.downcase == 'version' - end - end - end - - def contribution_guide - cache.fetch(:contribution_guide) do - tree(:head).blobs.find do |file| - file.contributing? - end - end - end - - def changelog - cache.fetch(:changelog) do - tree(:head).blobs.find do |file| - file.name =~ /\A(changelog|history)/i - end - end - end - - def license - cache.fetch(:license) do - tree(:head).blobs.find do |file| - file.name =~ /\Alicense/i - end - end - end - - def head_commit - @head_commit ||= commit(self.root_ref) - end - - def head_tree - @head_tree ||= Tree.new(self, head_commit.sha, nil) - end - - def tree(sha = :head, path = nil) - if sha == :head - if path.nil? - return head_tree - else - sha = head_commit.sha - end - 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) - args = %W(git rev-list --max-count=1 #{sha} -- #{path}) - sha = Gitlab::Popen.popen(args, path_to_repo).first.strip - commit(sha) - end - - # Remove archives older than 2 hours - def clean_old_archives - repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path - - return unless File.directory?(repository_downloads_path) - - Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) - end - - def branches_sorted_by(value) - case value - when 'recently_updated' - branches.sort do |a, b| - commit(b.target).committed_date <=> commit(a.target).committed_date - end - when 'last_updated' - branches.sort do |a, b| - commit(a.target).committed_date <=> commit(b.target).committed_date - end - else - branches - end - end - - def contributors - commits = self.commits(nil, nil, 2000, 0, true) - - commits.group_by(&:author_email).map do |email, commits| - contributor = Gitlab::Contributor.new - contributor.email = email - - commits.each do |commit| - if contributor.name.blank? - contributor.name = commit.author_name - end - - contributor.commits += 1 - end - - contributor - end - end - - def blob_for_diff(commit, diff) - file = blob_at(commit.id, diff.new_path) - - unless file - file = prev_blob_for_diff(commit, diff) - end - - file - end - - def prev_blob_for_diff(commit, diff) - if commit.parent_id - blob_at(commit.parent_id, diff.old_path) - end - end - - def branch_names_contains(sha) - args = %W(git branch --contains #{sha}) - names = Gitlab::Popen.popen(args, path_to_repo).first - - if names.respond_to?(:split) - names = names.split("\n").map(&:strip) - - names.each do |name| - name.slice! '* ' - end - - names - else - [] - end - end - - def tag_names_contains(sha) - args = %W(git tag --contains #{sha}) - names = Gitlab::Popen.popen(args, path_to_repo).first - - if names.respond_to?(:split) - names = names.split("\n").map(&:strip) - - names.each do |name| - name.slice! '* ' - end - - names - else - [] - end - end - - def branches - @branches ||= raw_repository.branches - end - - def tags - @tags ||= raw_repository.tags - end - - def root_ref - @root_ref ||= raw_repository.root_ref - end - - private - - def cache - @cache ||= RepositoryCache.new(path_with_namespace) - end -end diff --git a/app/models/service.rb b/app/models/service.rb deleted file mode 100644 index 393cf55a69f..00000000000 --- a/app/models/service.rb +++ /dev/null @@ -1,153 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# - -# To add new service you should build a class inherited from Service -# and implement a set of methods -class Service < ActiveRecord::Base - include Sortable - serialize :properties, JSON - - default_value_for :active, false - default_value_for :push_events, true - default_value_for :issues_events, true - default_value_for :merge_requests_events, true - default_value_for :tag_push_events, true - default_value_for :note_events, true - - after_initialize :initialize_properties - - belongs_to :project - has_one :service_hook - - validates :project_id, presence: true, unless: Proc.new { |service| service.template? } - - scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } - - scope :push_hooks, -> { where(push_events: true, active: true) } - scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } - scope :issue_hooks, -> { where(issues_events: true, active: true) } - scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } - scope :note_hooks, -> { where(note_events: true, active: true) } - - def activated? - active - end - - def template? - template - end - - def category - :common - end - - def initialize_properties - self.properties = {} if properties.nil? - end - - def title - # implement inside child - end - - def description - # implement inside child - end - - def help - # implement inside child - end - - def to_param - # implement inside child - end - - def fields - # implement inside child - [] - end - - def supported_events - %w(push tag_push issue merge_request) - end - - def execute - # implement inside child - end - - def can_test? - !project.empty_repo? - end - - # Provide convenient accessor methods - # for each serialized property. - def self.prop_accessor(*args) - args.each do |arg| - class_eval %{ - def #{arg} - properties['#{arg}'] - end - - def #{arg}=(value) - self.properties['#{arg}'] = value - end - } - end - end - - def async_execute(data) - return unless supported_events.include?(data[:object_kind]) - - Sidekiq::Client.enqueue(ProjectServiceWorker, id, data) - end - - def issue_tracker? - self.category == :issue_tracker - end - - def self.available_services_names - %w( - asana - assembla - bamboo - buildkite - campfire - custom_issue_tracker - emails_on_push - external_wiki - flowdock - gemnasium - gitlab_ci - hipchat - irker - jira - pivotaltracker - pushover - redmine - slack - teamcity - ) - end - - def self.create_from_template(project_id, template) - service = template.dup - service.template = false - service.project_id = project_id - service if service.save - end -end diff --git a/app/models/snippet.rb b/app/models/snippet.rb deleted file mode 100644 index b35e72c4bdb..00000000000 --- a/app/models/snippet.rb +++ /dev/null @@ -1,103 +0,0 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# expires_at :datetime -# type :string(255) -# visibility_level :integer default(0), not null -# - -class Snippet < ActiveRecord::Base - include Sortable - include Linguist::BlobHelper - include Gitlab::VisibilityLevel - - default_value_for :visibility_level, Snippet::PRIVATE - - belongs_to :author, class_name: "User" - - has_many :notes, as: :noteable, dependent: :destroy - - delegate :name, :email, to: :author, prefix: true, allow_nil: true - - validates :author, presence: true - validates :title, presence: true, length: { within: 0..255 } - validates :file_name, - presence: true, - length: { within: 0..255 }, - format: { with: Gitlab::Regex.file_name_regex, - message: Gitlab::Regex.file_name_regex_message } - validates :content, presence: true - validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } - - # Scopes - scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) } - scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) } - scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) } - scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) } - scope :fresh, -> { order("created_at DESC") } - scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } - scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } - - def self.content_types - [ - ".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java", - ".haml", ".html", ".sass", ".scss", ".xml", ".php", ".erb", - ".js", ".sh", ".coffee", ".yml", ".md" - ] - end - - def data - content - end - - def hook_attrs - attributes - end - - def size - 0 - end - - def name - file_name - end - - def sanitized_file_name - file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '') - end - - def mode - nil - end - - def expired? - expires_at && expires_at < Time.current - end - - def visibility_level_field - visibility_level - end - - class << self - def search(query) - where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") - end - - def search_code(query) - where('(content LIKE :query)', query: "%#{query}%") - end - - def accessible_to(user) - where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) - end - end -end diff --git a/app/models/subscription.rb b/app/models/subscription.rb deleted file mode 100644 index dd75d3ab8ba..00000000000 --- a/app/models/subscription.rb +++ /dev/null @@ -1,21 +0,0 @@ -# == Schema Information -# -# Table name: subscriptions -# -# id :integer not null, primary key -# user_id :integer -# subscribable_id :integer -# subscribable_type :string(255) -# subscribed :boolean -# created_at :datetime -# updated_at :datetime -# - -class Subscription < ActiveRecord::Base - belongs_to :user - belongs_to :subscribable, polymorphic: true - - validates :user_id, - uniqueness: { scope: [:subscribable_id, :subscribable_type] }, - presence: true -end diff --git a/app/models/tree.rb b/app/models/tree.rb deleted file mode 100644 index f279e896cda..00000000000 --- a/app/models/tree.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Tree - include Gitlab::MarkdownHelper - - attr_accessor :repository, :sha, :path, :entries - - def initialize(repository, sha, path = '/') - path = '/' if path.blank? - - @repository = repository - @sha = sha - @path = path - - git_repo = @repository.raw_repository - @entries = Gitlab::Git::Tree.where(git_repo, @sha, @path) - end - - def readme - return @readme if defined?(@readme) - - available_readmes = blobs.select(&:readme?) - - if available_readmes.count == 0 - return @readme = nil - end - - # Take the first previewable readme, or the first available readme, if we - # can't preview any of them - readme_tree = available_readmes.find do |readme| - previewable?(readme.name) - end || available_readmes.first - - readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) - - git_repo = repository.raw_repository - @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) - end - - def trees - @entries.select(&:dir?) - end - - def blobs - @entries.select(&:file?) - end - - 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 deleted file mode 100644 index d6b93afe739..00000000000 --- a/app/models/user.rb +++ /dev/null @@ -1,614 +0,0 @@ -# == Schema Information -# -# Table name: users -# -# id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime -# updated_at :datetime -# name :string(255) -# admin :boolean default(FALSE), not null -# projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# theme_id :integer default(1), not null -# bio :string(255) -# failed_attempts :integer default(0) -# locked_at :datetime -# username :string(255) -# can_create_group :boolean default(TRUE), not null -# can_create_team :boolean default(TRUE), not null -# state :string(255) -# color_scheme_id :integer default(1), not null -# notification_level :integer default(1), not null -# password_expires_at :datetime -# created_by_id :integer -# last_credential_check_at :datetime -# avatar :string(255) -# confirmation_token :string(255) -# confirmed_at :datetime -# confirmation_sent_at :datetime -# unconfirmed_email :string(255) -# hide_no_ssh_key :boolean default(FALSE) -# website_url :string(255) default(""), not null -# github_access_token :string(255) -# gitlab_access_token :string(255) -# notification_email :string(255) -# hide_no_password :boolean default(FALSE) -# password_automatically_set :boolean default(FALSE) -# bitbucket_access_token :string(255) -# bitbucket_access_token_secret :string(255) -# public_email :string(255) default(""), not null -# - -require 'carrierwave/orm/activerecord' -require 'file_size_validator' - -class User < ActiveRecord::Base - include Sortable - include Gitlab::ConfigHelper - include TokenAuthenticatable - extend Gitlab::ConfigHelper - include Gitlab::CurrentSettings - - default_value_for :admin, false - default_value_for :can_create_group, gitlab_config.default_can_create_group - default_value_for :can_create_team, false - default_value_for :hide_no_ssh_key, false - default_value_for :hide_no_password, false - default_value_for :theme_id, gitlab_config.default_theme - - devise :database_authenticatable, :lockable, :async, - :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable, :registerable - - attr_accessor :force_random_password - - # Virtual attribute for authenticating by either username or email - attr_accessor :login - - # - # Relations - # - - # Namespace for personal projects - has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace" - - # Profile - has_many :keys, dependent: :destroy - has_many :emails, dependent: :destroy - has_many :identities, dependent: :destroy - - # Groups - has_many :members, dependent: :destroy - has_many :project_members, source: 'ProjectMember' - has_many :group_members, source: 'GroupMember' - has_many :groups, through: :group_members - has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group - has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group - - # Projects - has_many :groups_projects, through: :groups, source: :projects - has_many :personal_projects, through: :namespace, source: :projects - has_many :projects, through: :project_members - has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' - has_many :users_star_projects, dependent: :destroy - has_many :starred_projects, through: :users_star_projects, source: :project - - has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" - has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' - has_many :issues, dependent: :destroy, foreign_key: :author_id - has_many :notes, dependent: :destroy, foreign_key: :author_id - has_many :merge_requests, dependent: :destroy, foreign_key: :author_id - has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" - has_many :subscriptions, dependent: :destroy - has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" - has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" - has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" - has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy - - - # - # Validations - # - validates :name, presence: true - validates :email, presence: true, email: { strict_mode: true }, uniqueness: true - validates :notification_email, presence: true, email: { strict_mode: true } - validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true - validates :bio, length: { maximum: 255 }, allow_blank: true - validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } - validates :username, - presence: true, - uniqueness: { case_sensitive: false }, - exclusion: { in: Gitlab::Blacklist.path }, - format: { with: Gitlab::Regex.namespace_regex, - message: Gitlab::Regex.namespace_regex_message } - - 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? } - validate :owns_notification_email, if: ->(user) { user.notification_email_changed? } - validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - - before_validation :generate_password, on: :create - before_validation :sanitize_attrs - before_validation :set_notification_email, if: ->(user) { user.email_changed? } - before_validation :set_public_email, if: ->(user) { user.public_email_changed? } - - before_save :ensure_authentication_token - after_save :ensure_namespace_correct - after_initialize :set_projects_limit - after_create :post_create_hook - after_destroy :post_destroy_hook - - - alias_attribute :private_token, :authentication_token - - delegate :path, to: :namespace, allow_nil: true, prefix: true - - state_machine :state, initial: :active do - event :block do - transition active: :blocked - end - - event :activate do - transition blocked: :active - end - end - - mount_uploader :avatar, AvatarUploader - - # Scopes - scope :admins, -> { where(admin: true) } - scope :blocked, -> { with_state(:blocked) } - scope :active, -> { with_state(:active) } - scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } - scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } - - # - # Class methods - # - class << self - # Devise method overridden to allow sign in with email or username - def find_for_database_authentication(warden_conditions) - conditions = warden_conditions.dup - if login = conditions.delete(:login) - where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first - else - where(conditions).first - end - end - - def sort(method) - case method.to_s - when 'recent_sign_in' then reorder(last_sign_in_at: :desc) - when 'oldest_sign_in' then reorder(last_sign_in_at: :asc) - else - order_by(method) - 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 - when "admins"; self.admins - when "blocked"; self.blocked - when "wop"; self.without_projects - else - self.active - end - end - - def search(query) - where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") - end - - def by_login(login) - where('lower(username) = :value OR lower(email) = :value', - value: login.to_s.downcase).first - end - - def by_username_or_id(name_or_id) - where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first - end - - def build_user(attrs = {}) - User.new(attrs) - end - end - - # - # Instance methods - # - - def to_param - username - end - - def notification - @notification ||= Notification.new(self) - end - - def generate_password - if self.force_random_password - self.password = self.password_confirmation = Devise.friendly_token.first(8) - end - end - - def generate_reset_token - @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token) - - self.reset_password_token = enc - self.reset_password_sent_at = Time.now.utc - - @reset_token - end - - def namespace_uniq - namespace_name = self.username - existing_namespace = Namespace.by_path(namespace_name) - if existing_namespace && existing_namespace != self.namespace - self.errors.add :username, "already exists" - end - end - - def avatar_type - unless self.avatar.image? - self.errors.add :avatar, "only images allowed" - end - end - - def unique_email - self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email) - end - - def owns_notification_email - self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email) - end - - # Groups user has access to - def authorized_groups - @authorized_groups ||= begin - group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) - Group.where(id: group_ids) - end - end - - - # Projects user has access to - def authorized_projects - @authorized_projects ||= begin - project_ids = personal_projects.pluck(:id) - project_ids.push(*groups_projects.pluck(:id)) - project_ids.push(*projects.pluck(:id).uniq) - Project.where(id: project_ids) - end - end - - def owned_projects - @owned_projects ||= begin - Project.where(namespace_id: owned_groups.pluck(:id).push(namespace.id)).joins(:namespace) - end - end - - # Team membership in authorized projects - def tm_in_authorized_projects - ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id) - end - - def is_admin? - admin - end - - def require_ssh_key? - keys.count == 0 - end - - def require_password? - password_automatically_set? && !ldap_user? - end - - def can_change_username? - gitlab_config.username_changing_enabled - end - - def can_create_project? - projects_limit_left > 0 - end - - def can_create_group? - can?(:create_group, nil) - end - - def abilities - Ability.abilities - end - - def can_select_namespace? - several_namespaces? || admin - 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.cared(self) - end - - def projects_limit_left - projects_limit - personal_projects.count - 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? - owned_groups.any? || masters_groups.any? - end - - def namespace_id - namespace.try :id - end - - def name_with_username - "#{name} (#{username})" - end - - def tm_of(project) - project.project_member_by_id(self.id) - end - - def already_forked?(project) - !!fork_of(project) - end - - def fork_of(project) - links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects) - - if links.any? - links.first.forked_to_project - else - nil - end - end - - def ldap_user? - identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) - end - - def ldap_identity - @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) - end - - def project_deploy_keys - DeployKey.in_projects(self.authorized_projects.pluck(:id)) - end - - def accessible_deploy_keys - @accessible_deploy_keys ||= begin - key_ids = project_deploy_keys.pluck(:id) - key_ids.push(*DeployKey.are_public.pluck(:id)) - DeployKey.where(id: key_ids) - end - end - - def created_by - User.find_by(id: created_by_id) if created_by_id - end - - def sanitize_attrs - %w(name username skype linkedin twitter bio).each do |attr| - value = self.send(attr) - self.send("#{attr}=", Sanitize.clean(value)) if value.present? - end - end - - def set_notification_email - if self.notification_email.blank? || !self.all_emails.include?(self.notification_email) - self.notification_email = self.email - end - end - - def set_public_email - if self.public_email.blank? || !self.all_emails.include?(self.public_email) - self.public_email = '' - end - end - - def set_projects_limit - connection_default_value_defined = new_record? && !projects_limit_changed? - return unless self.projects_limit.nil? || connection_default_value_defined - - self.projects_limit = current_application_settings.default_projects_limit - end - - def requires_ldap_check? - if !Gitlab.config.ldap.enabled - false - elsif ldap_user? - !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now - else - false - end - end - - def solo_owned_groups - @solo_owned_groups ||= owned_groups.select do |group| - group.owners == [self] - end - end - - def with_defaults - User.defaults.each do |k, v| - self.send("#{k}=", v) - end - - self - end - - def can_leave_project?(project) - project.namespace != namespace && - project.project_member(self) - end - - # Reset project events cache related to this user - # - # Since we do cache @event we need to reset cache in special cases: - # * when the user changes their avatar - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(author_id: self.id). - order('id DESC').limit(1000). - update_all(updated_at: Time.now) - end - - def full_website_url - return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\// - - website_url - end - - def short_website_url - website_url.sub(/\Ahttps?:\/\//, '') - end - - def all_ssh_keys - keys.map(&:key) - end - - def temp_oauth_email? - email.start_with?('temp-email-for-oauth') - end - - def public_profile? - authorized_projects.public_only.any? - end - - def avatar_url(size = nil) - if avatar.present? - [gitlab_config.url, avatar.url].join - else - GravatarService.new.execute(email, size) - end - end - - def all_emails - [self.email, *self.emails.map(&:email)] - end - - def hook_attrs - { - name: name, - username: username, - avatar_url: avatar_url - } - end - - def ensure_namespace_correct - # Ensure user has namespace - self.create_namespace!(path: self.username, name: self.username) unless self.namespace - - if self.username_changed? - self.namespace.update_attributes(path: self.username, name: self.username) - end - end - - def post_create_hook - log_info("User \"#{self.name}\" (#{self.email}) was created") - notification_service.new_user(self, @reset_token) if self.created_by_id - system_hook_service.execute_hooks_for(self, :create) - end - - def post_destroy_hook - log_info("User \"#{self.name}\" (#{self.email}) was removed") - system_hook_service.execute_hooks_for(self, :destroy) - end - - def notification_service - NotificationService.new - end - - def log_info(message) - Gitlab::AppLogger.info message - end - - def system_hook_service - SystemHooksService.new - end - - def starred?(project) - starred_projects.exists?(project) - end - - def toggle_star(project) - user_star_project = users_star_projects. - where(project: project, user: self).take - if user_star_project - user_star_project.destroy - else - UsersStarProject.create!(project: project, user: self) - end - end - - def manageable_namespaces - @manageable_namespaces ||= - begin - namespaces = [] - namespaces << namespace - namespaces += owned_groups - namespaces += masters_groups - end - end - - def oauth_authorized_tokens - Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil) - end - - def contributed_projects_ids - Event.contributions.where(author_id: self). - where("created_at > ?", Time.now - 1.year). - reorder(project_id: :desc). - select(:project_id). - uniq.map(&:project_id) - end -end diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb deleted file mode 100644 index 3d49cb05949..00000000000 --- a/app/models/users_star_project.rb +++ /dev/null @@ -1,19 +0,0 @@ -# == Schema Information -# -# Table name: users_star_projects -# -# id :integer not null, primary key -# project_id :integer not null -# user_id :integer not null -# created_at :datetime -# updated_at :datetime -# - -class UsersStarProject < ActiveRecord::Base - belongs_to :project, counter_cache: :star_count - belongs_to :user - - validates :user, presence: true - validates :user_id, uniqueness: { scope: [:project_id] } - validates :project, presence: true -end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb deleted file mode 100644 index e9413c34bae..00000000000 --- a/app/models/wiki_page.rb +++ /dev/null @@ -1,201 +0,0 @@ -class WikiPage - include ActiveModel::Validations - include ActiveModel::Conversion - include StaticModel - extend ActiveModel::Naming - - def self.primary_key - 'slug' - end - - def self.model_name - ActiveModel::Name.new(self, nil, 'wiki') - end - - def to_key - [:slug] - end - - validates :title, presence: true - validates :content, presence: true - - # The Gitlab ProjectWiki instance. - attr_reader :wiki - - # The raw Gollum::Page instance. - attr_reader :page - - # The attributes Hash used for storing and validating - # new Page values before writing to the Gollum repository. - attr_accessor :attributes - - def initialize(wiki, page = nil, persisted = false) - @wiki = wiki - @page = page - @persisted = persisted - @attributes = {}.with_indifferent_access - - set_attributes if persisted? - end - - # The escaped URL path of this page. - def slug - @attributes[:slug] - end - - alias_method :to_param, :slug - - # The formatted title of this page. - def title - if @attributes[:title] - @attributes[:title].gsub(/-+/, ' ') - else - "" - end - end - - # Sets the title of this page. - def title=(new_title) - @attributes[:title] = new_title - end - - # The raw content of this page. - def content - @attributes[:content] ||= if @page - @page.raw_data - end - end - - # The processed/formatted content of this page. - def formatted_content - @attributes[:formatted_content] ||= if @page - @page.formatted_data - end - end - - # The markup format for the page. - def format - @attributes[:format] || :markdown - end - - # The commit message for this page version. - def message - version.try(:message) - end - - # The Gitlab Commit instance for this page. - def version - return nil unless persisted? - - @version ||= @page.version - end - - # Returns an array of Gitlab Commit instances. - def versions - return [] unless persisted? - - @page.versions - end - - def commit - versions.first - end - - # Returns the Date that this latest version was - # created on. - def created_at - @page.version.date - end - - # Returns boolean True or False if this instance - # is an old version of the page. - def historical? - @page.historical? - end - - # Returns boolean True or False if this instance - # has been fully saved to disk or not. - def persisted? - @persisted == true - end - - # Creates a new Wiki Page. - # - # attr - Hash of attributes to set on the new page. - # :title - The title for the new page. - # :content - The raw markup content. - # :format - Optional symbol representing the - # content format. Can be any type - # listed in the ProjectWiki::MARKUPS - # Hash. - # :message - Optional commit message to set on - # the new page. - # - # Returns the String SHA1 of the newly created page - # or False if the save was unsuccessful. - def create(attr = {}) - @attributes.merge!(attr) - - save :create_page, title, content, format, message - end - - # Updates an existing Wiki Page, creating a new version. - # - # new_content - The raw markup content to replace the existing. - # format - Optional symbol representing the content format. - # See ProjectWiki::MARKUPS Hash for available formats. - # message - Optional commit message to set on the new version. - # - # Returns the String SHA1 of the newly created page - # or False if the save was unsuccessful. - def update(new_content = "", format = :markdown, message = nil) - @attributes[:content] = new_content - @attributes[:format] = format - - save :update_page, @page, content, format, message - end - - # Destroys the Wiki Page. - # - # Returns boolean True or False. - def delete - if wiki.delete_page(@page) - true - else - false - end - end - - private - - def set_attributes - attributes[:slug] = @page.escaped_url_path - attributes[:title] = @page.title - attributes[:format] = @page.format - end - - def save(method, *args) - project_wiki = wiki - if valid? && project_wiki.send(method, *args) - - page_details = if method == :update_page - # Use url_path instead of path to omit format extension - @page.url_path - else - title - end - - page_title, page_dir = project_wiki.page_title_and_dir(page_details) - gollum_wiki = project_wiki.wiki - @page = gollum_wiki.paged(page_title, page_dir) - - set_attributes - - @persisted = true - else - errors.add(:base, project_wiki.error_message) if project_wiki.error_message - @persisted = false - end - @persisted - end -end |