Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-10-17 12:24:51 +0300
committerKamil Trzcinski <ayufan@ayufan.eu>2016-10-17 12:24:51 +0300
commit40528a1326208a04f95f18d6dcc7547cdb8ae0c3 (patch)
tree9e3f7ac86edfe03b218e3cd12ed9cae8b2b539f2 /lib
parent0e1f39d8cee3a6d23fccb195f8257178df840805 (diff)
parent052de0600c6b137e6f9df08250b4cf5f38280295 (diff)
Merge remote-tracking branch 'origin/master' into 22191-delete-dynamic-envs-mr
Diffstat (limited to 'lib')
-rw-r--r--lib/api/access_requests.rb64
-rw-r--r--lib/api/api.rb13
-rw-r--r--lib/api/boards.rb132
-rw-r--r--lib/api/commits.rb36
-rw-r--r--lib/api/entities.rb18
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/license_templates.rb58
-rw-r--r--lib/api/members.rb96
-rw-r--r--lib/api/projects.rb57
-rw-r--r--lib/api/system_hooks.rb60
-rw-r--r--lib/api/templates.rb124
-rw-r--r--lib/api/todos.rb45
-rw-r--r--lib/api/users.rb20
-rw-r--r--lib/api/variables.rb89
-rw-r--r--lib/api/version.rb12
-rw-r--r--lib/banzai.rb4
-rw-r--r--lib/banzai/filter/emoji_filter.rb53
-rw-r--r--lib/banzai/filter/html_entity_filter.rb12
-rw-r--r--lib/banzai/note_renderer.rb5
-rw-r--r--lib/banzai/object_renderer.rb53
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb1
-rw-r--r--lib/banzai/renderer.rb32
-rw-r--r--lib/constraints/group_url_constrainer.rb7
-rw-r--r--lib/constraints/namespace_url_constrainer.rb13
-rw-r--r--lib/constraints/user_url_constrainer.rb7
-rw-r--r--lib/event_filter.rb23
-rw-r--r--lib/extracts_path.rb40
-rw-r--r--lib/gitlab/backend/shell.rb46
-rw-r--r--lib/gitlab/emoji.rb10
-rw-r--r--lib/gitlab/github_import/client.rb14
-rw-r--r--lib/gitlab/regex.rb4
-rw-r--r--lib/gitlab/workhorse.rb2
-rw-r--r--lib/tasks/cache.rake43
-rw-r--r--lib/tasks/ce_to_ee_merge_check.rake4
-rw-r--r--lib/tasks/gitlab/check.rake6
-rw-r--r--lib/tasks/gitlab/dev.rake107
-rw-r--r--lib/tasks/gitlab/shell.rake2
-rw-r--r--lib/tasks/gitlab/users.rake11
38 files changed, 924 insertions, 403 deletions
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index d3db7740830..87915b19480 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -5,15 +5,14 @@ module API
helpers ::API::Helpers::MembersHelpers
%w[group project].each do |source_type|
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ end
resource source_type.pluralize do
- # Get a list of group/project access requests viewable by the authenticated user.
- #
- # Parameters:
- # id (required) - The group/project ID
- #
- # Example Request:
- # GET /groups/:id/access_requests
- # GET /projects/:id/access_requests
+ desc "Gets a list of access requests for a #{source_type}." do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::AccessRequester
+ end
get ":id/access_requests" do
source = find_source(source_type, params[:id])
@@ -23,14 +22,10 @@ module API
present access_requesters.map(&:user), with: Entities::AccessRequester, source: source
end
- # Request access to the group/project
- #
- # Parameters:
- # id (required) - The group/project ID
- #
- # Example Request:
- # POST /groups/:id/access_requests
- # POST /projects/:id/access_requests
+ desc "Requests access for the authenticated user to a #{source_type}." do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::AccessRequester
+ end
post ":id/access_requests" do
source = find_source(source_type, params[:id])
access_requester = source.request_access(current_user)
@@ -42,37 +37,30 @@ module API
end
end
- # Approve a group/project access request
- #
- # Parameters:
- # id (required) - The group/project ID
- # user_id (required) - The user ID of the access requester
- # access_level (optional) - Access level
- #
- # Example Request:
- # PUT /groups/:id/access_requests/:user_id/approve
- # PUT /projects/:id/access_requests/:user_id/approve
+ desc 'Approves an access request for the given user.' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the access requester'
+ optional :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
+ end
put ':id/access_requests/:user_id/approve' do
- required_attributes! [:user_id]
source = find_source(source_type, params[:id])
- member = ::Members::ApproveAccessRequestService.new(source, current_user, params).execute
+ member = ::Members::ApproveAccessRequestService.new(source, current_user, declared(params)).execute
status :created
present member.user, with: Entities::Member, member: member
end
- # Deny a group/project access request
- #
- # Parameters:
- # id (required) - The group/project ID
- # user_id (required) - The user ID of the access requester
- #
- # Example Request:
- # DELETE /groups/:id/access_requests/:user_id
- # DELETE /projects/:id/access_requests/:user_id
+ desc 'Denies an access request for the given user.' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the access requester'
+ end
delete ":id/access_requests/:user_id" do
- required_attributes! [:user_id]
source = find_source(source_type, params[:id])
::Members::DestroyService.new(source, current_user, params).
diff --git a/lib/api/api.rb b/lib/api/api.rb
index cb47ec8f33f..67109ceeef9 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -31,11 +31,12 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::AwardEmoji
+ mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
mount ::API::Builds
- mount ::API::CommitStatuses
mount ::API::Commits
+ mount ::API::CommitStatuses
mount ::API::DeployKeys
mount ::API::Deployments
mount ::API::Environments
@@ -45,19 +46,18 @@ module API
mount ::API::Issues
mount ::API::Keys
mount ::API::Labels
- mount ::API::LicenseTemplates
mount ::API::Lint
mount ::API::Members
- mount ::API::MergeRequests
mount ::API::MergeRequestDiffs
+ mount ::API::MergeRequests
mount ::API::Milestones
mount ::API::Namespaces
mount ::API::Notes
mount ::API::NotificationSettings
mount ::API::Pipelines
mount ::API::ProjectHooks
- mount ::API::ProjectSnippets
mount ::API::Projects
+ mount ::API::ProjectSnippets
mount ::API::Repositories
mount ::API::Runners
mount ::API::Services
@@ -72,5 +72,10 @@ module API
mount ::API::Triggers
mount ::API::Users
mount ::API::Variables
+ mount ::API::Version
+
+ route :any, '*path' do
+ error!('404 Not Found', 404)
+ end
end
end
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
new file mode 100644
index 00000000000..b14dd4f6e83
--- /dev/null
+++ b/lib/api/boards.rb
@@ -0,0 +1,132 @@
+module API
+ # Boards API
+ class Boards < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get all project boards' do
+ detail 'This feature was introduced in 8.13'
+ success Entities::Board
+ end
+ get ':id/boards' do
+ authorize!(:read_board, user_project)
+ present user_project.boards, with: Entities::Board
+ end
+
+ params do
+ requires :board_id, type: Integer, desc: 'The ID of a board'
+ end
+ segment ':id/boards/:board_id' do
+ helpers do
+ def project_board
+ board = user_project.boards.first
+
+ if params[:board_id] == board.id
+ board
+ else
+ not_found!('Board')
+ end
+ end
+
+ def board_lists
+ project_board.lists.destroyable
+ end
+ end
+
+ desc 'Get the lists of a project board' do
+ detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13'
+ success Entities::List
+ end
+ get '/lists' do
+ authorize!(:read_board, user_project)
+ present board_lists, with: Entities::List
+ end
+
+ desc 'Get a list of a project board' do
+ detail 'This feature was introduced in 8.13'
+ success Entities::List
+ end
+ params do
+ requires :list_id, type: Integer, desc: 'The ID of a list'
+ end
+ get '/lists/:list_id' do
+ authorize!(:read_board, user_project)
+ present board_lists.find(params[:list_id]), with: Entities::List
+ end
+
+ desc 'Create a new board list' do
+ detail 'This feature was introduced in 8.13'
+ success Entities::List
+ end
+ params do
+ requires :label_id, type: Integer, desc: 'The ID of an existing label'
+ end
+ post '/lists' do
+ unless user_project.labels.exists?(params[:label_id])
+ render_api_error!({ error: "Label not found!" }, 400)
+ end
+
+ authorize!(:admin_list, user_project)
+
+ service = ::Boards::Lists::CreateService.new(user_project, current_user,
+ { label_id: params[:label_id] })
+
+ list = service.execute(project_board)
+
+ if list.valid?
+ present list, with: Entities::List
+ else
+ render_validation_error!(list)
+ end
+ end
+
+ desc 'Moves a board list to a new position' do
+ detail 'This feature was introduced in 8.13'
+ success Entities::List
+ end
+ params do
+ requires :list_id, type: Integer, desc: 'The ID of a list'
+ requires :position, type: Integer, desc: 'The position of the list'
+ end
+ put '/lists/:list_id' do
+ list = project_board.lists.movable.find(params[:list_id])
+
+ authorize!(:admin_list, user_project)
+
+ service = ::Boards::Lists::MoveService.new(user_project, current_user,
+ { position: params[:position] })
+
+ if service.execute(list)
+ present list, with: Entities::List
+ else
+ render_api_error!({ error: "List could not be moved!" }, 400)
+ end
+ end
+
+ desc 'Delete a board list' do
+ detail 'This feature was introduced in 8.13'
+ success Entities::List
+ end
+ params do
+ requires :list_id, type: Integer, desc: 'The ID of a board list'
+ end
+ delete "/lists/:list_id" do
+ authorize!(:admin_list, user_project)
+
+ list = board_lists.find(params[:list_id])
+
+ service = ::Boards::Lists::DestroyService.new(user_project, current_user)
+
+ if service.execute(list)
+ present list, with: Entities::List
+ else
+ render_api_error!({ error: 'List could not be deleted!' }, 400)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index b4eaf1813d4..14ddc8c9a62 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -29,6 +29,42 @@ module API
present commits, with: Entities::RepoCommit
end
+ desc 'Commit multiple file changes as one commit' do
+ detail 'This feature was introduced in GitLab 8.13'
+ end
+
+ params do
+ requires :id, type: Integer, desc: 'The project ID'
+ requires :branch_name, type: String, desc: 'The name of branch'
+ requires :commit_message, type: String, desc: 'Commit message'
+ requires :actions, type: Array, desc: 'Actions to perform in commit'
+ optional :author_email, type: String, desc: 'Author email for commit'
+ optional :author_name, type: String, desc: 'Author name for commit'
+ end
+
+ post ":id/repository/commits" do
+ authorize! :push_code, user_project
+
+ attrs = declared(params)
+ attrs[:source_branch] = attrs[:branch_name]
+ attrs[:target_branch] = attrs[:branch_name]
+ attrs[:actions].map! do |action|
+ action[:action] = action[:action].to_sym
+ action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
+ action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
+ action
+ end
+
+ result = ::Files::MultiService.new(user_project, current_user, attrs).execute
+
+ if result[:status] == :success
+ commit_detail = user_project.repository.commits(result[:result], limit: 1).first
+ present commit_detail, with: Entities::RepoCommitDetail
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
+
# Get a specific commit of a project
#
# Parameters:
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 04437322ec1..feaa0c213bf 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -432,8 +432,11 @@ module API
end
end
- class Label < Grape::Entity
+ class LabelBasic < Grape::Entity
expose :name, :color, :description
+ end
+
+ class Label < LabelBasic
expose :open_issues_count, :closed_issues_count, :open_merge_requests_count
expose :subscribed do |label, options|
@@ -441,6 +444,19 @@ module API
end
end
+ class List < Grape::Entity
+ expose :id
+ expose :label, using: Entities::LabelBasic
+ expose :position
+ end
+
+ class Board < Grape::Entity
+ expose :id
+ expose :lists, using: Entities::List do |board|
+ board.lists.destroyable
+ end
+ end
+
class Compare < Grape::Entity
expose :commit, using: Entities::RepoCommit do |compare, options|
Commit.decorate(compare.commits, nil).last
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 8b8c4eb4d46..67473f300c9 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -25,7 +25,7 @@ module API
# Until CSRF protection is added to the API, disallow this method for
# state-changing endpoints
def find_user_from_warden
- warden.try(:authenticate) if request.get? || request.head?
+ warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD'])
end
def find_user_by_private_token
@@ -433,7 +433,7 @@ module API
end
def secret_token
- File.read(Gitlab.config.gitlab_shell.secret_file).chomp
+ Gitlab::Shell.secret_token
end
def send_git_blob(repository, blob)
diff --git a/lib/api/license_templates.rb b/lib/api/license_templates.rb
deleted file mode 100644
index d0552299ed0..00000000000
--- a/lib/api/license_templates.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module API
- # License Templates API
- class LicenseTemplates < Grape::API
- PROJECT_TEMPLATE_REGEX =
- /[\<\{\[]
- (project|description|
- one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
- [\>\}\]]/xi.freeze
- YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
- FULLNAME_TEMPLATE_REGEX =
- /[\<\{\[]
- (fullname|name\sof\s(author|copyright\sowner))
- [\>\}\]]/xi.freeze
-
- # Get the list of the available license templates
- #
- # Parameters:
- # popular - Filter licenses to only the popular ones
- #
- # Example Request:
- # GET /licenses
- # GET /licenses?popular=1
- get 'licenses' do
- options = {
- featured: params[:popular].present? ? true : nil
- }
- present Licensee::License.all(options), with: Entities::RepoLicense
- end
-
- # Get text for specific license
- #
- # Parameters:
- # key (required) - The key of a license
- # project - Copyrighted project name
- # fullname - Full name of copyright holder
- #
- # Example Request:
- # GET /licenses/mit
- #
- get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do
- required_attributes! [:key]
-
- not_found!('License') unless Licensee::License.find(params[:key])
-
- # We create a fresh Licensee::License object since we'll modify its
- # content in place below.
- license = Licensee::License.new(params[:key])
-
- license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
- license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
-
- fullname = params[:fullname].presence || current_user.try(:name)
- license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
-
- present license, with: Entities::RepoLicense
- end
- end
-end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 34df55fe192..b80818f0eb6 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -5,16 +5,16 @@ module API
helpers ::API::Helpers::MembersHelpers
%w[group project].each do |source_type|
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ end
resource source_type.pluralize do
- # Get a list of group/project members viewable by the authenticated user.
- #
- # Parameters:
- # id (required) - The group/project ID
- # query - Query string
- #
- # Example Request:
- # GET /groups/:id/members
- # GET /projects/:id/members
+ desc 'Gets a list of group or project members viewable by the authenticated user.' do
+ success Entities::Member
+ end
+ params do
+ optional :query, type: String, desc: 'A query string to search for members'
+ end
get ":id/members" do
source = find_source(source_type, params[:id])
@@ -25,15 +25,12 @@ module API
present users, with: Entities::Member, source: source
end
- # Get a group/project member
- #
- # Parameters:
- # id (required) - The group/project ID
- # user_id (required) - The user ID of the member
- #
- # Example Request:
- # GET /groups/:id/members/:user_id
- # GET /projects/:id/members/:user_id
+ desc 'Gets a member of a group or project.' do
+ success Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the member'
+ end
get ":id/members/:user_id" do
source = find_source(source_type, params[:id])
@@ -43,26 +40,25 @@ module API
present member.user, with: Entities::Member, member: member
end
- # Add a new group/project member
- #
- # Parameters:
- # id (required) - The group/project ID
- # user_id (required) - The user ID of the new member
- # access_level (required) - A valid access level
- # expires_at (optional) - Date string in the format YEAR-MONTH-DAY
- #
- # Example Request:
- # POST /groups/:id/members
- # POST /projects/:id/members
+ desc 'Adds a member to a group or project.' do
+ success Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the new member'
+ requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
+ optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
+ end
post ":id/members" do
source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source)
- required_attributes! [:user_id, :access_level]
member = source.members.find_by(user_id: params[:user_id])
- # This is to ensure back-compatibility but 409 behavior should be used
- # for both project and group members in 9.0!
+ # We need this explicit check because `source.add_user` doesn't
+ # currently return the member created so it would return 201 even if
+ # the member already existed...
+ # The `source_type == 'group'` check is to ensure back-compatibility
+ # but 409 behavior should be used for both project and group members in 9.0!
conflict!('Member already exists') if source_type == 'group' && member
unless member
@@ -79,21 +75,17 @@ module API
end
end
- # Update a group/project member
- #
- # Parameters:
- # id (required) - The group/project ID
- # user_id (required) - The user ID of the member
- # access_level (required) - A valid access level
- # expires_at (optional) - Date string in the format YEAR-MONTH-DAY
- #
- # Example Request:
- # PUT /groups/:id/members/:user_id
- # PUT /projects/:id/members/:user_id
+ desc 'Updates a member of a group or project.' do
+ success Entities::Member
+ end
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the new member'
+ requires :access_level, type: Integer, desc: 'A valid access level'
+ optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
+ end
put ":id/members/:user_id" do
source = find_source(source_type, params[:id])
authorize_admin_source!(source_type, source)
- required_attributes! [:user_id, :access_level]
member = source.members.find_by!(user_id: params[:user_id])
attrs = attributes_for_keys [:access_level, :expires_at]
@@ -108,18 +100,12 @@ module API
end
end
- # Remove a group/project member
- #
- # Parameters:
- # id (required) - The group/project ID
- # user_id (required) - The user ID of the member
- #
- # Example Request:
- # DELETE /groups/:id/members/:user_id
- # DELETE /projects/:id/members/:user_id
+ desc 'Removes a user from a group or project.'
+ params do
+ requires :user_id, type: Integer, desc: 'The user ID of the member'
+ end
delete ":id/members/:user_id" do
source = find_source(source_type, params[:id])
- required_attributes! [:user_id]
# This is to ensure back-compatibility but find_by! should be used
# in that casse in 9.0!
@@ -134,7 +120,7 @@ module API
if member.nil?
{ message: "Access revoked", id: params[:user_id].to_i }
else
- ::Members::DestroyService.new(source, current_user, params).execute
+ ::Members::DestroyService.new(source, current_user, declared(params)).execute
present member.user, with: Entities::Member, member: member
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 680055c95eb..da16e24d7ea 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -22,14 +22,25 @@ module API
# Example Request:
# GET /projects
get do
- @projects = current_user.authorized_projects
- @projects = filter_projects(@projects)
- @projects = paginate @projects
- if params[:simple]
- present @projects, with: Entities::BasicProjectDetails, user: current_user
- else
- present @projects, with: Entities::ProjectWithAccess, user: current_user
- end
+ projects = current_user.authorized_projects
+ projects = filter_projects(projects)
+ projects = paginate projects
+ entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
+
+ present projects, with: entity, user: current_user
+ end
+
+ # Get a list of visible projects for authenticated user
+ #
+ # Example Request:
+ # GET /projects/visible
+ get '/visible' do
+ projects = ProjectsFinder.new.execute(current_user)
+ projects = filter_projects(projects)
+ projects = paginate projects
+ entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess
+
+ present projects, with: entity, user: current_user
end
# Get an owned projects list for authenticated user
@@ -37,10 +48,10 @@ module API
# Example Request:
# GET /projects/owned
get '/owned' do
- @projects = current_user.owned_projects
- @projects = filter_projects(@projects)
- @projects = paginate @projects
- present @projects, with: Entities::ProjectWithAccess, user: current_user
+ projects = current_user.owned_projects
+ projects = filter_projects(projects)
+ projects = paginate projects
+ present projects, with: Entities::ProjectWithAccess, user: current_user
end
# Gets starred project for the authenticated user
@@ -48,10 +59,10 @@ module API
# Example Request:
# GET /projects/starred
get '/starred' do
- @projects = current_user.viewable_starred_projects
- @projects = filter_projects(@projects)
- @projects = paginate @projects
- present @projects, with: Entities::Project, user: current_user
+ projects = current_user.viewable_starred_projects
+ projects = filter_projects(projects)
+ projects = paginate projects
+ present projects, with: Entities::Project, user: current_user
end
# Get all projects for admin user
@@ -60,10 +71,10 @@ module API
# GET /projects/all
get '/all' do
authenticated_as_admin!
- @projects = Project.all
- @projects = filter_projects(@projects)
- @projects = paginate @projects
- present @projects, with: Entities::ProjectWithAccess, user: current_user
+ projects = Project.all
+ projects = filter_projects(projects)
+ projects = paginate projects
+ present projects, with: Entities::ProjectWithAccess, user: current_user
end
# Get a single project
@@ -405,6 +416,12 @@ module API
required_attributes! [:group_id, :group_access]
attrs = attributes_for_keys [:group_id, :group_access, :expires_at]
+ group = Group.find_by_id(attrs[:group_id])
+
+ unless group && can?(current_user, :read_group, group)
+ not_found!('Group')
+ end
+
unless user_project.allowed_to_share_with_group?
return render_api_error!("The project sharing with group is disabled", 400)
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 22b8f90dc5c..2e76b91051f 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -7,38 +7,36 @@ module API
end
resource :hooks do
- # Get the list of system hooks
- #
- # Example Request:
- # GET /hooks
+ desc 'Get the list of system hooks' do
+ success Entities::Hook
+ end
get do
- @hooks = SystemHook.all
- present @hooks, with: Entities::Hook
+ hooks = SystemHook.all
+ present hooks, with: Entities::Hook
end
- # Create new system hook
- #
- # Parameters:
- # url (required) - url for system hook
- # Example Request
- # POST /hooks
+ desc 'Create a new system hook' do
+ success Entities::Hook
+ end
+ params do
+ requires :url, type: String, desc: 'The URL for the system hook'
+ end
post do
- attrs = attributes_for_keys [:url]
- required_attributes! [:url]
- @hook = SystemHook.new attrs
- if @hook.save
- present @hook, with: Entities::Hook
+ hook = SystemHook.new declared(params).to_h
+
+ if hook.save
+ present hook, with: Entities::Hook
else
not_found!
end
end
- # Test a hook
- #
- # Example Request
- # GET /hooks/:id
+ desc 'Test a hook'
+ params do
+ requires :id, type: Integer, desc: 'The ID of the system hook'
+ end
get ":id" do
- @hook = SystemHook.find(params[:id])
+ hook = SystemHook.find(params[:id])
data = {
event_name: "project_create",
name: "Ruby",
@@ -47,20 +45,20 @@ module API
owner_name: "Someone",
owner_email: "example@gitlabhq.com"
}
- @hook.execute(data, 'system_hooks')
+ hook.execute(data, 'system_hooks')
data
end
- # Delete a hook. This is an idempotent function.
- #
- # Parameters:
- # id (required) - ID of the hook
- # Example Request:
- # DELETE /hooks/:id
+ desc 'Delete a hook' do
+ success Entities::Hook
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the system hook'
+ end
delete ":id" do
begin
- @hook = SystemHook.find(params[:id])
- @hook.destroy
+ hook = SystemHook.find(params[:id])
+ present hook.destroy, with: Entities::Hook
rescue
# SystemHook raises an Error if no hook with id found
end
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index b9e718147e1..8a53d9c0095 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,39 +1,115 @@
module API
class Templates < Grape::API
GLOBAL_TEMPLATE_TYPES = {
- gitignores: Gitlab::Template::GitignoreTemplate,
- gitlab_ci_ymls: Gitlab::Template::GitlabCiYmlTemplate
+ gitignores: {
+ klass: Gitlab::Template::GitignoreTemplate,
+ gitlab_version: 8.8
+ },
+ gitlab_ci_ymls: {
+ klass: Gitlab::Template::GitlabCiYmlTemplate,
+ gitlab_version: 8.9
+ }
}.freeze
+ PROJECT_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]/xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ /[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]/xi.freeze
+ DEPRECATION_MESSAGE = ' This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze
helpers do
+ def parsed_license_template
+ # We create a fresh Licensee::License object since we'll modify its
+ # content in place below.
+ template = Licensee::License.new(params[:name])
+
+ template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s)
+ template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present?
+
+ fullname = params[:fullname].presence || current_user.try(:name)
+ template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname
+ template
+ end
+
def render_response(template_type, template)
not_found!(template_type.to_s.singularize) unless template
present template, with: Entities::Template
end
end
- GLOBAL_TEMPLATE_TYPES.each do |template_type, klass|
- # Get the list of the available template
- #
- # Example Request:
- # GET /gitignores
- # GET /gitlab_ci_ymls
- get template_type.to_s do
- present klass.all, with: Entities::TemplatesList
- end
-
- # Get the text for a specific template present in local filesystem
- #
- # Parameters:
- # name (required) - The name of a template
- #
- # Example Request:
- # GET /gitignores/Elixir
- # GET /gitlab_ci_ymls/Ruby
- get "#{template_type}/:name" do
- required_attributes! [:name]
- new_template = klass.find(params[:name])
- render_response(template_type, new_template)
+ { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status|
+ desc 'Get the list of the available license template' do
+ detailed_desc = 'This feature was introduced in GitLab 8.7.'
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success Entities::RepoLicense
+ end
+ params do
+ optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses'
+ end
+ get route do
+ options = {
+ featured: declared(params).popular.present? ? true : nil
+ }
+ present Licensee::License.all(options), with: Entities::RepoLicense
+ end
+ end
+
+ { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status|
+ desc 'Get the text for a specific license' do
+ detailed_desc = 'This feature was introduced in GitLab 8.7.'
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success Entities::RepoLicense
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get route, requirements: { name: /[\w\.-]+/ } do
+ not_found!('License') unless Licensee::License.find(declared(params).name)
+
+ template = parsed_license_template
+
+ present template, with: Entities::RepoLicense
+ end
+ end
+
+ GLOBAL_TEMPLATE_TYPES.each do |template_type, properties|
+ klass = properties[:klass]
+ gitlab_version = properties[:gitlab_version]
+
+ { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status|
+ desc 'Get the list of the available template' do
+ detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success Entities::TemplatesList
+ end
+ get route do
+ present klass.all, with: Entities::TemplatesList
+ end
+ end
+
+ { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status|
+ desc 'Get the text for a specific template present in local filesystem' do
+ detailed_desc = "This feature was introduced in GitLab #{gitlab_version}."
+ detailed_desc << DEPRECATION_MESSAGE unless status == :ok
+ detail detailed_desc
+ success Entities::Template
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the template'
+ end
+ get route do
+ new_template = klass.find(declared(params).name)
+
+ render_response(template_type, new_template)
+ end
end
end
end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 19df13d8aac..832b04a3bb1 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -8,18 +8,19 @@ module API
'issues' => ->(id) { find_project_issue(id) }
}
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
resource :projects do
ISSUABLE_TYPES.each do |type, finder|
type_id_str = "#{type.singularize}_id".to_sym
- # Create a todo on an issuable
- #
- # Parameters:
- # id (required) - The ID of a project
- # issuable_id (required) - The ID of an issuable
- # Example Request:
- # POST /projects/:id/issues/:issuable_id/todo
- # POST /projects/:id/merge_requests/:issuable_id/todo
+ desc 'Create a todo on an issuable' do
+ success Entities::Todo
+ end
+ params do
+ requires type_id_str, type: Integer, desc: 'The ID of an issuable'
+ end
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
todo = TodoService.new.mark_todo(issuable, current_user).first
@@ -40,25 +41,21 @@ module API
end
end
- # Get a todo list
- #
- # Example Request:
- # GET /todos
- #
+ desc 'Get a todo list' do
+ success Entities::Todo
+ end
get do
todos = find_todos
present paginate(todos), with: Entities::Todo, current_user: current_user
end
- # Mark a todo as done
- #
- # Parameters:
- # id: (required) - The ID of the todo being marked as done
- #
- # Example Request:
- # DELETE /todos/:id
- #
+ desc 'Mark a todo as done' do
+ success Entities::Todo
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of the todo being marked as done'
+ end
delete ':id' do
todo = current_user.todos.find(params[:id])
TodoService.new.mark_todos_as_done([todo], current_user)
@@ -66,11 +63,7 @@ module API
present todo.reload, with: Entities::Todo, current_user: current_user
end
- # Mark all todos as done
- #
- # Example Request:
- # DELETE /todos
- #
+ desc 'Mark all todos as done'
delete do
todos = find_todos
TodoService.new.mark_todos_as_done(todos, current_user)
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 18c4cad09ae..e868f628404 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -321,6 +321,26 @@ module API
user.activate
end
end
+
+ desc 'Get contribution events of a specified user' do
+ detail 'This feature was introduced in GitLab 8.13.'
+ success Entities::Event
+ end
+ params do
+ requires :id, type: String, desc: 'The user ID'
+ end
+ get ':id/events' do
+ user = User.find_by(id: declared(params).id)
+ not_found!('User') unless user
+
+ events = user.recent_events.
+ merge(ProjectsFinder.new.execute(current_user)).
+ references(:project).
+ with_associations.
+ page(params[:page])
+
+ present paginate(events), with: Entities::Event
+ end
end
resource :user do
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index f6495071a11..b9fb3c21dbb 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -4,27 +4,29 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+
resource :projects do
- # Get project variables
- #
- # Parameters:
- # id (required) - The ID of a project
- # page (optional) - The page number for pagination
- # per_page (optional) - The value of items per page to show
- # Example Request:
- # GET /projects/:id/variables
+ desc 'Get project variables' do
+ success Entities::Variable
+ end
+ params do
+ optional :page, type: Integer, desc: 'The page number for pagination'
+ optional :per_page, type: Integer, desc: 'The value of items per page to show'
+ end
get ':id/variables' do
variables = user_project.variables
present paginate(variables), with: Entities::Variable
end
- # Get specific variable of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # key (required) - The `key` of variable
- # Example Request:
- # GET /projects/:id/variables/:key
+ desc 'Get a specific variable from a project' do
+ success Entities::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ end
get ':id/variables/:key' do
key = params[:key]
variable = user_project.variables.find_by(key: key.to_s)
@@ -34,18 +36,15 @@ module API
present variable, with: Entities::Variable
end
- # Create a new variable in project
- #
- # Parameters:
- # id (required) - The ID of a project
- # key (required) - The key of variable
- # value (required) - The value of variable
- # Example Request:
- # POST /projects/:id/variables
+ desc 'Create a new variable in a project' do
+ success Entities::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ requires :value, type: String, desc: 'The value of the variable'
+ end
post ':id/variables' do
- required_attributes! [:key, :value]
-
- variable = user_project.variables.create(key: params[:key], value: params[:value])
+ variable = user_project.variables.create(declared(params, include_parent_namespaces: false).to_h)
if variable.valid?
present variable, with: Entities::Variable
@@ -54,41 +53,37 @@ module API
end
end
- # Update existing variable of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # key (optional) - The `key` of variable
- # value (optional) - New value for `value` field of variable
- # Example Request:
- # PUT /projects/:id/variables/:key
+ desc 'Update an existing variable from a project' do
+ success Entities::Variable
+ end
+ params do
+ optional :key, type: String, desc: 'The key of the variable'
+ optional :value, type: String, desc: 'The value of the variable'
+ end
put ':id/variables/:key' do
- variable = user_project.variables.find_by(key: params[:key].to_s)
+ variable = user_project.variables.find_by(key: params[:key])
return not_found!('Variable') unless variable
- attrs = attributes_for_keys [:value]
- if variable.update(attrs)
+ if variable.update(value: params[:value])
present variable, with: Entities::Variable
else
render_validation_error!(variable)
end
end
- # Delete existing variable of a project
- #
- # Parameters:
- # id (required) - The ID of a project
- # key (required) - The ID of a variable
- # Example Request:
- # DELETE /projects/:id/variables/:key
+ desc 'Delete an existing variable from a project' do
+ success Entities::Variable
+ end
+ params do
+ requires :key, type: String, desc: 'The key of the variable'
+ end
delete ':id/variables/:key' do
- variable = user_project.variables.find_by(key: params[:key].to_s)
+ variable = user_project.variables.find_by(key: params[:key])
return not_found!('Variable') unless variable
- variable.destroy
- present variable, with: Entities::Variable
+ present variable.destroy, with: Entities::Variable
end
end
end
diff --git a/lib/api/version.rb b/lib/api/version.rb
new file mode 100644
index 00000000000..9ba576bd828
--- /dev/null
+++ b/lib/api/version.rb
@@ -0,0 +1,12 @@
+module API
+ class Version < Grape::API
+ before { authenticate! }
+
+ desc 'Get the version information of the GitLab instance.' do
+ detail 'This feature was introduced in GitLab 8.13.'
+ end
+ get '/version' do
+ { version: Gitlab::VERSION, revision: Gitlab::REVISION }
+ end
+ end
+end
diff --git a/lib/banzai.rb b/lib/banzai.rb
index 9ebe379f454..35ca234c1ba 100644
--- a/lib/banzai.rb
+++ b/lib/banzai.rb
@@ -3,6 +3,10 @@ module Banzai
Renderer.render(text, context)
end
+ def self.render_field(object, field)
+ Renderer.render_field(object, field)
+ end
+
def self.cache_collection_render(texts_and_contexts)
Renderer.cache_collection_render(texts_and_contexts)
end
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index 2492b5213ac..a8c1ca0c60a 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -1,6 +1,6 @@
module Banzai
module Filter
- # HTML filter that replaces :emoji: with images.
+ # HTML filter that replaces :emoji: and unicode with images.
#
# Based on HTML::Pipeline::EmojiFilter
#
@@ -13,16 +13,17 @@ module Banzai
def call
search_text_nodes(doc).each do |node|
content = node.to_html
- next unless content.include?(':')
next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
- html = emoji_image_filter(content)
+ next unless content.include?(':') || node.text.match(emoji_unicode_pattern)
+
+ html = emoji_name_image_filter(content)
+ html = emoji_unicode_image_filter(html)
next if html == content
node.replace(html)
end
-
doc
end
@@ -31,18 +32,38 @@ module Banzai
# text - String text to replace :emoji: in.
#
# Returns a String with :emoji: replaced with images.
- def emoji_image_filter(text)
+ def emoji_name_image_filter(text)
text.gsub(emoji_pattern) do |match|
name = $1
- "<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{emoji_url(name)}' height='20' width='20' align='absmiddle' />"
+ emoji_image_tag(name, emoji_url(name))
end
end
+ # Replace unicode emoji with corresponding images if they exist.
+ #
+ # text - String text to replace unicode emoji in.
+ #
+ # Returns a String with unicode emoji replaced with images.
+ def emoji_unicode_image_filter(text)
+ text.gsub(emoji_unicode_pattern) do |moji|
+ emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji))
+ end
+ end
+
+ def emoji_image_tag(emoji_name, emoji_url)
+ "<img class='emoji' title=':#{emoji_name}:' alt=':#{emoji_name}:' src='#{emoji_url}' height='20' width='20' align='absmiddle' />"
+ end
+
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
end
+ # Build a regexp that matches all valid unicode emojis names.
+ def self.emoji_unicode_pattern
+ @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/
+ end
+
private
def emoji_url(name)
@@ -60,6 +81,18 @@ module Banzai
end
end
+ def emoji_unicode_url(moji)
+ emoji_unicode_path = emoji_unicode_filename(moji)
+
+ if context[:asset_host]
+ url_to_image(emoji_unicode_path)
+ elsif context[:asset_root]
+ File.join(context[:asset_root], url_to_image(emoji_unicode_path))
+ else
+ url_to_image(emoji_unicode_path)
+ end
+ end
+
def url_to_image(image)
ActionController::Base.helpers.url_to_image(image)
end
@@ -71,6 +104,14 @@ module Banzai
def emoji_filename(name)
"#{Gitlab::Emoji.emoji_filename(name)}.png"
end
+
+ def emoji_unicode_pattern
+ self.class.emoji_unicode_pattern
+ end
+
+ def emoji_unicode_filename(name)
+ "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png"
+ end
end
end
end
diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb
new file mode 100644
index 00000000000..e008fd428b0
--- /dev/null
+++ b/lib/banzai/filter/html_entity_filter.rb
@@ -0,0 +1,12 @@
+require 'erb'
+
+module Banzai
+ module Filter
+ # Text filter that escapes these HTML entities: & " < >
+ class HtmlEntityFilter < HTML::Pipeline::TextFilter
+ def call
+ ERB::Util.html_escape(text)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/note_renderer.rb b/lib/banzai/note_renderer.rb
index bab6a9934d1..2b7c10f1a0e 100644
--- a/lib/banzai/note_renderer.rb
+++ b/lib/banzai/note_renderer.rb
@@ -3,7 +3,7 @@ module Banzai
# Renders a collection of Note instances.
#
# notes - The notes to render.
- # project - The project to use for rendering/redacting.
+ # project - The project to use for redacting.
# user - The user viewing the notes.
# path - The request path.
# wiki - The project's wiki.
@@ -13,8 +13,7 @@ module Banzai
user,
requested_path: path,
project_wiki: wiki,
- ref: git_ref,
- pipeline: :note)
+ ref: git_ref)
renderer.render(notes, :note)
end
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 9aef807c152..9f8eb0931b8 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -1,28 +1,32 @@
module Banzai
- # Class for rendering multiple objects (e.g. Note instances) in a single pass.
+ # Class for rendering multiple objects (e.g. Note instances) in a single pass,
+ # using +render_field+ to benefit from caching in the database. Rendering and
+ # redaction are both performed.
#
- # Rendered Markdown is stored in an attribute in every object based on the
- # name of the attribute containing the Markdown. For example, when the
- # attribute `note` is rendered the HTML is stored in `note_html`.
+ # The unredacted HTML is generated according to the usual +render_field+
+ # policy, so specify the pipeline and any other context options on the model.
+ #
+ # The *redacted* (i.e., suitable for use) HTML is placed in an attribute
+ # named "redacted_<foo>", where <foo> is the name of the cache field for the
+ # chosen attribute.
+ #
+ # As an example, rendering the attribute `note` would place the unredacted
+ # HTML into `note_html` and the redacted HTML into `redacted_note_html`.
class ObjectRenderer
attr_reader :project, :user
- # Make sure to set the appropriate pipeline in the `raw_context` attribute
- # (e.g. `:note` for Note instances).
- #
- # project - A Project to use for rendering and redacting Markdown.
+ # project - A Project to use for redacting Markdown.
# user - The user viewing the Markdown/HTML documents, if any.
- # context - A Hash containing extra attributes to use in the rendering
- # pipeline.
- def initialize(project, user = nil, raw_context = {})
+ # context - A Hash containing extra attributes to use during redaction
+ def initialize(project, user = nil, redaction_context = {})
@project = project
@user = user
- @raw_context = raw_context
+ @redaction_context = redaction_context
end
# Renders and redacts an Array of objects.
#
- # objects - The objects to render
+ # objects - The objects to render.
# attribute - The attribute containing the raw Markdown to render.
#
# Returns the same input objects.
@@ -32,7 +36,7 @@ module Banzai
objects.each_with_index do |object, index|
redacted_data = redacted[index]
- object.__send__("#{attribute}_html=", redacted_data[:document].to_html.html_safe)
+ object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html.html_safe)
object.user_visible_reference_count = redacted_data[:visible_reference_count]
end
end
@@ -53,12 +57,8 @@ module Banzai
# Returns a Banzai context for the given object and attribute.
def context_for(object, attribute)
- context = base_context.merge(cache_key: [object, attribute])
-
- if object.respond_to?(:author)
- context[:author] = object.author
- end
-
+ context = base_context.dup
+ context = context.merge(object.banzai_render_context(attribute))
context
end
@@ -66,21 +66,16 @@ module Banzai
#
# Returns an Array of `Nokogiri::HTML::Document`.
def render_attributes(objects, attribute)
- strings_and_contexts = objects.map do |object|
+ objects.map do |object|
+ string = Banzai.render_field(object, attribute)
context = context_for(object, attribute)
- string = object.__send__(attribute)
-
- { text: string, context: context }
- end
-
- Banzai.cache_collection_render(strings_and_contexts).each_with_index.map do |html, index|
- Banzai::Pipeline[:relative_link].to_document(html, strings_and_contexts[index][:context])
+ Banzai::Pipeline[:relative_link].to_document(string, context)
end
end
def base_context
- @base_context ||= @raw_context.merge(current_user: user, project: project)
+ @base_context ||= @redaction_context.merge(current_user: user, project: project)
end
end
end
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index ba2555df98d..1929099931b 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -3,6 +3,7 @@ module Banzai
class SingleLinePipeline < GfmPipeline
def self.filters
@filters ||= FilterArray[
+ Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
Filter::EmojiFilter,
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index a4ae27eefd8..ce048a36fa0 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -1,6 +1,6 @@
module Banzai
module Renderer
- extend self
+ module_function
# Convert a Markdown String into an HTML-safe String of HTML
#
@@ -31,6 +31,34 @@ module Banzai
end
end
+ # Convert a Markdown-containing field on an object into an HTML-safe String
+ # of HTML. This method is analogous to calling render(object.field), but it
+ # can cache the rendered HTML in the object, rather than Redis.
+ #
+ # The context to use is learned from the passed-in object by calling
+ # #banzai_render_context(field), and cannot be changed. Use #render, passing
+ # it the field text, if a custom rendering is needed. The generated context
+ # is returned along with the HTML.
+ def render_field(object, field)
+ html_field = object.markdown_cache_field_for(field)
+
+ html = object.__send__(html_field)
+ return html if html.present?
+
+ html = cacheless_render_field(object, field)
+ object.update_column(html_field, html) unless object.new_record? || object.destroyed?
+
+ html
+ end
+
+ # Same as +render_field+, but without consulting or updating the cache field
+ def cacheless_render_field(object, field)
+ text = object.__send__(field)
+ context = object.banzai_render_context(field)
+
+ cacheless_render(text, context)
+ end
+
# Perform multiple render from an Array of Markdown String into an
# Array of HTML-safe String of HTML.
#
@@ -113,8 +141,6 @@ module Banzai
end.html_safe
end
- private
-
def cacheless_render(text, context = {})
Gitlab::Metrics.measure(:banzai_cacheless_render) do
result = render_result(text, context)
diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb
new file mode 100644
index 00000000000..ca39b1961ae
--- /dev/null
+++ b/lib/constraints/group_url_constrainer.rb
@@ -0,0 +1,7 @@
+require 'constraints/namespace_url_constrainer'
+
+class GroupUrlConstrainer < NamespaceUrlConstrainer
+ def find_resource(id)
+ Group.find_by_path(id)
+ end
+end
diff --git a/lib/constraints/namespace_url_constrainer.rb b/lib/constraints/namespace_url_constrainer.rb
new file mode 100644
index 00000000000..23920193743
--- /dev/null
+++ b/lib/constraints/namespace_url_constrainer.rb
@@ -0,0 +1,13 @@
+class NamespaceUrlConstrainer
+ def matches?(request)
+ id = request.path.sub(/\A\/+/, '').split('/').first.sub(/.atom\z/, '')
+
+ if id =~ Gitlab::Regex.namespace_regex
+ find_resource(id)
+ end
+ end
+
+ def find_resource(id)
+ Namespace.find_by_path(id)
+ end
+end
diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb
new file mode 100644
index 00000000000..504a0f5d93e
--- /dev/null
+++ b/lib/constraints/user_url_constrainer.rb
@@ -0,0 +1,7 @@
+require 'constraints/namespace_url_constrainer'
+
+class UserUrlConstrainer < NamespaceUrlConstrainer
+ def find_resource(id)
+ User.find_by('lower(username) = ?', id.downcase)
+ end
+end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 668d2fa41b3..96e70e37e8f 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -2,8 +2,8 @@ class EventFilter
attr_accessor :params
class << self
- def default_filter
- %w{ push issues merge_requests team}
+ def all
+ 'all'
end
def push
@@ -35,18 +35,21 @@ class EventFilter
return events unless params.present?
filter = params.dup
-
actions = []
- actions << Event::PUSHED if filter.include? 'push'
- actions << Event::MERGED if filter.include? 'merged'
- if filter.include? 'team'
- actions << Event::JOINED
- actions << Event::LEFT
+ case filter
+ when EventFilter.push
+ actions = [Event::PUSHED]
+ when EventFilter.merged
+ actions = [Event::MERGED]
+ when EventFilter.comments
+ actions = [Event::COMMENTED]
+ when EventFilter.team
+ actions = [Event::JOINED, Event::LEFT]
+ when EventFilter.all
+ actions = [Event::PUSHED, Event::MERGED, Event::COMMENTED, Event::JOINED, Event::LEFT]
end
- actions << Event::COMMENTED if filter.include? 'comments'
-
events.where(action: actions)
end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index a4558d157c0..9b74364849e 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -52,8 +52,7 @@ module ExtractsPath
# Append a trailing slash if we only get a ref and no file path
id += '/' unless id.ends_with?('/')
- valid_refs = @project.repository.ref_names
- valid_refs.select! { |v| id.start_with?("#{v}/") }
+ valid_refs = ref_names.select { |v| id.start_with?("#{v}/") }
if valid_refs.length == 0
# No exact ref match, so just try our best
@@ -74,6 +73,19 @@ module ExtractsPath
pair
end
+ # If we have an ID of 'foo.atom', and the controller provides Atom and HTML
+ # formats, then we have to check if the request was for the Atom version of
+ # the ID without the '.atom' suffix, or the HTML version of the ID including
+ # the suffix. We only check this if the version including the suffix doesn't
+ # match, so it is possible to create a branch which has an unroutable Atom
+ # feed.
+ def extract_ref_without_atom(id)
+ id_without_atom = id.sub(/\.atom$/, '')
+ valid_refs = ref_names.select { |v| "#{id_without_atom}/".start_with?("#{v}/") }
+
+ valid_refs.max_by(&:length)
+ end
+
# Assigns common instance variables for views working with Git tree-ish objects
#
# Assignments are:
@@ -86,6 +98,10 @@ module ExtractsPath
# If the :id parameter appears to be requesting a specific response format,
# that will be handled as well.
#
+ # If there is no path and the ref doesn't exist in the repo, try to resolve
+ # the ref without an '.atom' suffix. If _that_ ref is found, set the request's
+ # format to Atom manually.
+ #
# Automatically renders `not_found!` if a valid tree path could not be
# resolved (e.g., when a user inserts an invalid path or ref).
def assign_ref_vars
@@ -97,10 +113,18 @@ module ExtractsPath
@id = get_id
@ref, @path = extract_ref(@id)
@repo = @project.repository
- if @options[:extended_sha1].blank?
- @commit = @repo.commit(@ref)
- else
+
+ if @options[:extended_sha1].present?
@commit = @repo.commit(@options[:extended_sha1])
+ else
+ @commit = @repo.commit(@ref)
+
+ if @path.empty? && !@commit && @id.ends_with?('.atom')
+ @id = @ref = extract_ref_without_atom(@id)
+ @commit = @repo.commit(@ref)
+
+ request.format = :atom if @commit
+ end
end
raise InvalidPathError unless @commit
@@ -125,4 +149,10 @@ module ExtractsPath
id += "/" + params[:path] unless params[:path].blank?
id
end
+
+ def ref_names
+ return [] unless @project
+
+ @ref_names ||= @project.repository.ref_names
+ end
end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 79eac66b364..9cec71a3222 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -17,6 +17,18 @@ module Gitlab
end
class << self
+ def secret_token
+ @secret_token ||= begin
+ File.read(Gitlab.config.gitlab_shell.secret_file).chomp
+ end
+ end
+
+ def ensure_secret_token!
+ return if File.exist?(File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret'))
+
+ generate_and_link_secret_token
+ end
+
def version_required
@version_required ||= File.read(Rails.root.
join('GITLAB_SHELL_VERSION')).strip
@@ -25,6 +37,25 @@ module Gitlab
def strip_key(key)
key.split(/ /)[0, 2].join(' ')
end
+
+ private
+
+ # Create (if necessary) and link the secret token file
+ def generate_and_link_secret_token
+ secret_file = Gitlab.config.gitlab_shell.secret_file
+ shell_path = Gitlab.config.gitlab_shell.path
+
+ unless File.size?(secret_file)
+ # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
+ @secret_token = SecureRandom.hex(16)
+ File.write(secret_file, @secret_token)
+ end
+
+ link_path = File.join(shell_path, '.gitlab_shell_secret')
+ if File.exist?(shell_path) && !File.exist?(link_path)
+ FileUtils.symlink(secret_file, link_path)
+ end
+ end
end
# Init new repository
@@ -201,21 +232,6 @@ module Gitlab
File.exist?(full_path(storage, dir_name))
end
- # Create (if necessary) and link the secret token file
- def generate_and_link_secret_token
- secret_file = Gitlab.config.gitlab_shell.secret_file
- unless File.size?(secret_file)
- # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
- token = SecureRandom.hex(16)
- File.write(secret_file, token)
- end
-
- link_path = File.join(gitlab_shell_path, '.gitlab_shell_secret')
- if File.exist?(gitlab_shell_path) && !File.exist?(link_path)
- FileUtils.symlink(secret_file, link_path)
- end
- end
-
protected
def gitlab_shell_path
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index b63213ae208..bbbca8acc40 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -10,12 +10,20 @@ module Gitlab
Gemojione.index.instance_variable_get(:@emoji_by_moji)
end
+ def emojis_unicodes
+ emojis_by_moji.keys
+ end
+
def emojis_names
- emojis.keys.sort
+ emojis.keys
end
def emoji_filename(name)
emojis[name]["unicode"]
end
+
+ def emoji_unicode_filename(moji)
+ emojis_by_moji[moji]["unicode"]
+ end
end
end
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index e33ac61f5ae..7f424b74efb 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -102,9 +102,19 @@ module Gitlab
def request(method, *args, &block)
sleep rate_limit_sleep_time if rate_limit_exceed?
- data = api.send(method, *args, &block)
- yield data
+ data = api.send(method, *args)
+ return data unless data.is_a?(Array)
+ if block_given?
+ yield data
+ each_response_page(&block)
+ else
+ each_response_page { |page| data.concat(page) }
+ data
+ end
+ end
+
+ def each_response_page
last_response = api.last_response
while last_response.rels[:next]
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 776bbcbb5d0..0d30e1bb92e 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -2,7 +2,7 @@ module Gitlab
module Regex
extend self
- NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze
+ NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
@@ -10,7 +10,7 @@ module Gitlab
def namespace_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
- "Cannot start with '-' or end in '.'." \
+ "Cannot start with '-' or end in '.', '.git' or '.atom'." \
end
def namespace_name_regex
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 5d33f98e89e..594439a5d4b 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -111,7 +111,7 @@ module Gitlab
def write_secret
bytes = SecureRandom.random_bytes(SECRET_LENGTH)
File.open(secret_path, 'w:BINARY', 0600) do |f|
- f.chmod(0600)
+ f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op.
f.write(Base64.strict_encode64(bytes))
end
end
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 2214f855200..a95a3455a4a 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -1,22 +1,33 @@
namespace :cache do
- CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000
- REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
+ namespace :clear do
+ REDIS_CLEAR_BATCH_SIZE = 1000 # There seems to be no speedup when pushing beyond 1,000
+ REDIS_SCAN_START_STOP = '0' # Magic value, see http://redis.io/commands/scan
- desc "GitLab | Clear redis cache"
- task :clear => :environment do
- Gitlab::Redis.with do |redis|
- cursor = REDIS_SCAN_START_STOP
- loop do
- cursor, keys = redis.scan(
- cursor,
- match: "#{Gitlab::Redis::CACHE_NAMESPACE}*",
- count: CLEAR_BATCH_SIZE
- )
-
- redis.del(*keys) if keys.any?
-
- break if cursor == REDIS_SCAN_START_STOP
+ desc "GitLab | Clear redis cache"
+ task redis: :environment do
+ Gitlab::Redis.with do |redis|
+ cursor = REDIS_SCAN_START_STOP
+ loop do
+ cursor, keys = redis.scan(
+ cursor,
+ match: "#{Gitlab::Redis::CACHE_NAMESPACE}*",
+ count: REDIS_CLEAR_BATCH_SIZE
+ )
+
+ redis.del(*keys) if keys.any?
+
+ break if cursor == REDIS_SCAN_START_STOP
+ end
end
end
+
+ desc "GitLab | Clear database cache (in the background)"
+ task db: :environment do
+ ClearDatabaseCacheWorker.perform_async
+ end
+
+ task all: [:db, :redis]
end
+
+ task clear: 'cache:clear:all'
end
diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake
new file mode 100644
index 00000000000..424e7883060
--- /dev/null
+++ b/lib/tasks/ce_to_ee_merge_check.rake
@@ -0,0 +1,4 @@
+desc 'Checks if the branch would apply cleanly to EE'
+task ce_to_ee_merge_check: :environment do
+ Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke
+end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 5f4a6bbfa35..2ae48a970ce 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -671,7 +671,7 @@ namespace :gitlab do
"Enable mail_room in the init.d configuration."
)
for_more_information(
- "doc/incoming_email/README.md"
+ "doc/administration/reply_by_email.md"
)
fix_and_rerun
end
@@ -690,7 +690,7 @@ namespace :gitlab do
"Enable mail_room in your Procfile."
)
for_more_information(
- "doc/incoming_email/README.md"
+ "doc/administration/reply_by_email.md"
)
fix_and_rerun
end
@@ -747,7 +747,7 @@ namespace :gitlab do
"Check that the information in config/gitlab.yml is correct"
)
for_more_information(
- "doc/incoming_email/README.md"
+ "doc/administration/reply_by_email.md"
)
fix_and_rerun
end
diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake
new file mode 100644
index 00000000000..47bdb2d32d2
--- /dev/null
+++ b/lib/tasks/gitlab/dev.rake
@@ -0,0 +1,107 @@
+namespace :gitlab do
+ namespace :dev do
+ desc 'Checks if the branch would apply cleanly to EE'
+ task ce_to_ee_merge_check: :environment do
+ return if defined?(Gitlab::License)
+ return unless ENV['CI']
+
+ ce_repo = ENV['CI_BUILD_REPO']
+ ce_branch = ENV['CI_BUILD_REF_NAME']
+
+ ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git'
+ ee_branch = "#{ce_branch}-ee"
+ ee_dir = 'gitlab-ee-merge-check'
+
+ puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n"
+ `git clone #{ee_repo} #{ee_dir} --depth 1`
+ Dir.chdir(ee_dir) do
+ puts "\n => Fetching #{ce_repo}/#{ce_branch}\n"
+ `git fetch #{ce_repo} #{ce_branch} --depth 1`
+
+ # Try to merge the current tested branch to EE/master...
+ puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n"
+ `git merge FETCH_HEAD`
+
+ exit 0 if $?.success?
+
+ # Check if the <branch>-ee branch exists...
+ puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n"
+ `git rev-parse --verify #{ee_branch}`
+
+ # The <branch>-ee doesn't exist
+ unless $?.success?
+ puts
+ puts <<-MSG.strip_heredoc
+ =================================================================
+ The #{ce_branch} branch cannot be merged without conflicts to the
+ current EE/master, and no #{ee_branch} branch was detected in
+ the EE repository.
+
+ Please create a #{ee_branch} branch that includes changes from
+ #{ce_branch} but also specific changes than can be applied cleanly
+ to EE/master.
+
+ You can create this branch as follows:
+
+ 1. In the EE repo:
+ $ git fetch origin
+ $ git fetch #{ce_repo} #{ce_branch}
+ $ git checkout -b #{ee_branch} FETCH_HEAD
+ $ git rebase origin/master
+ 2. At this point you will likely have conflicts, solve them, and
+ continue/finish the rebase. Note: You can squash the CE commits
+ before rebasing.
+ 3. You can squash all the original #{ce_branch} commits into a
+ single "Port of #{ce_branch} to EE".
+ 4. Push your branch to #{ee_repo}:
+ $ git push origin #{ee_branch}
+ =================================================================\n
+ MSG
+
+ exit 1
+ end
+
+ # Try to merge the <branch>-ee branch to EE/master...
+ puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n"
+ `git merge #{ee_branch} master`
+
+ # The <branch>-ee cannot be merged cleanly to EE/master...
+ unless $?.success?
+ puts
+ puts <<-MSG.strip_heredoc
+ =================================================================
+ The #{ce_branch} branch cannot be merged without conflicts to
+ EE/master, and even though the #{ee_branch} branch exists in the EE
+ repository, it cannot be merged without conflicts to EE/master.
+
+ Please update the #{ee_branch}, push it again to #{ee_repo}, and
+ retry this job.
+ =================================================================\n
+ MSG
+
+ exit 2
+ end
+
+ puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n"
+ `git merge FETCH_HEAD`
+ exit 0 if $?.success?
+
+ # The <branch>-ee can be merged cleanly to EE/master, but <branch> still
+ # cannot be merged cleanly to EE/master...
+ puts
+ puts <<-MSG.strip_heredoc
+ =================================================================
+ The #{ce_branch} branch cannot be merged without conflicts to EE, and
+ even though the #{ee_branch} branch exists in the EE repository and
+ applies cleanly to EE/master, it doesn't prevent conflicts when
+ merging #{ce_branch} into EE.
+
+ We may be in a complex situation here.
+ =================================================================\n
+ MSG
+
+ exit 3
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index bb7eb852f1b..210899882b4 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -78,7 +78,7 @@ namespace :gitlab do
f.puts "PATH=#{ENV['PATH']}"
end
- Gitlab::Shell.new.generate_and_link_secret_token
+ Gitlab::Shell.ensure_secret_token!
end
desc "GitLab | Setup gitlab-shell"
diff --git a/lib/tasks/gitlab/users.rake b/lib/tasks/gitlab/users.rake
new file mode 100644
index 00000000000..3a16ace60bd
--- /dev/null
+++ b/lib/tasks/gitlab/users.rake
@@ -0,0 +1,11 @@
+namespace :gitlab do
+ namespace :users do
+ desc "GitLab | Clear the authentication token for all users"
+ task clear_all_authentication_tokens: :environment do |t, args|
+ # Do small batched updates because these updates will be slow and locking
+ User.select(:id).find_in_batches(batch_size: 100) do |batch|
+ User.where(id: batch.map(&:id)).update_all(authentication_token: nil)
+ end
+ end
+ end
+end