summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-08-30 19:51:51 +0100
committerFilipa Lacerda <filipa@gitlab.com>2017-08-30 19:51:51 +0100
commitb6502103d4fd08a4046907b4b076b3611f017cb1 (patch)
tree784bb8e46e66a7e9a6b646fbbbef19b824cf03e7 /lib
parent9d25efd5c0c37982a16c3636e27cd3e5da0d7fdb (diff)
parent81f08d30e641dc1a6666022ab1f5d36dbcdced7e (diff)
downloadgitlab-ce-36917-branch-tooltip.tar.gz
Merge branch 'master' into 36917-branch-tooltip36917-branch-tooltip
* master: (26 commits) Fix invalid attribute used for time-ago-tooltip component Update latest artifacts doc Update share project with groups docs Remove tooltips from new sidebar Use `git update-ref --stdin -z` to delete refs Don't use public_send in destroy_conditionally! helper Fix MySQL failure for emoji autocomplete Replace 'project/user_lookup.feature' spinach test with an rspec analog Add changelog Add time stats to issue and merge request API end points Resolve new N+1 by adding preloads and metadata to issues end points Add missing N+1 test to issues spec Add time stats to API schema for issue and merge request end points Add time stats documentation to issue and merge request end points Improve migrations using triggers Increase z-index of dropdowns Just use a block Fix tests Try to make reserved ref names more obvious Resolve feedback on the MR: ...
Diffstat (limited to 'lib')
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/entities.rb44
-rw-r--r--lib/api/helpers.rb6
-rw-r--r--lib/api/issues.rb28
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/tags.rb2
-rw-r--r--lib/gitlab/database.rb8
-rw-r--r--lib/gitlab/database/grant.rb34
-rw-r--r--lib/gitlab/database/migration_helpers.rb36
-rw-r--r--lib/gitlab/git/repository.rb25
-rw-r--r--lib/tasks/gitlab/cleanup.rake2
11 files changed, 173 insertions, 16 deletions
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index b87f7cdbad1..a989394ad91 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -130,7 +130,7 @@ module API
commit = user_project.repository.commit(branch.dereferenced_target)
- destroy_conditionally!(commit, last_update_field: :authored_date) do
+ destroy_conditionally!(commit, last_updated: commit.authored_date) do
result = DeleteBranchService.new(user_project, current_user)
.execute(params[:branch])
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index e8dd61e493f..803b48dd88a 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -320,7 +320,10 @@ module API
end
class IssueBasic < ProjectEntity
- expose :label_names, as: :labels
+ expose :labels do |issue, options|
+ # Avoids an N+1 query since labels are preloaded
+ issue.labels.map(&:title).sort
+ end
expose :milestone, using: Entities::Milestone
expose :assignees, :author, using: Entities::UserBasic
@@ -329,13 +332,32 @@ module API
end
expose :user_notes_count
- expose :upvotes, :downvotes
+ expose :upvotes do |issue, options|
+ if options[:issuable_metadata]
+ # Avoids an N+1 query when metadata is included
+ options[:issuable_metadata][issue.id].upvotes
+ else
+ issue.upvotes
+ end
+ end
+ expose :downvotes do |issue, options|
+ if options[:issuable_metadata]
+ # Avoids an N+1 query when metadata is included
+ options[:issuable_metadata][issue.id].downvotes
+ else
+ issue.downvotes
+ end
+ end
expose :due_date
expose :confidential
expose :web_url do |issue, options|
Gitlab::UrlBuilder.build(issue)
end
+
+ expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
+ issue
+ end
end
class Issue < IssueBasic
@@ -365,10 +387,22 @@ module API
end
class IssuableTimeStats < Grape::Entity
+ format_with(:time_tracking_formatter) do |time_spent|
+ Gitlab::TimeTrackingFormatter.output(time_spent)
+ end
+
expose :time_estimate
expose :total_time_spent
expose :human_time_estimate
- expose :human_total_time_spent
+
+ with_options(format_with: :time_tracking_formatter) do
+ expose :total_time_spent, as: :human_total_time_spent
+ end
+
+ def total_time_spent
+ # Avoids an N+1 query since timelogs are preloaded
+ object.timelogs.map(&:time_spent).sum
+ end
end
class ExternalIssue < Grape::Entity
@@ -418,6 +452,10 @@ module API
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
+
+ expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
+ merge_request
+ end
end
class MergeRequest < MergeRequestBasic
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 84980864151..3d377fdb9eb 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -19,8 +19,10 @@ module API
end
end
- def destroy_conditionally!(resource, last_update_field: :updated_at)
- check_unmodified_since!(resource.public_send(last_update_field))
+ def destroy_conditionally!(resource, last_updated: nil)
+ last_updated ||= resource.updated_at
+
+ check_unmodified_since!(last_updated)
status 204
if block_given?
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 6503629e2a2..0297023226f 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -4,6 +4,8 @@ module API
before { authenticate! }
+ helpers ::Gitlab::IssuableMetadata
+
helpers do
def find_issues(args = {})
args = params.merge(args)
@@ -13,6 +15,7 @@ module API
args[:label_name] = args.delete(:labels)
issues = IssuesFinder.new(current_user, args).execute
+ .preload(:assignees, :labels, :notes, :timelogs)
issues.reorder(args[:order_by] => args[:sort])
end
@@ -65,7 +68,13 @@ module API
get do
issues = find_issues
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user
+ options = {
+ with: Entities::IssueBasic,
+ current_user: current_user,
+ issuable_metadata: issuable_meta_data(issues, 'Issue')
+ }
+
+ present paginate(issues), options
end
end
@@ -86,7 +95,13 @@ module API
issues = find_issues(group_id: group.id)
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user
+ options = {
+ with: Entities::IssueBasic,
+ current_user: current_user,
+ issuable_metadata: issuable_meta_data(issues, 'Issue')
+ }
+
+ present paginate(issues), options
end
end
@@ -109,7 +124,14 @@ module API
issues = find_issues(project_id: project.id)
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
+ options = {
+ with: Entities::IssueBasic,
+ current_user: current_user,
+ project: user_project,
+ issuable_metadata: issuable_meta_data(issues, 'Issue')
+ }
+
+ present paginate(issues), options
end
desc 'Get a single project issue' do
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 969c6064662..eec8d9357aa 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -21,7 +21,7 @@ module API
return merge_requests if args[:view] == 'simple'
merge_requests
- .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels)
+ .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels, :timelogs)
end
params :merge_requests_params do
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 81b17935b81..912415e3a7f 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -70,7 +70,7 @@ module API
commit = user_project.repository.commit(tag.dereferenced_target)
- destroy_conditionally!(commit, last_update_field: :authored_date) do
+ destroy_conditionally!(commit, last_updated: commit.authored_date) do
result = ::Tags::DestroyService.new(user_project, current_user)
.execute(params[:tag_name])
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index e001d25e7b7..a6ec75da385 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -9,6 +9,14 @@ module Gitlab
ActiveRecord::Base.configurations[Rails.env]
end
+ def self.username
+ config['username'] || ENV['USER']
+ end
+
+ def self.database_name
+ config['database']
+ end
+
def self.adapter_name
config['adapter']
end
diff --git a/lib/gitlab/database/grant.rb b/lib/gitlab/database/grant.rb
new file mode 100644
index 00000000000..aee3981e79a
--- /dev/null
+++ b/lib/gitlab/database/grant.rb
@@ -0,0 +1,34 @@
+module Gitlab
+ module Database
+ # Model that can be used for querying permissions of a SQL user.
+ class Grant < ActiveRecord::Base
+ self.table_name =
+ if Database.postgresql?
+ 'information_schema.role_table_grants'
+ else
+ 'mysql.user'
+ end
+
+ def self.scope_to_current_user
+ if Database.postgresql?
+ where('grantee = user')
+ else
+ where("CONCAT(User, '@', Host) = current_user()")
+ end
+ end
+
+ # Returns true if the current user can create and execute triggers on the
+ # given table.
+ def self.create_and_execute_trigger?(table)
+ priv =
+ if Database.postgresql?
+ where(privilege_type: 'TRIGGER', table_name: table)
+ else
+ where(Trigger_priv: 'Y')
+ end
+
+ priv.scope_to_current_user.any?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 5e2c6cc5cad..fb14798efe6 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -358,6 +358,8 @@ module Gitlab
raise 'rename_column_concurrently can not be run inside a transaction'
end
+ check_trigger_permissions!(table)
+
old_col = column_for(table, old)
new_type = type || old_col.type
@@ -430,6 +432,8 @@ module Gitlab
def cleanup_concurrent_column_rename(table, old, new)
trigger_name = rename_trigger_name(table, old, new)
+ check_trigger_permissions!(table)
+
if Database.postgresql?
remove_rename_triggers_for_postgresql(table, trigger_name)
else
@@ -485,14 +489,14 @@ module Gitlab
# Removes the triggers used for renaming a PostgreSQL column concurrently.
def remove_rename_triggers_for_postgresql(table, trigger)
- execute("DROP TRIGGER #{trigger} ON #{table}")
- execute("DROP FUNCTION #{trigger}()")
+ execute("DROP TRIGGER IF EXISTS #{trigger} ON #{table}")
+ execute("DROP FUNCTION IF EXISTS #{trigger}()")
end
# Removes the triggers used for renaming a MySQL column concurrently.
def remove_rename_triggers_for_mysql(trigger)
- execute("DROP TRIGGER #{trigger}_insert")
- execute("DROP TRIGGER #{trigger}_update")
+ execute("DROP TRIGGER IF EXISTS #{trigger}_insert")
+ execute("DROP TRIGGER IF EXISTS #{trigger}_update")
end
# Returns the (base) name to use for triggers when renaming columns.
@@ -625,6 +629,30 @@ module Gitlab
conn.llen("queue:#{queue_name}")
end
end
+
+ def check_trigger_permissions!(table)
+ unless Grant.create_and_execute_trigger?(table)
+ dbname = Database.database_name
+ user = Database.username
+
+ raise <<-EOF
+Your database user is not allowed to create, drop, or execute triggers on the
+table #{table}.
+
+If you are using PostgreSQL you can solve this by logging in to the GitLab
+database (#{dbname}) using a super user and running:
+
+ ALTER #{user} WITH SUPERUSER
+
+For MySQL you instead need to run:
+
+ GRANT ALL PRIVILEGES ON *.* TO #{user}@'%'
+
+Both queries will grant the user super user permissions, ensuring you don't run
+into similar problems in the future (e.g. when new tables are created).
+ EOF
+ end
+ end
end
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 03e2bec84dd..fb6504bdea0 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -17,6 +17,7 @@ module Gitlab
NoRepository = Class.new(StandardError)
InvalidBlobName = Class.new(StandardError)
InvalidRef = Class.new(StandardError)
+ GitError = Class.new(StandardError)
class << self
# Unlike `new`, `create` takes the storage path, not the storage name
@@ -246,6 +247,13 @@ module Gitlab
branch_names + tag_names
end
+ # Returns an Array of all ref names, except when it's matching pattern
+ #
+ # regexp - The pattern for ref names we don't want
+ def all_ref_names_except(regexp)
+ rugged.references.reject { |ref| ref.name =~ regexp }.map(&:name)
+ end
+
# Discovers the default branch based on the repository's available branches
#
# - If no branches are present, returns nil
@@ -591,6 +599,23 @@ module Gitlab
rugged.branches.delete(branch_name)
end
+ def delete_refs(*ref_names)
+ instructions = ref_names.map do |ref|
+ "delete #{ref}\x00\x00"
+ end
+
+ command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
+ message, status = Gitlab::Popen.popen(
+ command,
+ path) do |stdin|
+ stdin.write(instructions.join)
+ end
+
+ unless status.zero?
+ raise GitError.new("Could not delete refs #{ref_names}: #{message}")
+ end
+ end
+
# Create a new branch named **ref+ based on **stat_point+, HEAD by default
#
# Examples:
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index f76bef5f4bf..8ae1b6a626a 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -111,7 +111,7 @@ namespace :gitlab do
next unless id > max_iid
project.deployments.find(id).create_ref
- rugged.references.delete(ref)
+ project.repository.delete_refs(ref)
end
end
end