summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
authorAlfredo Sumaran <alfredo@gitlab.com>2016-02-22 21:15:40 -0500
committerAlfredo Sumaran <alfredo@gitlab.com>2016-02-22 21:15:40 -0500
commit67948368aec144ff7e9997ce184cb4d111622f08 (patch)
treec94e382fb1aca074b6ce503255564828cb387e36 /app/models
parent608012629cc90337aba6c5bd4b2b73b530091fb0 (diff)
parent7c8099853621cc2a244bf6e0fd37a8b503d1ec5e (diff)
downloadgitlab-ce-sidebar-sizing-higher-viewport.tar.gz
Merge branch 'master' into sidebar-sizing-higher-viewportsidebar-sizing-higher-viewport
Diffstat (limited to 'app/models')
-rw-r--r--app/models/blob.rb34
-rw-r--r--app/models/ci/build.rb52
-rw-r--r--app/models/ci/runner.rb6
-rw-r--r--app/models/commit.rb38
-rw-r--r--app/models/commit_status.rb16
-rw-r--r--app/models/concerns/issuable.rb25
-rw-r--r--app/models/label.rb19
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--app/models/milestone.rb14
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/project_team.rb2
-rw-r--r--app/models/repository.rb83
-rw-r--r--app/models/todo.rb53
-rw-r--r--app/models/user.rb2
15 files changed, 327 insertions, 37 deletions
diff --git a/app/models/blob.rb b/app/models/blob.rb
new file mode 100644
index 00000000000..8ee9f3006b2
--- /dev/null
+++ b/app/models/blob.rb
@@ -0,0 +1,34 @@
+# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
+class Blob < SimpleDelegator
+ # Wrap a Gitlab::Git::Blob object, or return nil when given nil
+ #
+ # This method prevents the decorated object from evaluating to "truthy" when
+ # given a nil value. For example:
+ #
+ # blob = Blob.new(nil)
+ # puts "truthy" if blob # => "truthy"
+ #
+ # blob = Blob.decorate(nil)
+ # puts "truthy" if blob # No output
+ def self.decorate(blob)
+ return if blob.nil?
+
+ new(blob)
+ end
+
+ def svg?
+ text? && language && language.name == 'SVG'
+ end
+
+ def to_partial_path
+ if lfs_pointer?
+ 'download'
+ elsif image? || svg?
+ 'image'
+ elsif text?
+ 'text'
+ else
+ 'download'
+ end
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 623edd8bc57..1227458e525 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -31,15 +31,19 @@
# artifacts_file :text
# gl_project_id :integer
# artifacts_metadata :text
+# erased_by_id :integer
+# erased_at :datetime
#
module Ci
class Build < CommitStatus
include Gitlab::Application.routes.url_helpers
+
LAZY_ATTRIBUTES = ['trace']
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
+ belongs_to :erased_by, class_name: 'User'
serialize :options
@@ -103,23 +107,22 @@ module Ci
end
state_machine :status, initial: :pending do
- after_transition pending: :running do |build, transition|
+ after_transition pending: :running do |build|
build.execute_hooks
end
- after_transition any => [:success, :failed, :canceled] do |build, transition|
- return unless build.project
+ # We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
+ around_transition any => [:success, :failed, :canceled] do |build, block|
+ block.call
+ build.commit.create_next_builds(build) if build.commit
+ end
+ after_transition any => [:success, :failed, :canceled] do |build|
build.update_coverage
- build.commit.create_next_builds(build)
build.execute_hooks
end
end
- def ignored?
- failed? && allow_failure?
- end
-
def retryable?
project.builds_enabled? && commands.present?
end
@@ -179,6 +182,7 @@ module Ci
end
def update_coverage
+ return unless project
coverage_regex = project.build_coverage_regex
return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex)
@@ -203,6 +207,10 @@ module Ci
end
end
+ def has_trace?
+ raw_trace.present?
+ end
+
def raw_trace
if File.file?(path_to_trace)
File.read(path_to_trace)
@@ -330,6 +338,7 @@ module Ci
end
def execute_hooks
+ return unless project
build_data = Gitlab::BuildDataBuilder.build(self)
project.execute_hooks(build_data.dup, :build_hooks)
project.execute_services(build_data.dup, :build_hooks)
@@ -359,6 +368,33 @@ module Ci
Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
end
+ def erase(opts = {})
+ return false unless erasable?
+
+ remove_artifacts_file!
+ remove_artifacts_metadata!
+ erase_trace!
+ update_erased!(opts[:erased_by])
+ end
+
+ def erasable?
+ complete? && (artifacts? || has_trace?)
+ end
+
+ def erased?
+ !self.erased_at.nil?
+ end
+
+ private
+
+ def erase_trace!
+ self.trace = nil
+ end
+
+ def update_erased!(user = nil)
+ self.update(erased_by: user, erased_at: Time.now)
+ end
+
private
def yaml_variables
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 38b20cd7faa..e725a6d468c 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -22,6 +22,7 @@ module Ci
extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago
+ AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -38,6 +39,11 @@ module Ci
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
scope :ordered, ->() { order(id: :desc) }
+ scope :owned_or_shared, ->(project_id) do
+ joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
+ .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
+ end
+
acts_as_taggable
def self.search(query)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 23b771aebb7..3224f5457f0 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -215,6 +215,44 @@ class Commit
ci_commit.try(:status) || :not_found
end
+ def revert_branch_name
+ "revert-#{short_id}"
+ end
+
+ def revert_description
+ if merged_merge_request
+ "This reverts merge request #{merged_merge_request.to_reference}"
+ else
+ "This reverts commit #{sha}"
+ end
+ end
+
+ def revert_message
+ %Q{Revert "#{title}"\n\n#{revert_description}}
+ end
+
+ def reverts_commit?(commit)
+ description? && description.include?(commit.revert_description)
+ end
+
+ def merge_commit?
+ parents.size > 1
+ end
+
+ def merged_merge_request
+ return @merged_merge_request if defined?(@merged_merge_request)
+
+ @merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit?
+ end
+
+ def has_been_reverted?(current_user = nil, noteable = self)
+ Gitlab::ReferenceExtractor.lazily do
+ noteable.notes.system.flat_map do |note|
+ note.all_references(current_user).commits
+ end
+ end.any? { |commit_ref| commit_ref.reverts_commit?(self) }
+ end
+
private
def repo_changes
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 66e0502fc0c..7ef50836322 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -75,16 +75,16 @@ class CommitStatus < ActiveRecord::Base
transition [:pending, :running] => :canceled
end
- after_transition pending: :running do |build, transition|
- build.update_attributes started_at: Time.now
+ after_transition pending: :running do |commit_status|
+ commit_status.update_attributes started_at: Time.now
end
- after_transition any => [:success, :failed, :canceled] do |build, transition|
- build.update_attributes finished_at: Time.now
+ after_transition any => [:success, :failed, :canceled] do |commit_status|
+ commit_status.update_attributes finished_at: Time.now
end
- after_transition [:pending, :running] => :success do |build, transition|
- MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.project, nil).trigger(build)
+ after_transition [:pending, :running] => :success do |commit_status|
+ MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
end
state :pending, value: 'pending'
@@ -113,6 +113,10 @@ class CommitStatus < ActiveRecord::Base
canceled? || success? || failed?
end
+ def ignored?
+ failed? && allow_failure?
+ end
+
def duration
if started_at && finished_at
finished_at - started_at
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 5136d0196a5..e5f089fb8a0 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -69,10 +69,35 @@ module Issuable
case method.to_s
when 'milestone_due_asc' then order_milestone_due_asc
when 'milestone_due_desc' then order_milestone_due_desc
+ when 'downvotes_desc' then order_downvotes_desc
+ when 'upvotes_desc' then order_upvotes_desc
else
order_by(method)
end
end
+
+ def order_downvotes_desc
+ order_votes_desc('thumbsdown')
+ end
+
+ def order_upvotes_desc
+ order_votes_desc('thumbsup')
+ end
+
+ def order_votes_desc(award_emoji_name)
+ issuable_table = self.arel_table
+ note_table = Note.arel_table
+
+ join_clause = issuable_table.join(note_table, Arel::Nodes::OuterJoin).on(
+ note_table[:noteable_id].eq(issuable_table[:id]).and(
+ note_table[:noteable_type].eq(self.name).and(
+ note_table[:is_award].eq(true).and(note_table[:note].eq(award_emoji_name))
+ )
+ )
+ ).join_sources
+
+ joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC")
+ end
end
def today?
diff --git a/app/models/label.rb b/app/models/label.rb
index 220da10a6ab..07a1db4abe5 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -2,13 +2,14 @@
#
# Table name: labels
#
-# id :integer not null, primary key
-# title :string(255)
-# color :string(255)
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# template :boolean default(FALSE)
+# id :integer not null, primary key
+# title :string(255)
+# color :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# template :boolean default(FALSE)
+# description :string(255)
#
class Label < ActiveRecord::Base
@@ -85,6 +86,10 @@ class Label < ActiveRecord::Base
issues.opened.count
end
+ def closed_issues_count
+ issues.closed.count
+ end
+
def template?
template
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1be8061e53d..1543ef311d7 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -24,6 +24,7 @@
# merge_params :text
# merge_when_build_succeeds :boolean default(FALSE), not null
# merge_user_id :integer
+# merge_commit_sha :string
#
require Rails.root.join("app/models/commit")
@@ -137,7 +138,7 @@ class MergeRequest < ActiveRecord::Base
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) }
- scope :opened, -> { with_state(:opened) }
+ scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) }
scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
@@ -532,4 +533,12 @@ class MergeRequest < ActiveRecord::Base
[diff_base_commit, last_commit]
end
+
+ def merge_commit
+ @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
+ end
+
+ def can_be_reverted?(current_user = nil)
+ merge_commit && !merge_commit.has_been_reverted?(current_user, self)
+ end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9c4476c768e..cbe65d70997 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base
belongs_to :project
has_many :issues
+ has_many :labels, through: :issues
has_many :merge_requests
has_many :participants, through: :issues, source: :assignee
@@ -109,6 +110,19 @@ class Milestone < ActiveRecord::Base
0
end
+ # Returns the elapsed time (in percent) since the Milestone creation date until today.
+ # If the Milestone doesn't have a due_date then returns 0 since we can't calculate the elapsed time.
+ # If the Milestone is overdue then it returns 100%.
+ def percent_time_used
+ return 0 unless due_date
+ return 100 if expired?
+
+ duration = ((created_at - due_date.to_datetime) / 1.day)
+ days_elapsed = ((created_at - Time.now) / 1.day)
+
+ ((days_elapsed.to_f / duration) * 100).floor
+ end
+
def expires_at
if due_date
if due_date.past?
diff --git a/app/models/note.rb b/app/models/note.rb
index 55255d22c2f..d287e0f3c6d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -37,6 +37,8 @@ class Note < ActiveRecord::Base
belongs_to :author, class_name: "User"
belongs_to :updated_by, class_name: "User"
+ has_many :todos, dependent: :destroy
+
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
@@ -375,6 +377,7 @@ class Note < ActiveRecord::Base
#
def set_award!
return unless awards_supported? && contains_emoji_only?
+
self.is_award = true
self.note = award_emoji_name
end
@@ -382,7 +385,7 @@ class Note < ActiveRecord::Base
private
def awards_supported?
- noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
+ (noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)) && !for_diff_line?
end
def contains_emoji_only?
diff --git a/app/models/project.rb b/app/models/project.rb
index a43878ebcad..95ad88c76ae 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -382,6 +382,10 @@ class Project < ActiveRecord::Base
external_import? || forked?
end
+ def no_import?
+ import_status == 'none'
+ end
+
def external_import?
import_url.present?
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 9f380a382cb..9629c7e1bb9 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -136,7 +136,7 @@ class ProjectTeam
end
def human_max_access(user_id)
- Gitlab::Access.options.key max_member_access(user_id)
+ Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end
# This method assumes project and group members are eager loaded for optimal
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ba275fd9803..e050bd45254 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -23,13 +23,11 @@ class Repository
def raw_repository
return nil unless path_with_namespace
- @raw_repository ||= begin
- repo = Gitlab::Git::Repository.new(path_to_repo)
- repo.autocrlf = :input
- repo
- rescue Gitlab::Git::Repository::NoRepository
- nil
- end
+ @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
+ end
+
+ def update_autocrlf_option
+ raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
end
# Return absolute path to repository
@@ -40,7 +38,12 @@ class Repository
end
def exists?
- raw_repository
+ return false unless raw_repository
+
+ raw_repository.rugged
+ true
+ rescue Gitlab::Git::Repository::NoRepository
+ false
end
def empty?
@@ -67,7 +70,7 @@ class Repository
end
def commit(id = 'HEAD')
- return nil unless raw_repository
+ return nil unless exists?
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit, @project) if commit
commit
@@ -236,6 +239,19 @@ class Repository
end
expire_branch_cache(branch_name)
+
+ # This ensures this particular cache is flushed after the first commit to a
+ # new repository.
+ expire_emptiness_caches if empty?
+ end
+
+ # Expires _all_ caches, including those that would normally only be expired
+ # under specific conditions.
+ def expire_all_caches!
+ expire_cache
+ expire_root_ref_cache
+ expire_emptiness_caches
+ expire_has_visible_content_cache
end
def expire_branch_cache(branch_name = nil)
@@ -258,6 +274,14 @@ class Repository
@root_ref = nil
end
+ # Expires the cache(s) used to determine if a repository is empty or not.
+ def expire_emptiness_caches
+ cache.expire(:empty?)
+ @empty = nil
+
+ expire_has_visible_content_cache
+ end
+
def expire_has_visible_content_cache
cache.expire(:has_visible_content?)
@has_visible_content = nil
@@ -599,6 +623,34 @@ class Repository
end
end
+ def revert(user, commit, base_branch, target_branch = nil)
+ source_sha = find_branch(base_branch).target
+ target_branch ||= base_branch
+ args = [commit.id, source_sha]
+ args << { mainline: 1 } if commit.merge_commit?
+
+ revert_index = rugged.revert_commit(*args)
+ return false if revert_index.conflicts?
+
+ tree_id = revert_index.write_tree(rugged)
+ return false unless diff_exists?(source_sha, tree_id)
+
+ commit_with_hooks(user, target_branch) do |ref|
+ committer = user_to_committer(user)
+ source_sha = Rugged::Commit.create(rugged,
+ message: commit.revert_message,
+ author: committer,
+ committer: committer,
+ tree: tree_id,
+ parents: [rugged.lookup(source_sha)],
+ update_ref: ref)
+ end
+ end
+
+ def diff_exists?(sha1, sha2)
+ rugged.diff(sha1, sha2).size > 0
+ end
+
def merged_to_root_ref?(branch_name)
branch_commit = commit(branch_name)
root_ref_commit = commit(root_ref)
@@ -611,6 +663,8 @@ class Repository
end
def merge_base(first_commit_id, second_commit_id)
+ first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
+ second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
rugged.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError
nil
@@ -674,12 +728,15 @@ class Repository
end
def commit_with_hooks(current_user, branch)
+ update_autocrlf_option
+
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
+ target_branch = find_branch(branch)
was_empty = empty?
- unless was_empty
- oldrev = find_branch(branch).target
+ if !was_empty && target_branch
+ oldrev = target_branch.target
end
with_tmp_ref(oldrev) do |tmp_ref|
@@ -691,7 +748,7 @@ class Repository
end
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
- if was_empty
+ if was_empty || !target_branch
# Create branch
rugged.references.create(ref, newrev)
else
@@ -706,6 +763,8 @@ class Repository
end
end
end
+
+ newrev
end
end
diff --git a/app/models/todo.rb b/app/models/todo.rb
new file mode 100644
index 00000000000..34d71c1b0d3
--- /dev/null
+++ b/app/models/todo.rb
@@ -0,0 +1,53 @@
+# == Schema Information
+#
+# Table name: todos
+#
+# id :integer not null, primary key
+# user_id :integer not null
+# project_id :integer not null
+# target_id :integer not null
+# target_type :string not null
+# author_id :integer
+# note_id :integer
+# action :integer not null
+# state :string not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+class Todo < ActiveRecord::Base
+ ASSIGNED = 1
+ MENTIONED = 2
+
+ belongs_to :author, class_name: "User"
+ belongs_to :note
+ belongs_to :project
+ belongs_to :target, polymorphic: true, touch: true
+ belongs_to :user
+
+ delegate :name, :email, to: :author, prefix: true, allow_nil: true
+
+ validates :action, :project, :target, :user, presence: true
+
+ default_scope { reorder(id: :desc) }
+
+ scope :pending, -> { with_state(:pending) }
+ scope :done, -> { with_state(:done) }
+
+ state_machine :state, initial: :pending do
+ event :done do
+ transition pending: :done
+ end
+
+ state :pending
+ state :done
+ end
+
+ def body
+ if note.present?
+ note.note
+ else
+ target.title
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9fe94b13e52..02ff2456f2b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -140,7 +140,7 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
-
+ has_many :todos, dependent: :destroy
#
# Validations