summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api.rb25
-rw-r--r--lib/api/api.rb42
-rw-r--r--lib/api/deploy_keys.rb84
-rw-r--r--lib/api/entities.rb66
-rw-r--r--lib/api/groups.rb92
-rw-r--r--lib/api/helpers.rb54
-rw-r--r--lib/api/internal.rb40
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/api/merge_requests.rb49
-rw-r--r--lib/api/milestones.rb7
-rw-r--r--lib/api/notes.rb12
-rw-r--r--lib/api/project_hooks.rb108
-rw-r--r--lib/api/project_snippets.rb123
-rw-r--r--lib/api/projects.rb463
-rw-r--r--lib/api/repositories.rb190
-rw-r--r--lib/api/session.rb19
-rw-r--r--lib/api/system_hooks.rb70
-rw-r--r--lib/api/users.rb51
-rw-r--r--lib/backup/database.rb58
-rw-r--r--lib/backup/manager.rb106
-rw-r--r--lib/backup/repository.rb105
-rw-r--r--lib/backup/uploads.rb29
-rw-r--r--lib/extracts_path.rb66
-rw-r--r--lib/gitlab/access.rb52
-rw-r--r--lib/gitlab/auth.rb70
-rw-r--r--lib/gitlab/backend/grack_auth.rb123
-rw-r--r--lib/gitlab/backend/grack_helpers.rb28
-rw-r--r--lib/gitlab/backend/shell.rb169
-rw-r--r--lib/gitlab/backend/shell_adapter.rb12
-rw-r--r--lib/gitlab/backend/shell_env.rb2
-rw-r--r--lib/gitlab/blacklist.rb9
-rw-r--r--lib/gitlab/diff_parser.rb77
-rw-r--r--lib/gitlab/git_stats.rb73
-rw-r--r--lib/gitlab/graph/commit.rb52
-rw-r--r--lib/gitlab/graph/json_builder.rb268
-rw-r--r--lib/gitlab/identifier.rb23
-rw-r--r--lib/gitlab/inline_diff.rb21
-rw-r--r--lib/gitlab/issues_labels.rb28
-rw-r--r--lib/gitlab/ldap/user.rb94
-rw-r--r--lib/gitlab/markdown.rb42
-rw-r--r--lib/gitlab/oauth/user.rb85
-rw-r--r--lib/gitlab/popen.rb2
-rw-r--r--lib/gitlab/project_mover.rb45
-rw-r--r--lib/gitlab/reference_extractor.rb59
-rw-r--r--lib/gitlab/regex.rb8
-rw-r--r--lib/gitlab/satellite/action.rb22
-rw-r--r--lib/gitlab/satellite/edit_file_action.rb4
-rw-r--r--lib/gitlab/satellite/merge_action.rb127
-rw-r--r--lib/gitlab/satellite/satellite.rb49
-rw-r--r--lib/gitlab/theme.rb16
-rw-r--r--lib/gitlab/user_team_manager.rb135
-rw-r--r--lib/gitlab/version_info.rb54
-rw-r--r--lib/gitolited.rb11
-rw-r--r--lib/redcarpet/render/gitlab_html.rb3
-rwxr-xr-xlib/support/deploy/deploy.sh44
-rwxr-xr-xlib/support/init.d/gitlab262
-rw-r--r--lib/support/nginx/gitlab39
-rw-r--r--lib/tasks/cache.rake6
-rw-r--r--lib/tasks/dev.rake10
-rw-r--r--lib/tasks/gitlab/backup.rake213
-rw-r--r--lib/tasks/gitlab/bulk_add_permission.rake8
-rw-r--r--lib/tasks/gitlab/check.rake194
-rw-r--r--lib/tasks/gitlab/cleanup.rake6
-rw-r--r--lib/tasks/gitlab/enable_namespaces.rake4
-rw-r--r--lib/tasks/gitlab/import.rake57
-rw-r--r--lib/tasks/gitlab/info.rake6
-rw-r--r--lib/tasks/gitlab/setup.rake16
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--lib/tasks/gitlab/task_helpers.rake17
-rw-r--r--lib/tasks/gitlab/test.rake2
-rw-r--r--lib/tasks/migrate/migrate_iids.rake48
-rw-r--r--lib/tasks/sidekiq.rake10
-rw-r--r--lib/tasks/travis.rake2
73 files changed, 3104 insertions, 1484 deletions
diff --git a/lib/api.rb b/lib/api.rb
deleted file mode 100644
index d9dce7c70cc..00000000000
--- a/lib/api.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
-
-module Gitlab
- class API < Grape::API
- version 'v3', using: :path
-
- rescue_from ActiveRecord::RecordNotFound do
- rack_response({'message' => '404 Not found'}.to_json, 404)
- end
-
- format :json
- error_format :json
- helpers APIHelpers
-
- mount Groups
- mount Users
- mount Projects
- mount Issues
- mount Milestones
- mount Session
- mount MergeRequests
- mount Notes
- mount Internal
- end
-end
diff --git a/lib/api/api.rb b/lib/api/api.rb
new file mode 100644
index 00000000000..c4c9f166db1
--- /dev/null
+++ b/lib/api/api.rb
@@ -0,0 +1,42 @@
+Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
+
+module API
+ class API < Grape::API
+ version 'v3', using: :path
+
+ rescue_from ActiveRecord::RecordNotFound do
+ rack_response({'message' => '404 Not found'}.to_json, 404)
+ end
+
+ rescue_from :all do |exception|
+ # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
+ # why is this not wrapped in something reusable?
+ trace = exception.backtrace
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << trace.join("\n ")
+
+ API.logger.add Logger::FATAL, message
+ rack_response({'message' => '500 Internal Server Error'}, 500)
+ end
+
+ format :json
+ helpers APIHelpers
+
+ mount Groups
+ mount Users
+ mount Projects
+ mount Repositories
+ mount Issues
+ mount Milestones
+ mount Session
+ mount MergeRequests
+ mount Notes
+ mount Internal
+ mount SystemHooks
+ mount ProjectSnippets
+ mount DeployKeys
+ mount ProjectHooks
+ end
+end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
new file mode 100644
index 00000000000..55c947eb176
--- /dev/null
+++ b/lib/api/deploy_keys.rb
@@ -0,0 +1,84 @@
+module API
+ # Projects API
+ class DeployKeys < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+
+ # Get a specific project's keys
+ #
+ # Example Request:
+ # GET /projects/:id/keys
+ get ":id/keys" do
+ present user_project.deploy_keys, with: Entities::SSHKey
+ end
+
+ # Get single key owned by currently authenticated user
+ #
+ # Example Request:
+ # GET /projects/:id/keys/:id
+ get ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ present key, with: Entities::SSHKey
+ end
+
+ # Add new ssh key to currently authenticated user
+ # If deploy key already exists - it will be joined to project
+ # but only if original one was is accessible by same user
+ #
+ # Parameters:
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /projects/:id/keys
+ post ":id/keys" do
+ attrs = attributes_for_keys [:title, :key]
+
+ if attrs[:key].present?
+ attrs[:key].strip!
+
+ # check if key already exist in project
+ key = user_project.deploy_keys.find_by_key(attrs[:key])
+ if key
+ present key, with: Entities::SSHKey
+ return
+ end
+
+ # Check for available deploy keys in other projects
+ key = current_user.accessible_deploy_keys.find_by_key(attrs[:key])
+ if key
+ user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ return
+ end
+ end
+
+ key = DeployKey.new attrs
+
+ if key.valid? && user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
+ # Delete existed ssh key of currently authenticated user
+ #
+ # Example Request:
+ # DELETE /projects/:id/keys/:id
+ delete ":id/keys/:key_id" do
+ key = user_project.deploy_keys.find params[:key_id]
+ key.destroy
+ end
+ end
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c1873d87b55..1f35e9ec5fc 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1,46 +1,78 @@
-module Gitlab
+module API
module Entities
class User < Grape::Entity
expose :id, :username, :email, :name, :bio, :skype, :linkedin, :twitter,
- :dark_scheme, :theme_id, :blocked, :created_at, :extern_uid, :provider
+ :theme_id, :color_scheme_id, :state, :created_at, :extern_uid, :provider
+ end
+
+ class UserSafe < Grape::Entity
+ expose :name
end
class UserBasic < Grape::Entity
- expose :id, :username, :email, :name, :blocked, :created_at
+ expose :id, :username, :email, :name, :state, :created_at
end
- class UserLogin < UserBasic
+ class UserLogin < User
expose :private_token
+ expose :is_admin?, as: :is_admin
+ expose :can_create_group?, as: :can_create_group
+ expose :can_create_project?, as: :can_create_project
+ expose :can_create_team?, as: :can_create_team
end
class Hook < Grape::Entity
expose :id, :url, :created_at
end
+ class ForkedFromProject < Grape::Entity
+ expose :id
+ expose :name, :name_with_namespace
+ expose :path, :path_with_namespace
+ end
+
class Project < Grape::Entity
- expose :id, :name, :description, :default_branch
+ expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :owner, using: Entities::UserBasic
- expose :private_flag, as: :private
+ expose :name, :name_with_namespace
expose :path, :path_with_namespace
- expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :created_at
+ expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public
expose :namespace
+ expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
end
class ProjectMember < UserBasic
- expose :project_access, :as => :access_level do |user, options|
+ expose :project_access, as: :access_level do |user, options|
options[:project].users_projects.find_by_user_id(user.id).project_access
end
end
+ class TeamMember < UserBasic
+ expose :permission, as: :access_level do |user, options|
+ options[:user_team].user_team_user_relationships.find_by_user_id(user.id).permission
+ end
+ end
+
+ class TeamProject < Project
+ expose :greatest_access, as: :greatest_access_level do |project, options|
+ options[:user_team].user_team_project_relationships.find_by_project_id(project.id).greatest_access
+ end
+ end
+
class Group < Grape::Entity
expose :id, :name, :path, :owner_id
end
-
+
class GroupDetail < Group
expose :projects, using: Entities::Project
end
-
+ class GroupMember < UserBasic
+ expose :group_access, as: :access_level do |user, options|
+ options[:group].users_groups.find_by_user_id(user.id).group_access
+ end
+ end
+
class RepoObject < Grape::Entity
expose :name, :commit
expose :protected do |repo, options|
@@ -63,7 +95,7 @@ module Gitlab
class Milestone < Grape::Entity
expose :id
expose (:project_id) {|milestone| milestone.project.id}
- expose :title, :description, :due_date, :closed, :updated_at, :created_at
+ expose :title, :description, :due_date, :state, :updated_at, :created_at
end
class Issue < Grape::Entity
@@ -73,7 +105,7 @@ module Gitlab
expose :label_list, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :closed, :updated_at, :created_at
+ expose :state, :updated_at, :created_at
end
class SSHKey < Grape::Entity
@@ -81,13 +113,15 @@ module Gitlab
end
class MergeRequest < Grape::Entity
- expose :id, :target_branch, :source_branch, :project_id, :title, :closed, :merged
+ expose :id, :target_branch, :source_branch, :title, :state
+ expose :target_project_id, as: :project_id
expose :author, :assignee, using: Entities::UserBasic
end
class Note < Grape::Entity
expose :id
expose :note, as: :body
+ expose :attachment_identifier, as: :attachment
expose :author, using: Entities::UserBasic
expose :created_at
end
@@ -96,5 +130,11 @@ module Gitlab
expose :note
expose :author, using: Entities::UserBasic
end
+
+ class Event < Grape::Entity
+ expose :title, :project_id, :action_name
+ expose :target_id, :target_type, :author_id
+ expose :data, :target_title
+ end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index a67caef0bc5..396554404af 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -1,9 +1,23 @@
-module Gitlab
+module API
# groups API
class Groups < Grape::API
before { authenticate! }
resource :groups do
+ helpers do
+ def find_group(id)
+ group = Group.find(id)
+ if current_user.admin or current_user.groups.include? group
+ group
+ else
+ render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
+ end
+ end
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+ end
+
# Get a groups list
#
# Example Request:
@@ -20,12 +34,14 @@ module Gitlab
# Create group. Available only for admin
#
# Parameters:
- # name (required) - Name
- # path (required) - Path
+ # name (required) - The name of the group
+ # path (required) - The path of the group
# Example Request:
# POST /groups
post do
authenticated_as_admin!
+ required_attributes! [:name, :path]
+
attrs = attributes_for_keys [:name, :path]
@group = Group.new(attrs)
@group.owner = current_user
@@ -44,13 +60,79 @@ module Gitlab
# Example Request:
# GET /groups/:id
get ":id" do
+ group = find_group(params[:id])
+ present group, with: Entities::GroupDetail
+ end
+
+ # Transfer a project to the Group namespace
+ #
+ # Parameters:
+ # id - group id
+ # project_id - project id
+ # Example Request:
+ # POST /groups/:id/projects/:project_id
+ post ":id/projects/:project_id" do
+ authenticated_as_admin!
@group = Group.find(params[:id])
- if current_user.admin or current_user.groups.include? @group
- present @group, with: Entities::GroupDetail
+ project = Project.find(params[:project_id])
+ if project.transfer(@group)
+ present @group
else
not_found!
end
end
+
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ members = group.users_groups
+ users = (paginate members).collect(&:user)
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ required_attributes! [:user_id, :access_level]
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+ group = find_group(params[:id])
+ if group.users_groups.find_by_user_id(params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+ group.add_users([params[:user_id]], params[:access_level])
+ member = group.users_groups.find_by_user_id(params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ member = group.users_groups.find_by_user_id(params[:user_id])
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 6bd8111c2b2..4f189f35196 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -1,16 +1,43 @@
-module Gitlab
+module API
module APIHelpers
+ PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN"
+ PRIVATE_TOKEN_PARAM = :private_token
+ SUDO_HEADER ="HTTP_SUDO"
+ SUDO_PARAM = :sudo
+
def current_user
- @current_user ||= User.find_by_authentication_token(params[:private_token] || env["HTTP_PRIVATE_TOKEN"])
+ @current_user ||= User.find_by_authentication_token(params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER])
+ identifier = sudo_identifier()
+ # If the sudo is the current user do nothing
+ if (identifier && !(@current_user.id == identifier || @current_user.username == identifier))
+ render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
+ begin
+ @current_user = User.by_username_or_id(identifier)
+ rescue => ex
+ not_found!("No user id or username for: #{identifier}")
+ end
+ not_found!("No user id or username for: #{identifier}") if current_user.nil?
+ end
+ @current_user
+ end
+
+ def sudo_identifier()
+ identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER]
+ # Regex for integers
+ if (!!(identifier =~ /^[0-9]+$/))
+ identifier.to_i
+ else
+ identifier
+ end
end
def user_project
- @project ||= find_project
+ @project ||= find_project(params[:id])
@project || not_found!
end
- def find_project
- project = Project.find_by_id(params[:id]) || Project.find_with_namespace(params[:id])
+ def find_project(id)
+ project = Project.find_by_id(id) || Project.find_with_namespace(id)
if project && can?(current_user, :read_project, project)
project
@@ -41,6 +68,17 @@ module Gitlab
abilities.allowed?(object, action, subject)
end
+ # Checks the occurrences of required attributes, each attribute must be present in the params hash
+ # or a Bad Request error is invoked.
+ #
+ # Parameters:
+ # keys (required) - A hash consisting of keys that must be present
+ def required_attributes!(keys)
+ keys.each do |key|
+ bad_request!(key) unless params[key].present?
+ end
+ end
+
def attributes_for_keys(keys)
attrs = {}
keys.each do |key|
@@ -55,6 +93,12 @@ module Gitlab
render_api_error!('403 Forbidden', 403)
end
+ def bad_request!(attribute)
+ message = ["400 (Bad request)"]
+ message << "\"" + attribute.to_s + "\" not given"
+ render_api_error!(message.join(' '), 400)
+ end
+
def not_found!(resource = nil)
message = ["404"]
message << resource if resource
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 3e5e3a478ba..79f8eb3a543 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,23 +1,45 @@
-module Gitlab
+module API
# Internal access API
class Internal < Grape::API
+
+ DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
+ PUSH_COMMANDS = %w{ git-receive-pack }
+
namespace 'internal' do
#
# Check if ssh key has access to project code
#
+ # Params:
+ # key_id - SSH Key id
+ # project - project path with namespace
+ # action - git action (git-upload-pack or git-receive-pack)
+ # ref - branch name
+ #
get "/allowed" do
+ # Check for *.wiki repositories.
+ # Strip out the .wiki from the pathname before finding the
+ # project. This applies the correct project permissions to
+ # the wiki repository as well.
+ project_path = params[:project]
+ project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
+
key = Key.find(params[:key_id])
- project = Project.find_with_namespace(params[:project])
+ project = Project.find_with_namespace(project_path)
git_cmd = params[:action]
+ return false unless project
- if key.is_deploy_key
- project == key.project && git_cmd == 'git-upload-pack'
+
+ if key.is_a? DeployKey
+ key.projects.include?(project) && DOWNLOAD_COMMANDS.include?(git_cmd)
else
user = key.user
+
+ return false if user.blocked?
+
action = case git_cmd
- when 'git-upload-pack'
+ when *DOWNLOAD_COMMANDS
then :download_code
- when 'git-receive-pack'
+ when *PUSH_COMMANDS
then
if project.protected_branch?(params[:ref])
:push_code_to_protected_branches
@@ -35,12 +57,14 @@ module Gitlab
#
get "/discover" do
key = Key.find(params[:key_id])
- present key.user, with: Entities::User
+ present key.user, with: Entities::UserSafe
end
get "/check" do
{
- api_version: '3'
+ api_version: API.version,
+ gitlab_version: Gitlab::VERSION,
+ gitlab_rev: Gitlab::REVISION,
}
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 4d832fbe593..a15203d1563 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -1,7 +1,8 @@
-module Gitlab
+module API
# Issues API
class Issues < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :issues do
# Get currently authenticated user's issues
@@ -48,6 +49,7 @@ module Gitlab
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
+ required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs[:label_list] = params[:labels] if params[:labels].present?
@issue = user_project.issues.new attrs
@@ -69,16 +71,16 @@ module Gitlab
# assignee_id (optional) - The ID of a user to assign issue
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
- # closed (optional) - The state of an issue (0 = false, 1 = true)
+ # state_event (optional) - The state event of an issue (close|reopen)
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
@issue = user_project.issues.find(params[:issue_id])
authorize! :modify_issue, @issue
- attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :closed]
+ attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
- IssueObserver.current_user = current_user
+
if @issue.update_attributes attrs
present @issue, with: Entities::Issue
else
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 470cd1e1c2d..d690f1d07e7 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -1,9 +1,28 @@
-module Gitlab
+module API
# MergeRequest API
class MergeRequests < Grape::API
before { authenticate! }
+ before { Thread.current[:current_user] = current_user }
resource :projects do
+ helpers do
+ def handle_merge_request_errors!(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ elsif errors[:branch_conflict].any?
+ error!(errors[:branch_conflict], 422)
+ end
+ not_found!
+ end
+
+ def not_fork?(target_project_id, user_project)
+ target_project_id.nil? || target_project_id == user_project.id.to_s
+ end
+
+ def target_matches_fork(target_project_id,user_project)
+ user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
+ end
+ end
# List merge requests
#
@@ -40,9 +59,10 @@ module Gitlab
#
# Parameters:
#
- # id (required) - The ID of a project
+ # id (required) - The ID of a project - this will be the source of the merge request
# source_branch (required) - The source branch
# target_branch (required) - The target branch
+ # target_project - The target project of the merge request defaults to the :id of the project
# assignee_id - Assignee user ID
# title (required) - Title of MR
#
@@ -51,16 +71,27 @@ module Gitlab
#
post ":id/merge_requests" do
authorize! :write_merge_request, user_project
-
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title]
+ required_attributes! [:source_branch, :target_branch, :title]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id]
merge_request = user_project.merge_requests.new(attrs)
merge_request.author = current_user
+ merge_request.source_project = user_project
+ target_project_id = attrs[:target_project_id]
+ if not_fork?(target_project_id, user_project)
+ merge_request.target_project = user_project
+ else
+ if target_matches_fork(target_project_id,user_project)
+ merge_request.target_project = Project.find_by_id(attrs[:target_project_id])
+ else
+ render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
+ end
+ end
if merge_request.save
merge_request.reload_code
present merge_request, with: Entities::MergeRequest
else
- not_found!
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -73,12 +104,12 @@ module Gitlab
# target_branch - The target branch
# assignee_id - Assignee user ID
# title - Title of MR
- # closed - Status of MR. true - closed
+ # state_event - Status of MR. (close|reopen|merge)
# Example:
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :closed]
+ attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
@@ -88,7 +119,7 @@ module Gitlab
merge_request.mark_as_unchecked
present merge_request, with: Entities::MergeRequest
else
- not_found!
+ handle_merge_request_errors! merge_request.errors
end
end
@@ -102,6 +133,8 @@ module Gitlab
# POST /projects/:id/merge_request/:merge_request_id/comments
#
post ":id/merge_request/:merge_request_id/comments" do
+ required_attributes! [:note]
+
merge_request = user_project.merge_requests.find(params[:merge_request_id])
note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
note.author = current_user
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 6aca9d01b09..aee12e7dc40 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Milestones API
class Milestones < Grape::API
before { authenticate! }
@@ -41,6 +41,7 @@ module Gitlab
# POST /projects/:id/milestones
post ":id/milestones" do
authorize! :admin_milestone, user_project
+ required_attributes! [:title]
attrs = attributes_for_keys [:title, :description, :due_date]
@milestone = user_project.milestones.new attrs
@@ -59,14 +60,14 @@ module Gitlab
# title (optional) - The title of a milestone
# description (optional) - The description of a milestone
# due_date (optional) - The due date of a milestone
- # closed (optional) - The status of the milestone
+ # state_event (optional) - The state event of the milestone (close|activate)
# Example Request:
# PUT /projects/:id/milestones/:milestone_id
put ":id/milestones/:milestone_id" do
authorize! :admin_milestone, user_project
@milestone = user_project.milestones.find(params[:milestone_id])
- attrs = attributes_for_keys [:title, :description, :due_date, :closed]
+ attrs = attributes_for_keys [:title, :description, :due_date, :state_event]
if @milestone.update_attributes attrs
present @milestone, with: Entities::Milestone
else
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 70344d6e381..cb2bc764476 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Notes API
class Notes < Grape::API
before { authenticate! }
@@ -14,6 +14,10 @@ module Gitlab
# GET /projects/:id/notes
get ":id/notes" do
@notes = user_project.notes.common
+
+ # Get recent notes if recent = true
+ @notes = @notes.order('id DESC') if params[:recent]
+
present paginate(@notes), with: Entities::Note
end
@@ -37,12 +41,16 @@ module Gitlab
# Example Request:
# POST /projects/:id/notes
post ":id/notes" do
+ required_attributes! [:body]
+
@note = user_project.notes.new(note: params[:body])
@note.author = current_user
if @note.save
present @note, with: Entities::Note
else
+ # :note is exposed as :body, but :note is set on error
+ bad_request!(:note) if @note.errors[:note].any?
not_found!
end
end
@@ -89,6 +97,8 @@ module Gitlab
# POST /projects/:id/issues/:noteable_id/notes
# POST /projects/:id/snippets/:noteable_id/notes
post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
+ required_attributes! [:body]
+
@noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"])
@note = @noteable.notes.new(note: params[:body])
@note.author = current_user
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
new file mode 100644
index 00000000000..28501256795
--- /dev/null
+++ b/lib/api/project_hooks.rb
@@ -0,0 +1,108 @@
+module API
+ # Projects API
+ class ProjectHooks < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get project hooks
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/hooks
+ get ":id/hooks" do
+ authorize! :admin_project, user_project
+ @hooks = paginate user_project.hooks
+ present @hooks, with: Entities::Hook
+ end
+
+ # Get a project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # Example Request:
+ # GET /projects/:id/hooks/:hook_id
+ get ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ @hook = user_project.hooks.find(params[:hook_id])
+ present @hook, with: Entities::Hook
+ end
+
+
+ # Add hook to project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # url (required) - The hook URL
+ # Example Request:
+ # POST /projects/:id/hooks
+ post ":id/hooks" do
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ @hook = user_project.hooks.new({"url" => params[:url]})
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Update an existing project hook
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of a project hook
+ # url (required) - The hook URL
+ # Example Request:
+ # PUT /projects/:id/hooks/:hook_id
+ put ":id/hooks/:hook_id" do
+ @hook = user_project.hooks.find(params[:hook_id])
+ authorize! :admin_project, user_project
+ required_attributes! [:url]
+
+ attrs = attributes_for_keys [:url]
+ if @hook.update_attributes attrs
+ present @hook, with: Entities::Hook
+ else
+ if @hook.errors[:url].present?
+ error!("Invalid url given", 422)
+ end
+ not_found!
+ end
+ end
+
+ # Deletes project hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # hook_id (required) - The ID of hook to delete
+ # Example Request:
+ # DELETE /projects/:id/hooks/:hook_id
+ delete ":id/hooks/:hook_id" do
+ authorize! :admin_project, user_project
+ required_attributes! [:hook_id]
+
+ begin
+ @hook = ProjectHook.find(params[:hook_id])
+ @hook.destroy
+ rescue
+ # ProjectHook can raise Error if hook_id not found
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
new file mode 100644
index 00000000000..bee6544ea3d
--- /dev/null
+++ b/lib/api/project_snippets.rb
@@ -0,0 +1,123 @@
+module API
+ # Projects API
+ class ProjectSnippets < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get a project snippets
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/snippets
+ get ":id/snippets" do
+ present paginate(user_project.snippets), with: Entities::ProjectSnippet
+ end
+
+ # Get a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id
+ get ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ present @snippet, with: Entities::ProjectSnippet
+ end
+
+ # Create a new project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # title (required) - The title of a snippet
+ # file_name (required) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (required) - The content of a snippet
+ # Example Request:
+ # POST /projects/:id/snippets
+ post ":id/snippets" do
+ authorize! :write_project_snippet, user_project
+ required_attributes! [:title, :file_name, :code]
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+ @snippet = user_project.snippets.new attrs
+ @snippet.author = current_user
+
+ if @snippet.save
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Update an existing project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # title (optional) - The title of a snippet
+ # file_name (optional) - The name of a snippet file
+ # lifetime (optional) - The expiration date of a snippet
+ # code (optional) - The content of a snippet
+ # Example Request:
+ # PUT /projects/:id/snippets/:snippet_id
+ put ":id/snippets/:snippet_id" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+
+ attrs = attributes_for_keys [:title, :file_name]
+ attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
+ attrs[:content] = params[:code] if params[:code].present?
+
+ if @snippet.update_attributes attrs
+ present @snippet, with: Entities::ProjectSnippet
+ else
+ not_found!
+ end
+ end
+
+ # Delete a project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # DELETE /projects/:id/snippets/:snippet_id
+ delete ":id/snippets/:snippet_id" do
+ begin
+ @snippet = user_project.snippets.find(params[:snippet_id])
+ authorize! :modify_project_snippet, @snippet
+ @snippet.destroy
+ rescue
+ end
+ end
+
+ # Get a raw project snippet
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # snippet_id (required) - The ID of a project snippet
+ # Example Request:
+ # GET /projects/:id/snippets/:snippet_id/raw
+ get ":id/snippets/:snippet_id/raw" do
+ @snippet = user_project.snippets.find(params[:snippet_id])
+
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ present @snippet.content
+ end
+ end
+ end
+end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index d416121a78a..cf357b23c40 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,9 +1,18 @@
-module Gitlab
+module API
# Projects API
class Projects < Grape::API
before { authenticate! }
resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
# Get a projects list for authenticated user
#
# Example Request:
@@ -13,6 +22,15 @@ module Gitlab
present @projects, with: Entities::Project
end
+ # Get an owned projects list for authenticated user
+ #
+ # Example Request:
+ # GET /projects/owned
+ get '/owned' do
+ @projects = paginate current_user.owned_projects
+ present @projects, with: Entities::Project
+ end
+
# Get a single project
#
# Parameters:
@@ -23,32 +41,128 @@ module Gitlab
present user_project, with: Entities::Project
end
+ # Get a single project events
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id
+ get ":id/events" do
+ limit = (params[:per_page] || 20).to_i
+ offset = (params[:page] || 0).to_i * limit
+ events = user_project.events.recent.limit(limit).offset(offset)
+
+ present events, with: Entities::Event
+ end
+
# Create new project
#
# Parameters:
# name (required) - name for new project
# description (optional) - short project description
# default_branch (optional) - 'master' by default
- # issues_enabled (optional) - enabled by default
- # wall_enabled (optional) - enabled by default
- # merge_requests_enabled (optional) - enabled by default
- # wiki_enabled (optional) - enabled by default
+ # issues_enabled (optional)
+ # wall_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # namespace_id (optional) - defaults to user namespace
+ # public (optional) - false by default
# Example Request
# POST /projects
post do
+ required_attributes! [:name]
attrs = attributes_for_keys [:name,
- :description,
- :default_branch,
- :issues_enabled,
- :wall_enabled,
- :merge_requests_enabled,
- :wiki_enabled]
+ :path,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :wall_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :namespace_id,
+ :public]
@project = ::Projects::CreateContext.new(current_user, attrs).execute
if @project.saved?
present @project, with: Entities::Project
else
+ if @project.errors[:limit_reached].present?
+ error!(@project.errors[:limit_reached], 403)
+ end
+ not_found!
+ end
+ end
+
+ # Create new project for a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # user_id (required) - The ID of a user
+ # name (required) - name for new project
+ # description (optional) - short project description
+ # default_branch (optional) - 'master' by default
+ # issues_enabled (optional)
+ # wall_enabled (optional)
+ # merge_requests_enabled (optional)
+ # wiki_enabled (optional)
+ # snippets_enabled (optional)
+ # public (optional)
+ # Example Request
+ # POST /projects/user/:user_id
+ post "user/:user_id" do
+ authenticated_as_admin!
+ user = User.find(params[:user_id])
+ attrs = attributes_for_keys [:name,
+ :description,
+ :default_branch,
+ :issues_enabled,
+ :wall_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :snippets_enabled,
+ :public]
+ @project = ::Projects::CreateContext.new(user, attrs).execute
+ if @project.saved?
+ present @project, with: Entities::Project
+ else
+ not_found!
+ end
+ end
+
+
+ # Mark this project as forked from another
+ #
+ # Parameters:
+ # id: (required) - The ID of the project being marked as a fork
+ # forked_from_id: (required) - The ID of the project it was forked from
+ # Example Request:
+ # POST /projects/:id/fork/:forked_from_id
+ post ":id/fork/:forked_from_id" do
+ authenticated_as_admin!
+ forked_from_project = find_project(params[:forked_from_id])
+ unless forked_from_project.nil?
+ if user_project.forked_from_project.nil?
+ user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+ else
+ render_api_error!("Project already forked", 409)
+ end
+ else
not_found!
end
+
+ end
+
+ # Remove a forked_from relationship
+ #
+ # Parameters:
+ # id: (required) - The ID of the project being marked as a fork
+ # Example Request:
+ # DELETE /projects/:id/fork
+ delete ":id/fork" do
+ authenticated_as_admin!
+ unless user_project.forked_project_link.nil?
+ user_project.forked_project_link.destroy
+ end
end
# Get a project team members
@@ -89,16 +203,22 @@ module Gitlab
# POST /projects/:id/members
post ":id/members" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.new(
- user_id: params[:user_id],
- project_access: params[:access_level]
- )
+ required_attributes! [:user_id, :access_level]
+
+ # either the user is already a team member or a new one
+ team_member = user_project.team_member_by_id(params[:user_id])
+ if team_member.nil?
+ team_member = user_project.users_projects.new(
+ user_id: params[:user_id],
+ project_access: params[:access_level]
+ )
+ end
- if users_project.save
- @member = users_project.user
+ if team_member.save
+ @member = team_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- not_found!
+ handle_project_member_errors team_member.errors
end
end
@@ -112,13 +232,16 @@ module Gitlab
# PUT /projects/:id/members/:user_id
put ":id/members/:user_id" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.find_by_user_id params[:user_id]
+ required_attributes! [:access_level]
+
+ team_member = user_project.users_projects.find_by_user_id(params[:user_id])
+ not_found!("User can not be found") if team_member.nil?
- if users_project.update_attributes(project_access: params[:access_level])
- @member = users_project.user
+ if team_member.update_attributes(project_access: params[:access_level])
+ @member = team_member.user
present @member, with: Entities::ProjectMember, project: user_project
else
- not_found!
+ handle_project_member_errors team_member.errors
end
end
@@ -131,297 +254,27 @@ module Gitlab
# DELETE /projects/:id/members/:user_id
delete ":id/members/:user_id" do
authorize! :admin_project, user_project
- users_project = user_project.users_projects.find_by_user_id params[:user_id]
- users_project.destroy
- end
-
- # Get project hooks
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/hooks
- get ":id/hooks" do
- authorize! :admin_project, user_project
- @hooks = paginate user_project.hooks
- present @hooks, with: Entities::Hook
- end
-
- # Get a project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # Example Request:
- # GET /projects/:id/hooks/:hook_id
- get ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- present @hook, with: Entities::Hook
- end
-
-
- # Add hook to project
- #
- # Parameters:
- # id (required) - The ID of a project
- # url (required) - The hook URL
- # Example Request:
- # POST /projects/:id/hooks
- post ":id/hooks" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.new({"url" => params[:url]})
- if @hook.save
- present @hook, with: Entities::Hook
+ team_member = user_project.users_projects.find_by_user_id(params[:user_id])
+ unless team_member.nil?
+ team_member.destroy
else
- error!({'message' => '404 Not found'}, 404)
+ {message: "Access revoked", id: params[:user_id].to_i}
end
end
- # Update an existing project hook
+ # search for projects current_user has access to
#
# Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of a project hook
- # url (required) - The hook URL
+ # query (required) - A string contained in the project name
+ # per_page (optional) - number of projects to return per page
+ # page (optional) - the page to retrieve
# Example Request:
- # PUT /projects/:id/hooks/:hook_id
- put ":id/hooks/:hook_id" do
- @hook = user_project.hooks.find(params[:hook_id])
- authorize! :admin_project, user_project
-
- attrs = attributes_for_keys [:url]
-
- if @hook.update_attributes attrs
- present @hook, with: Entities::Hook
- else
- not_found!
- end
+ # GET /projects/search/:query
+ get "/search/:query" do
+ ids = current_user.authorized_projects.map(&:id)
+ projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%")
+ present paginate(projects), with: Entities::Project
end
-
- # Delete project hook
- #
- # Parameters:
- # id (required) - The ID of a project
- # hook_id (required) - The ID of hook to delete
- # Example Request:
- # DELETE /projects/:id/hooks
- delete ":id/hooks" do
- authorize! :admin_project, user_project
- @hook = user_project.hooks.find(params[:hook_id])
- @hook.destroy
- end
-
- # Get a project repository branches
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/branches
- get ":id/repository/branches" do
- present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
- end
-
- # Get a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # GET /projects/:id/repository/branches/:branch
- get ":id/repository/branches/:branch" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- not_found!("Branch does not exist") if @branch.nil?
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Protect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/protect
- put ":id/repository/branches/:branch/protect" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- protected = user_project.protected_branches.find_by_name(@branch.name)
-
- unless protected
- user_project.protected_branches.create(:name => @branch.name)
- end
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Unprotect a single branch
- #
- # Parameters:
- # id (required) - The ID of a project
- # branch (required) - The name of the branch
- # Example Request:
- # PUT /projects/:id/repository/branches/:branch/unprotect
- put ":id/repository/branches/:branch/unprotect" do
- @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
- protected = user_project.protected_branches.find_by_name(@branch.name)
-
- if protected
- protected.destroy
- end
-
- present @branch, with: Entities::RepoObject, project: user_project
- end
-
- # Get a project repository tags
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/repository/tags
- get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
- end
-
- # Get a project repository commits
- #
- # Parameters:
- # id (required) - The ID of a project
- # ref_name (optional) - The name of a repository branch or tag
- # Example Request:
- # GET /projects/:id/repository/commits
- get ":id/repository/commits" do
- authorize! :download_code, user_project
-
- page = params[:page] || 0
- per_page = params[:per_page] || 20
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
-
- commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
- present CommitDecorator.decorate(commits), with: Entities::RepoCommit
- end
-
- # Get a project snippets
- #
- # Parameters:
- # id (required) - The ID of a project
- # Example Request:
- # GET /projects/:id/snippets
- get ":id/snippets" do
- present paginate(user_project.snippets), with: Entities::ProjectSnippet
- end
-
- # Get a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id
- get ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- present @snippet, with: Entities::ProjectSnippet
- end
-
- # Create a new project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # title (required) - The title of a snippet
- # file_name (required) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (required) - The content of a snippet
- # Example Request:
- # POST /projects/:id/snippets
- post ":id/snippets" do
- authorize! :write_snippet, user_project
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
- @snippet = user_project.snippets.new attrs
- @snippet.author = current_user
-
- if @snippet.save
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Update an existing project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # title (optional) - The title of a snippet
- # file_name (optional) - The name of a snippet file
- # lifetime (optional) - The expiration date of a snippet
- # code (optional) - The content of a snippet
- # Example Request:
- # PUT /projects/:id/snippets/:snippet_id
- put ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- attrs = attributes_for_keys [:title, :file_name]
- attrs[:expires_at] = params[:lifetime] if params[:lifetime].present?
- attrs[:content] = params[:code] if params[:code].present?
-
- if @snippet.update_attributes attrs
- present @snippet, with: Entities::ProjectSnippet
- else
- not_found!
- end
- end
-
- # Delete a project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # DELETE /projects/:id/snippets/:snippet_id
- delete ":id/snippets/:snippet_id" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_snippet, @snippet
-
- @snippet.destroy
- end
-
- # Get a raw project snippet
- #
- # Parameters:
- # id (required) - The ID of a project
- # snippet_id (required) - The ID of a project snippet
- # Example Request:
- # GET /projects/:id/snippets/:snippet_id/raw
- get ":id/snippets/:snippet_id/raw" do
- @snippet = user_project.snippets.find(params[:snippet_id])
- content_type 'text/plain'
- present @snippet.content
- end
-
- # Get a raw file contents
- #
- # Parameters:
- # id (required) - The ID of a project
- # sha (required) - The commit or branch name
- # filepath (required) - The path to the file to display
- # Example Request:
- # GET /projects/:id/repository/commits/:sha/blob
- get ":id/repository/commits/:sha/blob" do
- authorize! :download_code, user_project
-
- ref = params[:sha]
-
- commit = user_project.repository.commit ref
- not_found! "Commit" unless commit
-
- tree = Tree.new commit.tree, ref, params[:filepath]
- not_found! "File" unless tree.try(:tree)
-
- content_type tree.mime_type
- present tree.data
- end
-
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
new file mode 100644
index 00000000000..fef32d3a2fe
--- /dev/null
+++ b/lib/api/repositories.rb
@@ -0,0 +1,190 @@
+module API
+ # Projects API
+ class Repositories < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ helpers do
+ def handle_project_member_errors(errors)
+ if errors[:project_access].any?
+ error!(errors[:project_access], 422)
+ end
+ not_found!
+ end
+ end
+
+ # Get a project repository branches
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/branches
+ get ":id/repository/branches" do
+ present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # GET /projects/:id/repository/branches/:branch
+ get ":id/repository/branches/:branch" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found!("Branch does not exist") if @branch.nil?
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Protect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/protect
+ put ":id/repository/branches/:branch/protect" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found! unless @branch
+ protected = user_project.protected_branches.find_by_name(@branch.name)
+
+ unless protected
+ user_project.protected_branches.create(name: @branch.name)
+ end
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Unprotect a single branch
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # branch (required) - The name of the branch
+ # Example Request:
+ # PUT /projects/:id/repository/branches/:branch/unprotect
+ put ":id/repository/branches/:branch/unprotect" do
+ @branch = user_project.repo.heads.find { |item| item.name == params[:branch] }
+ not_found! unless @branch
+ protected = user_project.protected_branches.find_by_name(@branch.name)
+
+ if protected
+ protected.destroy
+ end
+
+ present @branch, with: Entities::RepoObject, project: user_project
+ end
+
+ # Get a project repository tags
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # Example Request:
+ # GET /projects/:id/repository/tags
+ get ":id/repository/tags" do
+ present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject
+ end
+
+ # Get a project repository commits
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/commits
+ get ":id/repository/commits" do
+ authorize! :download_code, user_project
+
+ page = (params[:page] || 0).to_i
+ per_page = (params[:per_page] || 20).to_i
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+
+ commits = user_project.repository.commits(ref, nil, per_page, page * per_page)
+ present commits, with: Entities::RepoCommit
+ end
+
+ # Get a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash or name of a repository branch or tag
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha
+ get ":id/repository/commits/:sha" do
+ authorize! :download_code, user_project
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! "Commit" unless commit
+ present commit, with: Entities::RepoCommit
+ end
+
+ # Get the diff for a specific commit of a project
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # Example Request:
+ # GET /projects/:id/repository/commits/:sha/diff
+ get ":id/repository/commits/:sha/diff" do
+ authorize! :download_code, user_project
+ sha = params[:sha]
+ result = CommitLoadContext.new(user_project, current_user, {id: sha}).execute
+ not_found! "Commit" unless result[:commit]
+ result[:commit].diffs
+ end
+
+ # Get a project repository tree
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used
+ # Example Request:
+ # GET /projects/:id/repository/tree
+ get ":id/repository/tree" do
+ authorize! :download_code, user_project
+
+ ref = params[:ref_name] || user_project.try(:default_branch) || 'master'
+ path = params[:path] || nil
+
+ commit = user_project.repository.commit(ref)
+ tree = Tree.new(user_project.repository, commit.id, ref, path)
+
+ trees = []
+
+ %w(trees blobs submodules).each do |type|
+ trees += tree.send(type).map { |t| { name: t.name, type: type.singularize, mode: t.mode, id: t.id } }
+ end
+
+ trees
+ end
+
+ # Get a raw file contents
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit or branch name
+ # filepath (required) - The path to the file to display
+ # Example Request:
+ # GET /projects/:id/repository/blobs/:sha
+ get [ ":id/repository/blobs/:sha", ":id/repository/commits/:sha/blob" ] do
+ authorize! :download_code, user_project
+ required_attributes! [:filepath]
+
+ ref = params[:sha]
+
+ repo = user_project.repository
+
+ commit = repo.commit(ref)
+ not_found! "Commit" unless commit
+
+ blob = Gitlab::Git::Blob.new(repo, commit.id, ref, params[:filepath])
+ not_found! "File" unless blob.exists?
+
+ env['api.format'] = :txt
+
+ content_type blob.mime_type
+ present blob.data
+ end
+ end
+ end
+end
+
diff --git a/lib/api/session.rb b/lib/api/session.rb
index b4050160ae4..cc646895914 100644
--- a/lib/api/session.rb
+++ b/lib/api/session.rb
@@ -1,20 +1,21 @@
-module Gitlab
+module API
# Users API
class Session < Grape::API
# Login to get token
#
+ # Parameters:
+ # login (*required) - user login
+ # email (*required) - user email
+ # password (required) - user password
+ #
# Example Request:
# POST /session
post "/session" do
- resource = User.find_for_database_authentication(email: params[:email])
-
- return unauthorized! unless resource
+ auth = Gitlab::Auth.new
+ user = auth.find(params[:email] || params[:login], params[:password])
- if resource.valid_password?(params[:password])
- present resource, with: Entities::UserLogin
- else
- unauthorized!
- end
+ return unauthorized! unless user
+ present user, with: Entities::UserLogin
end
end
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
new file mode 100644
index 00000000000..3e239c5afe7
--- /dev/null
+++ b/lib/api/system_hooks.rb
@@ -0,0 +1,70 @@
+module API
+ # Hooks API
+ class SystemHooks < Grape::API
+ before {
+ authenticate!
+ authenticated_as_admin!
+ }
+
+ resource :hooks do
+ # Get the list of system hooks
+ #
+ # Example Request:
+ # GET /hooks
+ get do
+ @hooks = SystemHook.all
+ present @hooks, with: Entities::Hook
+ end
+
+ # Create new system hook
+ #
+ # Parameters:
+ # url (required) - url for system hook
+ # Example Request
+ # POST /hooks
+ post do
+ attrs = attributes_for_keys [:url]
+ required_attributes! [:url]
+ @hook = SystemHook.new attrs
+ if @hook.save
+ present @hook, with: Entities::Hook
+ else
+ not_found!
+ end
+ end
+
+ # Test a hook
+ #
+ # Example Request
+ # GET /hooks/:id
+ get ":id" do
+ @hook = SystemHook.find(params[:id])
+ data = {
+ event_name: "project_create",
+ name: "Ruby",
+ path: "ruby",
+ project_id: 1,
+ owner_name: "Someone",
+ owner_email: "example@gitlabhq.com"
+ }
+ @hook.execute(data)
+ data
+ end
+
+ # Delete a hook. This is an idempotent function.
+ #
+ # Parameters:
+ # id (required) - ID of the hook
+ # Example Request:
+ # DELETE /hooks/:id
+ delete ":id" do
+ begin
+ @hook = SystemHook.find(params[:id])
+ @hook.destroy
+ rescue
+ # SystemHook raises an Error if no hook with id found
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7ea90c75e9e..00dc2311ffd 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,4 +1,4 @@
-module Gitlab
+module API
# Users API
class Users < Grape::API
before { authenticate! }
@@ -9,7 +9,10 @@ module Gitlab
# Example Request:
# GET /users
get do
- @users = paginate User
+ @users = User.scoped
+ @users = @users.active if params[:active].present?
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = paginate @users
present @users, with: Entities::User
end
@@ -41,8 +44,9 @@ module Gitlab
# POST /users
post do
authenticated_as_admin!
+ required_attributes! [:email, :password, :name, :username]
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
- user = User.new attrs, as: :admin
+ user = User.build_user(attrs, as: :admin)
if user.save
present user, with: Entities::User
else
@@ -59,7 +63,7 @@ module Gitlab
# skype - Skype ID
# linkedin - Linkedin
# twitter - Twitter account
- # projects_limit - Limit projects wich user can create
+ # projects_limit - Limit projects each user can create
# extern_uid - External authentication provider UID
# provider - External provider
# bio - Bio
@@ -67,16 +71,38 @@ module Gitlab
# PUT /users/:id
put ":id" do
authenticated_as_admin!
+
attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio]
- user = User.find_by_id(params[:id])
+ user = User.find(params[:id])
+ not_found!("User not found") unless user
- if user && user.update_attributes(attrs)
+ if user.update_attributes(attrs)
present user, with: Entities::User
else
not_found!
end
end
+ # Add ssh key to a specified user. Only available to admin users.
+ #
+ # Parameters:
+ # id (required) - The ID of a user
+ # key (required) - New SSH Key
+ # title (required) - New SSH Key's title
+ # Example Request:
+ # POST /users/:id/keys
+ post ":id/keys" do
+ authenticated_as_admin!
+ user = User.find(params[:id])
+ attrs = attributes_for_keys [:title, :key]
+ key = user.keys.new attrs
+ if key.save
+ present key, with: Entities::SSHKey
+ else
+ not_found!
+ end
+ end
+
# Delete user. Available only for admin
#
# Example Request:
@@ -99,7 +125,7 @@ module Gitlab
# Example Request:
# GET /user
get do
- present @current_user, with: Entities::User
+ present @current_user, with: Entities::UserLogin
end
# Get currently authenticated user's keys
@@ -127,6 +153,8 @@ module Gitlab
# Example Request:
# POST /user/keys
post "keys" do
+ required_attributes! [:title, :key]
+
attrs = attributes_for_keys [:title, :key]
key = current_user.keys.new attrs
if key.save
@@ -136,15 +164,18 @@ module Gitlab
end
end
- # Delete existed ssh key of currently authenticated user
+ # Delete existing ssh key of currently authenticated user
#
# Parameters:
# id (required) - SSH Key ID
# Example Request:
# DELETE /user/keys/:id
delete "keys/:id" do
- key = current_user.keys.find params[:id]
- key.delete
+ begin
+ key = current_user.keys.find params[:id]
+ key.destroy
+ rescue
+ end
end
end
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
new file mode 100644
index 00000000000..c4fb2e2e159
--- /dev/null
+++ b/lib/backup/database.rb
@@ -0,0 +1,58 @@
+require 'yaml'
+
+module Backup
+ class Database
+ attr_reader :config, :db_dir
+
+ def initialize
+ @config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
+ @db_dir = File.join(Gitlab.config.backup.path, 'db')
+ FileUtils.mkdir_p(@db_dir) unless Dir.exists?(@db_dir)
+ end
+
+ def dump
+ case config["adapter"]
+ when /^mysql/ then
+ system("mysqldump #{mysql_args} #{config['database']} > #{db_file_name}")
+ when "postgresql" then
+ pg_env
+ system("pg_dump #{config['database']} > #{db_file_name}")
+ end
+ end
+
+ def restore
+ case config["adapter"]
+ when /^mysql/ then
+ system("mysql #{mysql_args} #{config['database']} < #{db_file_name}")
+ when "postgresql" then
+ pg_env
+ system("psql #{config['database']} -f #{db_file_name}")
+ end
+ end
+
+ protected
+
+ def db_file_name
+ File.join(db_dir, 'database.sql')
+ end
+
+ def mysql_args
+ args = {
+ 'host' => '--host',
+ 'port' => '--port',
+ 'socket' => '--socket',
+ 'username' => '--user',
+ 'encoding' => '--default-character-set',
+ 'password' => '--password'
+ }
+ args.map { |opt, arg| "#{arg}='#{config[opt]}'" if config[opt] }.compact.join(' ')
+ end
+
+ def pg_env
+ ENV['PGUSER'] = config["username"] if config["username"]
+ ENV['PGHOST'] = config["host"] if config["host"]
+ ENV['PGPORT'] = config["port"].to_s if config["port"]
+ ENV['PGPASSWORD'] = config["password"].to_s if config["password"]
+ end
+ end
+end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
new file mode 100644
index 00000000000..258a0fb2589
--- /dev/null
+++ b/lib/backup/manager.rb
@@ -0,0 +1,106 @@
+module Backup
+ class Manager
+ def pack
+ # saving additional informations
+ s = {}
+ s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
+ s[:backup_created_at] = Time.now
+ s[:gitlab_version] = %x{git rev-parse HEAD}.gsub(/\n/,"")
+ s[:tar_version] = %x{tar --version | head -1}.gsub(/\n/,"")
+
+ Dir.chdir(Gitlab.config.backup.path)
+
+ File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file|
+ file << s.to_yaml.gsub(/^---\n/,'')
+ end
+
+ # create archive
+ print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... "
+ if Kernel.system("tar -cf #{s[:backup_created_at].to_i}_gitlab_backup.tar repositories/ db/ uploads/ backup_information.yml")
+ puts "done".green
+ else
+ puts "failed".red
+ end
+ end
+
+ def cleanup
+ print "Deleting tmp directories ... "
+ if Kernel.system("rm -rf repositories/ db/ uploads/ backup_information.yml")
+ puts "done".green
+ else
+ puts "failed".red
+ end
+ end
+
+ def remove_old
+ # delete backups
+ print "Deleting old backups ... "
+ keep_time = Gitlab.config.backup.keep_time.to_i
+ path = Gitlab.config.backup.path
+
+ if keep_time > 0
+ removed = 0
+ file_list = Dir.glob(Rails.root.join(path, "*_gitlab_backup.tar"))
+ file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ }
+ file_list.sort.each do |timestamp|
+ if Time.at(timestamp) < (Time.now - keep_time)
+ if system("rm #{timestamp}_gitlab_backup.tar")
+ removed += 1
+ end
+ end
+ end
+ puts "done. (#{removed} removed)".green
+ else
+ puts "skipping".yellow
+ end
+ end
+
+ def unpack
+ Dir.chdir(Gitlab.config.backup.path)
+
+ # check for existing backups in the backup dir
+ file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
+ puts "no backups found" if file_list.count == 0
+ if file_list.count > 1 && ENV["BACKUP"].nil?
+ puts "Found more than one backup, please specify which one you want to restore:"
+ puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
+ exit 1
+ end
+
+ tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
+
+ unless File.exists?(tar_file)
+ puts "The specified backup doesn't exist!"
+ exit 1
+ end
+
+ print "Unpacking backup ... "
+ unless Kernel.system("tar -xf #{tar_file}")
+ puts "failed".red
+ exit 1
+ else
+ puts "done".green
+ end
+
+ settings = YAML.load_file("backup_information.yml")
+ ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
+
+ # backups directory is not always sub of Rails root and able to execute the git rev-parse below
+ begin
+ Dir.chdir(Rails.root)
+
+ # restoring mismatching backups can lead to unexpected problems
+ if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/, "")
+ puts "GitLab version mismatch:".red
+ puts " Your current HEAD differs from the HEAD in the backup!".red
+ puts " Please switch to the following revision and try again:".red
+ puts " revision: #{settings[:gitlab_version]}".red
+ exit 1
+ end
+ ensure
+ # chdir back to original intended dir
+ Dir.chdir(Gitlab.config.backup.path)
+ end
+ end
+ end
+end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
new file mode 100644
index 00000000000..c5e3d049fd7
--- /dev/null
+++ b/lib/backup/repository.rb
@@ -0,0 +1,105 @@
+require 'yaml'
+
+module Backup
+ class Repository
+ attr_reader :repos_path
+
+ def dump
+ prepare
+
+ Project.find_each(batch_size: 1000) do |project|
+ print " * #{project.path_with_namespace} ... "
+
+ if project.empty_repo?
+ puts "[SKIPPED]".cyan
+ next
+ end
+
+ # Create namespace dir if missing
+ FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
+
+ if system("cd #{path_to_repo(project)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(project)} --all > /dev/null 2>&1")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ wiki = GollumWiki.new(project)
+
+ if File.exists?(path_to_repo(wiki))
+ print " * #{wiki.path_with_namespace} ... "
+ if system("cd #{path_to_repo(wiki)} > /dev/null 2>&1 && git bundle create #{path_to_bundle(wiki)} --all > /dev/null 2>&1")
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ end
+ end
+ end
+ end
+
+ def restore
+ if File.exists?(repos_path)
+ # Move repos dir to 'repositories.old' dir
+ bk_repos_path = File.join(repos_path, '..', 'repositories.old.' + Time.now.to_i.to_s)
+ FileUtils.mv(repos_path, bk_repos_path)
+ end
+
+ FileUtils.mkdir_p(repos_path)
+
+ Project.find_each(batch_size: 1000) do |project|
+ print "#{project.path_with_namespace} ... "
+
+ project.namespace.ensure_dir_exist if project.namespace
+
+ if system("git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)} > /dev/null 2>&1")
+ puts "[DONE]".green
+ else
+ puts "[FAILED]".red
+ end
+
+ wiki = GollumWiki.new(project)
+
+ if File.exists?(path_to_bundle(wiki))
+ print " * #{wiki.path_with_namespace} ... "
+ if system("git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)} > /dev/null 2>&1")
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ end
+ end
+ end
+
+ print 'Put GitLab hooks in repositories dirs'.yellow
+ gitlab_shell_user_home = File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
+ if system("#{gitlab_shell_user_home}/gitlab-shell/support/rewrite-hooks.sh #{Gitlab.config.gitlab_shell.repos_path}")
+ puts " [DONE]".green
+ else
+ puts " [FAILED]".red
+ end
+
+ end
+
+ protected
+
+ def path_to_repo(project)
+ File.join(repos_path, project.path_with_namespace + '.git')
+ end
+
+ def path_to_bundle(project)
+ File.join(backup_repos_path, project.path_with_namespace + ".bundle")
+ end
+
+ def repos_path
+ Gitlab.config.gitlab_shell.repos_path
+ end
+
+ def backup_repos_path
+ File.join(Gitlab.config.backup.path, "repositories")
+ end
+
+ def prepare
+ FileUtils.rm_rf(backup_repos_path)
+ FileUtils.mkdir_p(backup_repos_path)
+ end
+ end
+end
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
new file mode 100644
index 00000000000..462d3f1e274
--- /dev/null
+++ b/lib/backup/uploads.rb
@@ -0,0 +1,29 @@
+module Backup
+ class Uploads
+ attr_reader :app_uploads_dir, :backup_uploads_dir, :backup_dir
+
+ def initialize
+ @app_uploads_dir = Rails.root.join('public', 'uploads')
+ @backup_dir = Gitlab.config.backup.path
+ @backup_uploads_dir = File.join(Gitlab.config.backup.path, 'uploads')
+ end
+
+ # Copy uploads from public/uploads to backup/uploads
+ def dump
+ FileUtils.mkdir_p(backup_uploads_dir)
+ FileUtils.cp_r(app_uploads_dir, backup_dir)
+ end
+
+ def restore
+ backup_existing_uploads_dir
+
+ FileUtils.cp_r(backup_uploads_dir, app_uploads_dir)
+ end
+
+ def backup_existing_uploads_dir
+ if File.exists?(app_uploads_dir)
+ FileUtils.mv(app_uploads_dir, Rails.root.join('public', "uploads.#{Time.now.to_i}"))
+ end
+ end
+ end
+end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index fb595e18b24..53bc079296a 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -8,7 +8,7 @@ module ExtractsPath
included do
if respond_to?(:before_filter)
- before_filter :assign_ref_vars, only: [:show]
+ before_filter :assign_ref_vars
end
end
@@ -33,7 +33,7 @@ module ExtractsPath
# extract_ref("v2.0.0/README.md")
# # => ['v2.0.0', 'README.md']
#
- # extract_ref('/gitlab/vagrant/tree/master/app/models/project.rb')
+ # extract_ref('master/app/models/project.rb')
# # => ['master', 'app/models/project.rb']
#
# extract_ref('issues/1234/app/models/project.rb')
@@ -45,22 +45,12 @@ module ExtractsPath
#
# Returns an Array where the first value is the tree-ish and the second is the
# path
- def extract_ref(input)
+ def extract_ref(id)
pair = ['', '']
return pair unless @project
- # Remove relative_url_root from path
- input.gsub!(/^#{Gitlab.config.gitlab.relative_url_root}/, "")
- # Remove project, actions and all other staff from path
- input.gsub!(/^\/#{Regexp.escape(@project.path_with_namespace)}/, "")
- input.gsub!(/^\/(tree|commits|blame|blob|refs|graph)\//, "") # remove actions
- input.gsub!(/\?.*$/, "") # remove stamps suffix
- input.gsub!(/.atom$/, "") # remove rss feed
- input.gsub!(/.json$/, "") # remove json suffix
- input.gsub!(/\/edit$/, "") # remove edit route part
-
- if input.match(/^([[:alnum:]]{40})(.+)/)
+ if id.match(/^([[:alnum:]]{40})(.+)/)
# If the ref appears to be a SHA, we're done, just split the string
pair = $~.captures
else
@@ -68,7 +58,6 @@ module ExtractsPath
# branches and tags
# Append a trailing slash if we only get a ref and no file path
- id = input
id += '/' unless id.ends_with?('/')
valid_refs = @project.repository.ref_names
@@ -96,8 +85,8 @@ module ExtractsPath
# - @id - A string representing the joined ref and path
# - @ref - A string representing the ref (e.g., the branch, tag, or commit SHA)
# - @path - A string representing the filesystem path
- # - @commit - A CommitDecorator representing the commit from the given ref
- # - @tree - A TreeDecorator representing the tree at the given ref/path
+ # - @commit - A Commit representing the commit from the given ref
+ # - @tree - A Tree representing the tree at the given ref/path
#
# If the :id parameter appears to be requesting a specific response format,
# that will be handled as well.
@@ -105,28 +94,33 @@ module ExtractsPath
# 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
- # Handle formats embedded in the id
- if params[:id].ends_with?('.atom')
- params[:id].gsub!(/\.atom$/, '')
- request.format = :atom
+ # assign allowed options
+ allowed_options = ["filter_ref", "extended_sha1"]
+ @options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
+ @options = HashWithIndifferentAccess.new(@options)
+
+ @id = get_id
+ @ref, @path = extract_ref(@id)
+ @repo = @project.repository
+ if @options[:extended_sha1].blank?
+ @commit = @repo.commit(@ref)
+ else
+ @commit = @repo.commit(@options[:extended_sha1])
end
+ @tree = Tree.new(@repo, @commit.id, @ref, @path)
+ @hex_path = Digest::SHA1.hexdigest(@path)
+ @logs_path = logs_file_project_ref_path(@project, @ref, @path)
- path = CGI::unescape(request.fullpath.dup)
-
- @ref, @path = extract_ref(path)
-
- @id = File.join(@ref, @path)
-
- # It is used "@project.repository.commits(@ref, @path, 1, 0)",
- # because "@project.repository.commit(@ref)" returns wrong commit when @ref is tag name.
- commits = @project.repository.commits(@ref, @path, 1, 0)
- @commit = CommitDecorator.decorate(commits.first)
+ raise InvalidPathError unless @tree.exists?
+ rescue RuntimeError, NoMethodError, InvalidPathError
+ not_found!
+ end
- @tree = Tree.new(@commit.tree, @ref, @path)
- @tree = TreeDecorator.new(@tree)
+ private
- raise InvalidPathError if @tree.invalid?
- rescue NoMethodError, InvalidPathError
- not_found!
+ def get_id
+ id = params[:id] || params[:ref]
+ id += "/" + params[:path] unless params[:path].blank?
+ id
end
end
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
new file mode 100644
index 00000000000..87f9cfab608
--- /dev/null
+++ b/lib/gitlab/access.rb
@@ -0,0 +1,52 @@
+# Gitlab::Access module
+#
+# Define allowed roles that can be used
+# in GitLab code to determine authorization level
+#
+module Gitlab
+ module Access
+ GUEST = 10
+ REPORTER = 20
+ DEVELOPER = 30
+ MASTER = 40
+ OWNER = 50
+
+ class << self
+ def values
+ options.values
+ end
+
+ def options
+ {
+ "Guest" => GUEST,
+ "Reporter" => REPORTER,
+ "Developer" => DEVELOPER,
+ "Master" => MASTER,
+ }
+ end
+
+ def options_with_owner
+ options.merge(
+ "Owner" => OWNER
+ )
+ end
+
+ def sym_options
+ {
+ guest: GUEST,
+ reporter: REPORTER,
+ developer: DEVELOPER,
+ master: MASTER,
+ }
+ end
+ end
+
+ def human_access
+ Gitlab::Access.options_with_owner.key(access_field)
+ end
+
+ def owner?
+ access_field == OWNER
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index d0e792befbb..0f196297477 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,72 +1,24 @@
module Gitlab
class Auth
- def find_for_ldap_auth(auth, signed_in_resource = nil)
- uid = auth.info.uid
- provider = auth.provider
- email = auth.info.email.downcase unless auth.info.email.nil?
- raise OmniAuth::Error, "LDAP accounts must provide an uid and email address" if uid.nil? or email.nil?
+ def find(login, password)
+ user = User.find_by_email(login) || User.find_by_username(login)
- if @user = User.find_by_extern_uid_and_provider(uid, provider)
- @user
- elsif @user = User.find_by_email(email)
- log.info "Updating legacy LDAP user #{email} with extern_uid => #{uid}"
- @user.update_attributes(:extern_uid => uid, :provider => provider)
- @user
- else
- create_from_omniauth(auth, true)
- end
- end
+ if user.nil? || user.ldap_user?
+ # Second chance - try LDAP authentication
+ return nil unless ldap_conf.enabled
- def create_from_omniauth(auth, ldap = false)
- provider = auth.provider
- uid = auth.info.uid || auth.uid
- uid = uid.to_s.force_encoding("utf-8")
- name = auth.info.name.to_s.force_encoding("utf-8")
- email = auth.info.email.to_s.downcase unless auth.info.email.nil?
-
- ldap_prefix = ldap ? '(LDAP) ' : ''
- raise OmniAuth::Error, "#{ldap_prefix}#{provider} does not provide an email"\
- " address" if auth.info.email.blank?
-
- log.info "#{ldap_prefix}Creating user from #{provider} login"\
- " {uid => #{uid}, name => #{name}, email => #{email}}"
- password = Devise.friendly_token[0, 8].downcase
- @user = User.new({
- extern_uid: uid,
- provider: provider,
- name: name,
- username: email.match(/^[^@]*/)[0],
- email: email,
- password: password,
- password_confirmation: password,
- projects_limit: Gitlab.config.gitlab.default_projects_limit,
- }, as: :admin)
- if Gitlab.config.omniauth['block_auto_created_users'] && !ldap
- @user.blocked = true
- end
- @user.save!
- @user
- end
-
- def find_or_new_for_omniauth(auth)
- provider, uid = auth.provider, auth.uid
- email = auth.info.email.downcase unless auth.info.email.nil?
-
- if @user = User.find_by_provider_and_extern_uid(provider, uid)
- @user
- elsif @user = User.find_by_email(email)
- @user.update_attributes(:extern_uid => uid, :provider => provider)
- @user
+ Gitlab::LDAP::User.authenticate(login, password)
else
- if Gitlab.config.omniauth['allow_single_sign_on']
- @user = create_from_omniauth(auth)
- @user
- end
+ user if user.valid_password?(password)
end
end
def log
Gitlab::AppLogger
end
+
+ def ldap_conf
+ @ldap_conf ||= Gitlab.config.ldap
+ end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index abbee6132d3..c522e0a413b 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -1,8 +1,11 @@
require_relative 'shell_env'
+require_relative 'grack_helpers'
module Grack
class Auth < Rack::Auth::Basic
- attr_accessor :user, :project
+ include Helpers
+
+ attr_accessor :user, :project, :ref, :env
def call(env)
@env = env
@@ -10,52 +13,73 @@ module Grack
@auth = Request.new(env)
# Need this patch due to the rails mount
- @env['PATH_INFO'] = @request.path
- @env['SCRIPT_NAME'] = ""
- return render_not_found unless project
- return unauthorized unless project.public || @auth.provided?
- return bad_request if @auth.provided? && !@auth.basic?
-
- if valid?
- if @auth.provided?
- @env['REMOTE_USER'] = @auth.username
- end
- return @app.call(env)
+ # Need this if under RELATIVE_URL_ROOT
+ unless Gitlab.config.gitlab.relative_url_root.empty?
+ # If website is mounted using relative_url_root need to remove it first
+ @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
else
- unauthorized
+ @env['PATH_INFO'] = @request.path
end
+
+ @env['SCRIPT_NAME'] = ""
+
+ auth!
end
- def valid?
+ private
+
+ def auth!
+ return render_not_found unless project
+
if @auth.provided?
+ return bad_request unless @auth.basic?
+
# Authentication with username and password
login, password = @auth.credentials
- self.user = User.find_by_email(login) || User.find_by_username(login)
- return false unless user.try(:valid_password?, password)
- Gitlab::ShellEnv.set_env(user)
+ @user = authenticate_user(login, password)
+
+ if @user
+ Gitlab::ShellEnv.set_env(@user)
+ @env['REMOTE_USER'] = @auth.username
+ else
+ return unauthorized
+ end
+
+ else
+ return unauthorized unless project.public
end
+ if authorized_git_request?
+ @app.call(env)
+ else
+ unauthorized
+ end
+ end
+
+ def authorized_git_request?
# Git upload and receive
if @request.get?
- validate_get_request
+ authorize_request(@request.params['service'])
elsif @request.post?
- validate_post_request
+ authorize_request(File.basename(@request.path))
else
false
end
end
- def validate_get_request
- project.public || can?(user, :download_code, project)
+ def authenticate_user(login, password)
+ auth = Gitlab::Auth.new
+ auth.find(login, password)
end
- def validate_post_request
- if @request.path_info.end_with?('git-upload-pack')
+ def authorize_request(service)
+ case service
+ when 'git-upload-pack'
project.public || can?(user, :download_code, project)
- elsif @request.path_info.end_with?('git-receive-pack')
- action = if project.protected_branch?(current_ref)
+ when'git-receive-pack'
+ action = if project.protected_branch?(ref)
:push_code_to_protected_branches
else
:push_code
@@ -67,45 +91,24 @@ module Grack
end
end
- def can?(object, action, subject)
- abilities.allowed?(object, action, subject)
- end
-
- def current_ref
- if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
- input = Zlib::GzipReader.new(@request.body).read
- else
- input = @request.body.read
- end
- # Need to reset seek point
- @request.body.rewind
- /refs\/heads\/([\w\.-]+)/.match(input).to_a.last
- end
-
def project
- unless instance_variable_defined? :@project
- # Find project by PATH_INFO from env
- if m = /^\/([\w\.\/-]+)\.git/.match(@request.path_info).to_a
- @project = Project.find_with_namespace(m.last)
- end
- end
- return @project
+ @project ||= project_by_path(@request.path_info)
end
- PLAIN_TYPE = {"Content-Type" => "text/plain"}
-
- def render_not_found
- [404, PLAIN_TYPE, ["Not Found"]]
+ def ref
+ @ref ||= parse_ref
end
- protected
+ def parse_ref
+ input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
+ Zlib::GzipReader.new(@request.body).read
+ else
+ @request.body.read
+ end
- def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ # Need to reset seek point
+ @request.body.rewind
+ /refs\/heads\/([\/\w\.-]+)/n.match(input.force_encoding('ascii-8bit')).to_a.last
end
- end# Auth
-end# Grack
+ end
+end
diff --git a/lib/gitlab/backend/grack_helpers.rb b/lib/gitlab/backend/grack_helpers.rb
new file mode 100644
index 00000000000..5ac9e9f325b
--- /dev/null
+++ b/lib/gitlab/backend/grack_helpers.rb
@@ -0,0 +1,28 @@
+module Grack
+ module Helpers
+ def project_by_path(path)
+ if m = /^\/([\w\.\/-]+)\.git/.match(path).to_a
+ path_with_namespace = m.last
+ path_with_namespace.gsub!(/\.wiki$/, '')
+
+ Project.find_with_namespace(path_with_namespace)
+ end
+ end
+
+ def render_not_found
+ [404, {"Content-Type" => "text/plain"}, ["Not Found"]]
+ end
+
+ def can?(object, action, subject)
+ abilities.allowed?(object, action, subject)
+ end
+
+ def abilities
+ @abilities ||= begin
+ abilities = Six.new
+ abilities << Ability
+ abilities
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index b7b92e86a87..c819ce56ac9 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -10,7 +10,7 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system("/home/git/gitlab-shell/bin/gitlab-projects add-project #{name}.git")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "add-project", "#{name}.git"
end
# Import repository
@@ -21,7 +21,43 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system("/home/git/gitlab-shell/bin/gitlab-projects import-project #{name}.git #{url}")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "import-project", "#{name}.git", url
+ end
+
+ # Move repository
+ #
+ # path - project path with namespace
+ # new_path - new project path with namespace
+ #
+ # Ex.
+ # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
+ #
+ def mv_repository(path, new_path)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ end
+
+ # Update HEAD for repository
+ #
+ # path - project path with namespace
+ # branch - repository branch name
+ #
+ # Ex.
+ # update_repository_head("gitlab/gitlab-ci", "3-1-stable")
+ #
+ def update_repository_head(path, branch)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ end
+
+ # Fork repository to new namespace
+ #
+ # path - project path with namespace
+ # fork_namespace - namespace for forked project
+ #
+ # Ex.
+ # fork_repository("gitlab/gitlab-ci", "randx")
+ #
+ def fork_repository(path, fork_namespace)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
end
# Remove repository from file system
@@ -32,7 +68,57 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system("/home/git/gitlab-shell/bin/gitlab-projects rm-project #{name}.git")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-project", "#{name}.git"
+ end
+
+ # Add repository branch from passed ref
+ #
+ # path - project path with namespace
+ # branch_name - new branch name
+ # ref - HEAD for new branch
+ #
+ # Ex.
+ # add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
+ #
+ def add_branch(path, branch_name, ref)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ end
+
+ # Remove repository branch
+ #
+ # path - project path with namespace
+ # branch_name - branch name to remove
+ #
+ # Ex.
+ # rm_branch("gitlab/gitlab-ci", "4-0-stable")
+ #
+ def rm_branch(path, branch_name)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ end
+
+ # Add repository tag from passed ref
+ #
+ # path - project path with namespace
+ # tag_name - new tag name
+ # ref - HEAD for new tag
+ #
+ # Ex.
+ # add_tag("gitlab/gitlab-ci", "v4.0", "master")
+ #
+ def add_tag(path, tag_name, ref)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref
+ end
+
+ # Remove repository tag
+ #
+ # path - project path with namespace
+ # tag_name - tag name to remove
+ #
+ # Ex.
+ # rm_tag("gitlab/gitlab-ci", "v4.0")
+ #
+ def rm_tag(path, tag_name)
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
end
# Add new key to gitlab-shell
@@ -41,7 +127,7 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system("/home/git/gitlab-shell/bin/gitlab-keys add-key #{key_id} \"#{key_content}\"")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "add-key", key_id, key_content
end
# Remove ssh key from gitlab shell
@@ -50,11 +136,84 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system("/home/git/gitlab-shell/bin/gitlab-keys rm-key #{key_id} \"#{key_content}\"")
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "rm-key", key_id, key_content
+ end
+
+ # Remove all ssh keys from gitlab shell
+ #
+ # Ex.
+ # remove_all_keys
+ #
+ def remove_all_keys
+ system "#{gitlab_shell_user_home}/gitlab-shell/bin/gitlab-keys", "clear"
+ end
+
+ # Add empty directory for storing repositories
+ #
+ # Ex.
+ # add_namespace("gitlab")
+ #
+ def add_namespace(name)
+ FileUtils.mkdir(full_path(name), mode: 0770) unless exists?(name)
+ end
+
+ # Remove directory from repositories storage
+ # Every repository inside this directory will be removed too
+ #
+ # Ex.
+ # rm_namespace("gitlab")
+ #
+ def rm_namespace(name)
+ FileUtils.rm_r(full_path(name), force: true)
+ end
+
+ # Move namespace directory inside repositories storage
+ #
+ # Ex.
+ # mv_namespace("gitlab", "gitlabhq")
+ #
+ def mv_namespace(old_name, new_name)
+ return false if exists?(new_name) || !exists?(old_name)
+
+ FileUtils.mv(full_path(old_name), full_path(new_name))
+ end
+
+ # Remove GitLab Satellites for provided path (namespace or repo dir)
+ #
+ # Ex.
+ # rm_satellites("gitlab")
+ #
+ # rm_satellites("gitlab/gitlab-ci.git")
+ #
+ def rm_satellites(path)
+ raise ArgumentError.new("Path can't be blank") if path.blank?
+
+ satellites_path = File.join(Gitlab.config.satellites.path, path)
+ FileUtils.rm_r(satellites_path, force: true)
end
def url_to_repo path
Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git"
end
+
+ protected
+
+ def gitlab_shell_user_home
+ File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
+ end
+
+ def repos_path
+ Gitlab.config.gitlab_shell.repos_path
+ end
+
+ def full_path(dir_name)
+ raise ArgumentError.new("Directory name can't be blank") if dir_name.blank?
+
+ File.join(repos_path, dir_name)
+ end
+
+ def exists?(dir_name)
+ File.exists?(full_path(dir_name))
+ end
end
end
diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/backend/shell_adapter.rb
new file mode 100644
index 00000000000..f247f4593d7
--- /dev/null
+++ b/lib/gitlab/backend/shell_adapter.rb
@@ -0,0 +1,12 @@
+# == GitLab Shell mixin
+#
+# Provide a shortcut to Gitlab::Shell instance by gitlab_shell
+#
+module Gitlab
+ module ShellAdapter
+ def gitlab_shell
+ Gitlab::Shell.new
+ end
+ end
+end
+
diff --git a/lib/gitlab/backend/shell_env.rb b/lib/gitlab/backend/shell_env.rb
index 15721875093..044afb27f3f 100644
--- a/lib/gitlab/backend/shell_env.rb
+++ b/lib/gitlab/backend/shell_env.rb
@@ -1,6 +1,6 @@
module Gitlab
# This module provide 2 methods
- # to set specific ENV variabled for GitLab Shell
+ # to set specific ENV variables for GitLab Shell
module ShellEnv
extend self
diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb
new file mode 100644
index 00000000000..2f9091e07df
--- /dev/null
+++ b/lib/gitlab/blacklist.rb
@@ -0,0 +1,9 @@
+module Gitlab
+ module Blacklist
+ extend self
+
+ def path
+ %w(admin dashboard groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes)
+ end
+ end
+end
diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb
new file mode 100644
index 00000000000..fb27280c4a4
--- /dev/null
+++ b/lib/gitlab/diff_parser.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ class DiffParser
+ include Enumerable
+
+ attr_reader :lines, :new_path
+
+ def initialize(diff)
+ @lines = diff.diff.lines.to_a
+ @new_path = diff.new_path
+ end
+
+ def each
+ line_old = 1
+ line_new = 1
+ type = nil
+
+ lines_arr = ::Gitlab::InlineDiff.processing lines
+ lines_arr.each do |line|
+ raw_line = line.dup
+
+ next if line.match(/^\-\-\- \/dev\/null/)
+ next if line.match(/^\+\+\+ \/dev\/null/)
+ next if line.match(/^\-\-\- a/)
+ next if line.match(/^\+\+\+ b/)
+
+ full_line = html_escape(line.gsub(/\n/, ''))
+ full_line = ::Gitlab::InlineDiff.replace_markers full_line
+
+ if line.match(/^@@ -/)
+ type = "match"
+
+ line_old = line.match(/\-[0-9]*/)[0].to_i.abs rescue 0
+ line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0
+
+ next if line_old == 1 && line_new == 1 #top of file
+ yield(full_line, type, nil, nil, nil)
+ next
+ else
+ type = identification_type(line)
+ line_code = generate_line_code(new_path, line_new, line_old)
+ yield(full_line, type, line_code, line_new, line_old, raw_line)
+ end
+
+
+ if line[0] == "+"
+ line_new += 1
+ elsif line[0] == "-"
+ line_old += 1
+ else
+ line_new += 1
+ line_old += 1
+ end
+ end
+ end
+
+ private
+
+ def identification_type(line)
+ if line[0] == "+"
+ "new"
+ elsif line[0] == "-"
+ "old"
+ else
+ nil
+ end
+ end
+
+ def generate_line_code(path, line_new, line_old)
+ "#{Digest::SHA1.hexdigest(path)}_#{line_old}_#{line_new}"
+ end
+
+ def html_escape str
+ replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
+ str.gsub(/[&"'><]/, replacements)
+ end
+ end
+end
diff --git a/lib/gitlab/git_stats.rb b/lib/gitlab/git_stats.rb
deleted file mode 100644
index 855bffb5dde..00000000000
--- a/lib/gitlab/git_stats.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-module Gitlab
- class GitStats
- attr_accessor :repo, :ref
-
- def initialize repo, ref
- @repo, @ref = repo, ref
- end
-
- def authors
- @authors ||= collect_authors
- end
-
- def commits_count
- @commits_count ||= repo.commit_count(ref)
- end
-
- def files_count
- args = [ref, '-r', '--name-only' ]
- repo.git.run(nil, 'ls-tree', nil, {}, args).split("\n").count
- end
-
- def authors_count
- authors.size
- end
-
- def graph
- @graph ||= build_graph
- end
-
- protected
-
- def collect_authors
- shortlog = repo.git.shortlog({e: true, s: true }, ref)
-
- authors = []
-
- lines = shortlog.split("\n")
-
- lines.each do |line|
- data = line.split("\t")
- commits = data.first
- author = Grit::Actor.from_string(data.last)
-
- authors << OpenStruct.new(
- name: author.name,
- email: author.email,
- commits: commits.to_i
- )
- end
-
- authors.sort_by(&:commits).reverse
- end
-
- def build_graph n = 4
- from, to = (Date.today - n.weeks), Date.today
- args = ['--all', "--since=#{from.to_s(:date)}", '--format=%ad' ]
- rev_list = repo.git.run(nil, 'rev-list', nil, {}, args).split("\n")
-
- commits_dates = rev_list.values_at(* rev_list.each_index.select {|i| i.odd?})
- commits_dates = commits_dates.map { |date_str| Time.parse(date_str).to_date.to_s(:date) }
-
- commits_per_day = from.upto(to).map do |day|
- commits_dates.count(day.to_date.to_s(:date))
- end
-
- OpenStruct.new(
- labels: from.upto(to).map { |day| day.stamp('Aug 23') },
- commits: commits_per_day,
- weeks: n
- )
- end
- end
-end
diff --git a/lib/gitlab/graph/commit.rb b/lib/gitlab/graph/commit.rb
deleted file mode 100644
index 13c8ebc9952..00000000000
--- a/lib/gitlab/graph/commit.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require "grit"
-
-module Gitlab
- module Graph
- class Commit
- include ActionView::Helpers::TagHelper
-
- attr_accessor :time, :space, :refs, :parent_spaces
-
- def initialize(commit)
- @_commit = commit
- @time = -1
- @space = 0
- @parent_spaces = []
- end
-
- def method_missing(m, *args, &block)
- @_commit.send(m, *args, &block)
- end
-
- def to_graph_hash
- h = {}
- h[:parents] = self.parents.collect do |p|
- [p.id,0,0]
- end
- h[:author] = {
- name: author.name,
- email: author.email
- }
- h[:time] = time
- h[:space] = space
- h[:parent_spaces] = parent_spaces
- h[:refs] = refs.collect{|r|r.name}.join(" ") unless refs.nil?
- h[:id] = sha
- h[:date] = date
- h[:message] = message
- h
- end
-
- def add_refs(ref_cache, repo)
- if ref_cache.empty?
- repo.refs.each do |ref|
- ref_cache[ref.commit.id] ||= []
- ref_cache[ref.commit.id] << ref
- end
- end
- @refs = ref_cache[@_commit.id] if ref_cache.include?(@_commit.id)
- @refs ||= []
- end
- end
- end
-end
diff --git a/lib/gitlab/graph/json_builder.rb b/lib/gitlab/graph/json_builder.rb
deleted file mode 100644
index cc971a245a7..00000000000
--- a/lib/gitlab/graph/json_builder.rb
+++ /dev/null
@@ -1,268 +0,0 @@
-require "grit"
-
-module Gitlab
- module Graph
- class JsonBuilder
- attr_accessor :days, :commits, :ref_cache, :repo
-
- def self.max_count
- @max_count ||= 650
- end
-
- def initialize project, ref, commit
- @project = project
- @ref = ref
- @commit = commit
- @repo = project.repo
- @ref_cache = {}
-
- @commits = collect_commits
- @days = index_commits
- end
-
- def to_json(*args)
- {
- days: @days.compact.map { |d| [d.day, d.strftime("%b")] },
- commits: @commits.map(&:to_graph_hash)
- }.to_json(*args)
- end
-
- protected
-
- # Get commits from repository
- #
- def collect_commits
-
- @commits = Grit::Commit.find_all(repo, nil, {topo_order: true, max_count: self.class.max_count, skip: to_commit}).dup
-
- # Decorate with app/models/commit.rb
- @commits.map! { |commit| ::Commit.new(commit) }
-
- # Decorate with lib/gitlab/graph/commit.rb
- @commits.map! { |commit| Gitlab::Graph::Commit.new(commit) }
-
- # add refs to each commit
- @commits.each { |commit| commit.add_refs(ref_cache, repo) }
-
- @commits
- end
-
- # Method is adding time and space on the
- # list of commits. As well as returns date list
- # corelated with time set on commits.
- #
- # @param [Array<Graph::Commit>] commits to index
- #
- # @return [Array<TimeDate>] list of commit dates corelated with time on commits
- def index_commits
- days, times = [], []
- map = {}
-
- commits.reverse.each_with_index do |c,i|
- c.time = i
- days[i] = c.committed_date
- map[c.id] = c
- times[i] = c
- end
-
- @_reserved = {}
- days.each_index do |i|
- @_reserved[i] = []
- end
-
- commits_sort_by_ref.each do |commit|
- if map.include? commit.id then
- place_chain(map[commit.id], map)
- end
- end
-
- # find parent spaces for not overlap lines
- times.each do |c|
- c.parent_spaces.concat(find_free_parent_spaces(c, map, times))
- end
-
- days
- end
-
- # Skip count that the target commit is displayed in center.
- def to_commit
- commits = Grit::Commit.find_all(repo, nil, {topo_order: true})
- commit_index = commits.index do |c|
- c.id == @commit.id
- end
-
- if commit_index && (self.class.max_count / 2 < commit_index) then
- # get max index that commit is displayed in the center.
- commit_index - self.class.max_count / 2
- else
- 0
- end
- end
-
- def commits_sort_by_ref
- commits.sort do |a,b|
- if include_ref?(a)
- -1
- elsif include_ref?(b)
- 1
- else
- b.committed_date <=> a.committed_date
- end
- end
- end
-
- def include_ref?(commit)
- heads = commit.refs.select do |ref|
- ref.is_a?(Grit::Head) or ref.is_a?(Grit::Remote) or ref.is_a?(Grit::Tag)
- end
-
- heads.map! do |head|
- head.name
- end
-
- heads.include?(@ref)
- end
-
- def find_free_parent_spaces(commit, map, times)
- spaces = []
-
- commit.parents.each do |p|
- if map.include?(p.id) then
- parent = map[p.id]
-
- range = if commit.time < parent.time then
- commit.time..parent.time
- else
- parent.time..commit.time
- end
-
- space = if commit.space >= parent.space then
- find_free_parent_space(range, parent.space, 1, commit.space, times)
- else
- find_free_parent_space(range, parent.space, -1, parent.space, times)
- end
-
- mark_reserved(range, space)
- spaces << space
- end
- end
-
- spaces
- end
-
- def find_free_parent_space(range, space_base, space_step, space_default, times)
- if is_overlap?(range, times, space_default) then
- find_free_space(range, space_base, space_step)
- else
- space_default
- end
- end
-
- def is_overlap?(range, times, overlap_space)
- range.each do |i|
- if i != range.first &&
- i != range.last &&
- times[i].space == overlap_space then
-
- return true;
- end
- end
-
- false
- end
-
- # Add space mark on commit and its parents
- #
- # @param [Graph::Commit] the commit object.
- # @param [Hash<String,Graph::Commit>] map of commits
- def place_chain(commit, map, parent_time = nil)
- leaves = take_left_leaves(commit, map)
- if leaves.empty?
- return
- end
- # and mark it as reserved
- min_time = leaves.last.time
- max_space = 1
- parents = leaves.last.parents.collect
- parents.each do |p|
- if map.include? p.id
- parent = map[p.id]
- if parent.time < min_time
- min_time = parent.time
- end
- if max_space < parent.space then
- max_space = parent.space
- end
- end
- end
- if parent_time.nil?
- max_time = leaves.first.time
- else
- max_time = parent_time - 1
- end
-
- time_range = leaves.last.time..leaves.first.time
- space = find_free_space(time_range, max_space, 2)
- leaves.each{|l| l.space = space}
-
- mark_reserved(min_time..max_time, space)
-
- # Visit branching chains
- leaves.each do |l|
- parents = l.parents.collect.select{|p| map.include? p.id and map[p.id].space.zero?}
- for p in parents
- place_chain(map[p.id], map, l.time)
- end
- end
- end
-
- def mark_reserved(time_range, space)
- for day in time_range
- @_reserved[day].push(space)
- end
- end
-
- def find_free_space(time_range, space_base, space_step)
- reserved = []
- for day in time_range
- reserved += @_reserved[day]
- end
- reserved.uniq!
-
- space = space_base
- while reserved.include?(space) do
- space += space_step
- if space <= 0 then
- space_step *= -1
- space = space_base + space_step
- end
- end
-
- space
- end
-
- # Takes most left subtree branch of commits
- # which don't have space mark yet.
- #
- # @param [Graph::Commit] the commit object.
- # @param [Hash<String,Graph::Commit>] map of commits
- #
- # @return [Array<Graph::Commit>] list of branch commits
- def take_left_leaves(commit, map)
- leaves = []
- leaves.push(commit) if commit.space.zero?
-
- while true
- return leaves if commit.parents.count.zero?
- return leaves unless map.include? commit.parents.first.id
-
- commit = map[commit.parents.first.id]
-
- return leaves unless commit.space.zero?
-
- leaves.push(commit)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb
new file mode 100644
index 00000000000..a1ff248a77f
--- /dev/null
+++ b/lib/gitlab/identifier.rb
@@ -0,0 +1,23 @@
+# Detect user based on identifier like
+# key-13 or user-36 or last commit
+module Gitlab
+ module Identifier
+ def identify(identifier, project, newrev)
+ if identifier.blank?
+ # Local push from gitlab
+ email = project.repository.commit(newrev).author_email rescue nil
+ User.find_by_email(email) if email
+
+ elsif identifier =~ /\Auser-\d+\Z/
+ # git push over http
+ user_id = identifier.gsub("user-", "")
+ User.find_by_id(user_id)
+
+ elsif identifier =~ /\Akey-\d+\Z/
+ # git push over ssh
+ key_id = identifier.gsub("key-", "")
+ Key.find_by_id(key_id).try(:user)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb
index 7a0a3214aa1..89c8e0680c3 100644
--- a/lib/gitlab/inline_diff.rb
+++ b/lib/gitlab/inline_diff.rb
@@ -4,7 +4,7 @@ module Gitlab
START = "#!idiff-start!#"
FINISH = "#!idiff-finish!#"
-
+
def processing diff_arr
indexes = _indexes_of_changed_lines diff_arr
@@ -13,6 +13,9 @@ module Gitlab
second_line = diff_arr[index+2]
max_length = [first_line.size, second_line.size].max
+ # Skip inline diff if empty line was replaced with content
+ next if first_line == "-\n"
+
first_the_same_symbols = 0
(0..max_length + 1).each do |i|
first_the_same_symbols = i - 1
@@ -20,9 +23,19 @@ module Gitlab
break
end
end
+
first_token = first_line[0..first_the_same_symbols][1..-1]
- diff_arr[index+1].sub!(first_token, first_token + START)
- diff_arr[index+2].sub!(first_token, first_token + START)
+ start = first_token + START
+
+ if first_token.empty?
+ # In case if we remove string of spaces in commit
+ diff_arr[index+1].sub!("-", "-" => "-#{START}")
+ diff_arr[index+2].sub!("+", "+" => "+#{START}")
+ else
+ diff_arr[index+1].sub!(first_token, first_token => start)
+ diff_arr[index+2].sub!(first_token, first_token => start)
+ end
+
last_the_same_symbols = 0
(1..max_length + 1).each do |i|
last_the_same_symbols = -i
@@ -60,8 +73,6 @@ module Gitlab
line.gsub!(FINISH, "</span>")
line
end
-
end
-
end
end
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
new file mode 100644
index 00000000000..bc49d27b521
--- /dev/null
+++ b/lib/gitlab/issues_labels.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ class IssuesLabels
+ class << self
+ def important_labels
+ %w(bug critical confirmed)
+ end
+
+ def warning_labels
+ %w(documentation support)
+ end
+
+ def neutral_labels
+ %w(discussion suggestion)
+ end
+
+ def positive_labels
+ %w(feature enhancement)
+ end
+
+ def generate(project)
+ labels = important_labels + warning_labels + neutral_labels + positive_labels
+
+ project.issues_default_label_list = labels
+ project.save
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
new file mode 100644
index 00000000000..260bacfeeb0
--- /dev/null
+++ b/lib/gitlab/ldap/user.rb
@@ -0,0 +1,94 @@
+require 'gitlab/oauth/user'
+
+# LDAP extension for User model
+#
+# * Find or create user from omniauth.auth data
+# * Links LDAP account with existing user
+# * Auth LDAP user with login and password
+#
+module Gitlab
+ module LDAP
+ class User < Gitlab::OAuth::User
+ class << self
+ def find_or_create(auth)
+ @auth = auth
+
+ if uid.blank? || email.blank?
+ raise_error("Account must provide an uid and email address")
+ end
+
+ user = find(auth)
+
+ unless user
+ # Look for user with same emails
+ #
+ # Possible cases:
+ # * When user already has account and need to link his LDAP account.
+ # * LDAP uid changed for user with same email and we need to update his uid
+ #
+ user = find_user(email)
+
+ if user
+ user.update_attributes(extern_uid: uid, provider: provider)
+ log.info("(LDAP) Updating legacy LDAP user #{email} with extern_uid => #{uid}")
+ else
+ # Create a new user inside GitLab database
+ # based on LDAP credentials
+ #
+ #
+ user = create(auth)
+ end
+ end
+
+ user
+ end
+
+ def find_user(email)
+ user = model.find_by_email(email)
+
+ # If no user found and allow_username_or_email_login is true
+ # we look for user by extracting part of his email
+ if !user && email && ldap_conf['allow_username_or_email_login']
+ uname = email.partition('@').first
+ user = model.find_by_username(uname)
+ end
+
+ user
+ end
+
+ def authenticate(login, password)
+ # Check user against LDAP backend if user is not authenticated
+ # Only check with valid login and password to prevent anonymous bind results
+ return nil unless ldap_conf.enabled && login.present? && password.present?
+
+ ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf)
+ ldap_user = ldap.bind_as(
+ filter: Net::LDAP::Filter.eq(ldap.uid, login),
+ size: 1,
+ password: password
+ )
+
+ find_by_uid(ldap_user.dn) if ldap_user
+ end
+
+ private
+
+ def find_by_uid(uid)
+ model.where(provider: provider, extern_uid: uid).last
+ end
+
+ def provider
+ 'ldap'
+ end
+
+ def raise_error(message)
+ raise OmniAuth::Error, "(LDAP) " + message
+ end
+
+ def ldap_conf
+ Gitlab.config.ldap
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index e7d6e3e6bd9..dc9c0e0ab2c 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -7,6 +7,7 @@ module Gitlab
# Supported reference formats are:
# * @foo for team members
# * #123 for issues
+ # * #JIRA-123 for Jira issues
# * !123 for merge requests
# * $123 for snippets
# * 123456 for commits
@@ -17,7 +18,7 @@ module Gitlab
# Examples
#
# >> gfm("Hey @david, can you fix this?")
- # => "Hey <a href="/gitlab/team_members/1">@david</a>, can you fix this?"
+ # => "Hey <a href="/u/david">@david</a>, can you fix this?"
#
# >> gfm("Commit 35d5f7c closes #1234")
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
@@ -25,6 +26,8 @@ module Gitlab
# >> gfm(":trollface:")
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":trollface:\" />
module Markdown
+ include IssuesHelper
+
attr_reader :html_options
# Public: Parse the provided text with GitLab-Flavored Markdown
@@ -60,7 +63,7 @@ module Gitlab
insert_piece($1)
end
- sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
+ sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class), tags: ActionView::Base.sanitized_allowed_tags + %w(table tr td th)
end
private
@@ -95,10 +98,11 @@ module Gitlab
(?<prefix>\W)? # Prefix
( # Reference
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
- |\#(?<issue>\d+) # Issue ID
+ |\#(?<issue>([a-zA-Z]+-)?\d+) # Issue ID
|!(?<merge_request>\d+) # MR ID
|\$(?<snippet>\d+) # Snippet ID
|(?<commit>[\h]{6,40}) # Commit ID
+ |(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
(?<suffix>\W)? # Suffix
}x.freeze
@@ -111,13 +115,18 @@ module Gitlab
prefix = $~[:prefix]
suffix = $~[:suffix]
type = TYPES.select{|t| !$~[t].nil?}.first
- identifier = $~[type]
- # Avoid HTML entities
- if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
- match
- elsif ref_link = reference_link(type, identifier)
- "#{prefix}#{ref_link}#{suffix}"
+ if type
+ identifier = $~[type]
+
+ # Avoid HTML entities
+ if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
+ match
+ elsif ref_link = reference_link(type, identifier)
+ "#{prefix}#{ref_link}#{suffix}"
+ else
+ match
+ end
else
match
end
@@ -157,19 +166,22 @@ module Gitlab
end
def reference_user(identifier)
- if member = @project.users_projects.joins(:user).where(users: { username: identifier }).first
- link_to("@#{identifier}", project_team_member_url(@project, member), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
+ if member = @project.team_members.find { |user| user.username == identifier }
+ link_to("@#{identifier}", user_url(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
end
end
def reference_issue(identifier)
- if issue = @project.issues.where(id: identifier).first
- link_to("##{identifier}", project_issue_url(@project, issue), html_options.merge(title: "Issue: #{issue.title}", class: "gfm gfm-issue #{html_options[:class]}"))
+ if @project.issue_exists? identifier
+ url = url_for_issue(identifier)
+ title = title_for_issue(identifier)
+
+ link_to("##{identifier}", url, html_options.merge(title: "Issue: #{title}", class: "gfm gfm-issue #{html_options[:class]}"))
end
end
def reference_merge_request(identifier)
- if merge_request = @project.merge_requests.where(id: identifier).first
+ if merge_request = @project.merge_requests.where(iid: identifier).first
link_to("!#{identifier}", project_merge_request_url(@project, merge_request), html_options.merge(title: "Merge Request: #{merge_request.title}", class: "gfm gfm-merge_request #{html_options[:class]}"))
end
end
@@ -182,7 +194,7 @@ module Gitlab
def reference_commit(identifier)
if @project.valid_repo? && commit = @project.repository.commit(identifier)
- link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: CommitDecorator.new(commit).link_title, class: "gfm gfm-commit #{html_options[:class]}"))
+ link_to(identifier, project_commit_url(@project, commit), html_options.merge(title: commit.link_title, class: "gfm gfm-commit #{html_options[:class]}"))
end
end
end
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
new file mode 100644
index 00000000000..1b32b99f4ba
--- /dev/null
+++ b/lib/gitlab/oauth/user.rb
@@ -0,0 +1,85 @@
+# OAuth extension for User model
+#
+# * Find GitLab user based on omniauth uid and provider
+# * Create new user from omniauth data
+#
+module Gitlab
+ module OAuth
+ class User
+ class << self
+ attr_reader :auth
+
+ def find(auth)
+ @auth = auth
+ find_by_uid_and_provider
+ end
+
+ def create(auth)
+ @auth = auth
+ password = Devise.friendly_token[0, 8].downcase
+ opts = {
+ extern_uid: uid,
+ provider: provider,
+ name: name,
+ username: username,
+ email: email,
+ password: password,
+ password_confirmation: password,
+ }
+
+ user = model.build_user(opts, as: :admin)
+ user.save!
+ log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}"
+
+ if Gitlab.config.omniauth['block_auto_created_users'] && !ldap?
+ user.block
+ end
+
+ user
+ end
+
+ private
+
+ def find_by_uid_and_provider
+ model.where(provider: provider, extern_uid: uid).last
+ end
+
+ def uid
+ auth.info.uid || auth.uid
+ end
+
+ def email
+ auth.info.email.downcase unless auth.info.email.nil?
+ end
+
+ def name
+ auth.info.name.to_s.force_encoding("utf-8")
+ end
+
+ def username
+ email.match(/^[^@]*/)[0]
+ end
+
+ def provider
+ auth.provider
+ end
+
+ def log
+ Gitlab::AppLogger
+ end
+
+ def model
+ ::User
+ end
+
+ def raise_error(message)
+ raise OmniAuth::Error, "(OAuth) " + message
+ end
+
+ def ldap?
+ provider == 'ldap'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index f2cfd8073e3..2f30fde2078 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -2,7 +2,7 @@ module Gitlab
module Popen
def popen(cmd, path)
vars = { "PWD" => path }
- options = { :chdir => path }
+ options = { chdir: path }
@cmd_output = ""
@cmd_status = 0
diff --git a/lib/gitlab/project_mover.rb b/lib/gitlab/project_mover.rb
deleted file mode 100644
index e21f45c6564..00000000000
--- a/lib/gitlab/project_mover.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# ProjectMover class
-#
-# Used for moving project repositories from one subdir to another
-module Gitlab
- class ProjectMover
- class ProjectMoveError < StandardError; end
-
- attr_reader :project, :old_dir, :new_dir
-
- def initialize(project, old_dir, new_dir)
- @project = project
- @old_dir = old_dir
- @new_dir = new_dir
- end
-
- def execute
- # Create new dir if missing
- new_dir_path = File.join(Gitlab.config.gitlab_shell.repos_path, new_dir)
- FileUtils.mkdir( new_dir_path, mode: 0770 ) unless File.exists?(new_dir_path)
-
- old_path = File.join(Gitlab.config.gitlab_shell.repos_path, old_dir, "#{project.path}.git")
- new_path = File.join(new_dir_path, "#{project.path}.git")
-
- if File.exists? new_path
- raise ProjectMoveError.new("Destination #{new_path} already exists")
- end
-
- begin
- FileUtils.mv( old_path, new_path )
- log_info "Project #{project.name} was moved from #{old_path} to #{new_path}"
- true
- rescue Exception => e
- message = "Project #{project.name} cannot be moved from #{old_path} to #{new_path}"
- log_info "Error! #{message} (#{e.message})"
- raise ProjectMoveError.new(message)
- end
- end
-
- protected
-
- def log_info message
- Gitlab::AppLogger.info message
- end
- end
-end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
new file mode 100644
index 00000000000..94b01e808d9
--- /dev/null
+++ b/lib/gitlab/reference_extractor.rb
@@ -0,0 +1,59 @@
+module Gitlab
+ # Extract possible GFM references from an arbitrary String for further processing.
+ class ReferenceExtractor
+ attr_accessor :users, :issues, :merge_requests, :snippets, :commits
+
+ include Markdown
+
+ def initialize
+ @users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], []
+ end
+
+ def analyze string
+ parse_references(string.dup)
+ end
+
+ # Given a valid project, resolve the extracted identifiers of the requested type to
+ # model objects.
+
+ def users_for project
+ users.map do |identifier|
+ project.users.where(username: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def issues_for project
+ issues.map do |identifier|
+ project.issues.where(iid: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def merge_requests_for project
+ merge_requests.map do |identifier|
+ project.merge_requests.where(iid: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def snippets_for project
+ snippets.map do |identifier|
+ project.snippets.where(id: identifier).first
+ end.reject(&:nil?)
+ end
+
+ def commits_for project
+ repo = project.repository
+ return [] if repo.nil?
+
+ commits.map do |identifier|
+ repo.commit(identifier)
+ end.reject(&:nil?)
+ end
+
+ private
+
+ def reference_link type, identifier
+ # Append identifier to the appropriate collection.
+ send("#{type}s") << identifier
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 483042205ea..b4be46d3b42 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -7,7 +7,11 @@ module Gitlab
end
def project_name_regex
- /\A[a-zA-Z][a-zA-Z0-9_\-\. ]*\z/
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\. ]*\z/
+ end
+
+ def name_regex
+ /\A[a-zA-Z0-9_\-\. ]*\z/
end
def path_regex
@@ -17,7 +21,7 @@ module Gitlab
protected
def default_regex
- /\A[a-zA-Z][a-zA-Z0-9_\-\.]*\z/
+ /\A[a-zA-Z0-9][a-zA-Z0-9_\-\.]*\z/
end
end
end
diff --git a/lib/gitlab/satellite/action.rb b/lib/gitlab/satellite/action.rb
index 63303ca3de1..5ea6f956765 100644
--- a/lib/gitlab/satellite/action.rb
+++ b/lib/gitlab/satellite/action.rb
@@ -25,25 +25,31 @@ module Gitlab
end
end
rescue Errno::ENOMEM => ex
- Gitlab::GitLogger.error(ex.message)
- return false
+ return handle_exception(ex)
rescue Grit::Git::GitTimeout => ex
- Gitlab::GitLogger.error(ex.message)
- return false
+ return handle_exception(ex)
ensure
Gitlab::ShellEnv.reset_env
end
- # * Clears the satellite
- # * Updates the satellite from Gitolite
+ # * Recreates the satellite
# * Sets up Git variables for the user
#
# Note: use this within #in_locked_and_timed_satellite
def prepare_satellite!(repo)
project.satellite.clear_and_update!
- repo.git.config({}, "user.name", user.name)
- repo.git.config({}, "user.email", user.email)
+ repo.config['user.name'] = user.name
+ repo.config['user.email'] = user.email
+ end
+
+ def default_options(options = {})
+ {raise: true, timeout: true}.merge(options)
+ end
+
+ def handle_exception(exception)
+ Gitlab::GitLogger.error(exception.message)
+ false
end
end
end
diff --git a/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/edit_file_action.rb
index e9053f904c0..d793d0ba8dc 100644
--- a/lib/gitlab/satellite/edit_file_action.rb
+++ b/lib/gitlab/satellite/edit_file_action.rb
@@ -13,7 +13,7 @@ module Gitlab
# Updates the files content and creates a new commit for it
#
# Returns false if the ref has been updated while editing the file
- # Returns false if commiting the change fails
+ # Returns false if committing the change fails
# Returns false if pushing from the satellite to Gitolite failed or was rejected
# Returns true otherwise
def commit!(content, commit_message, last_commit)
@@ -49,7 +49,7 @@ module Gitlab
protected
def can_edit?(last_commit)
- current_last_commit = @project.repository.last_commit_for(ref, file_path).sha
+ current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha
last_commit == current_last_commit
end
end
diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb
index 832db6621c4..156483be8dd 100644
--- a/lib/gitlab/satellite/merge_action.rb
+++ b/lib/gitlab/satellite/merge_action.rb
@@ -5,48 +5,120 @@ module Gitlab
attr_accessor :merge_request
def initialize(user, merge_request)
- super user, merge_request.project
+ super user, merge_request.target_project
@merge_request = merge_request
end
# Checks if a merge request can be executed without user interaction
def can_be_merged?
in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
merge_in_satellite!(merge_repo)
end
end
# Merges the source branch into the target branch in the satellite and
- # pushes it back to Gitolite.
- # It also removes the source branch if requested in the merge request.
+ # pushes it back to the repository.
+ # It also removes the source branch if requested in the merge request (and this is permitted by the merge request).
#
# Returns false if the merge produced conflicts
- # Returns false if pushing from the satellite to Gitolite failed or was rejected
+ # Returns false if pushing from the satellite to the repository failed or was rejected
# Returns true otherwise
def merge!
in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
if merge_in_satellite!(merge_repo)
# push merge back to Gitolite
# will raise CommandFailed when push fails
- merge_repo.git.push({raise: true, timeout: true}, :origin, merge_request.target_branch)
-
+ merge_repo.git.push(default_options, :origin, merge_request.target_branch)
# remove source branch
if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch)
# will raise CommandFailed when push fails
- merge_repo.git.push({raise: true, timeout: true}, :origin, ":#{merge_request.source_branch}")
+ merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}")
end
-
# merge, push and branch removal successful
true
end
end
rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
+ handle_exception(ex)
end
- private
+ # Get a raw diff of the source to the target
+ def diff_in_satellite
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+
+ if merge_request.for_fork?
+ diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
+ else
+ diff = merge_repo.git.native(:diff, default_options, "#{merge_request.target_branch}", "#{merge_request.source_branch}")
+ end
+
+ return diff
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Only show what is new in the source branch compared to the target branch, not the other way around.
+ # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2)
+ # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
+ def diffs_between_satellite
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ if merge_request.for_fork?
+ common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip
+ #this method doesn't take default options
+ diffs = merge_repo.diff(common_commit, "source/#{merge_request.source_branch}")
+ else
+ raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
+ end
+ diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) }
+ return diffs
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ # Get commit as an email patch
+ def format_patch
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+
+ if (merge_request.for_fork?)
+ patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}")
+ else
+ patch = merge_repo.git.format_patch(default_options({stdout: true}), "#{merge_request.target_branch}..#{merge_request.source_branch}")
+ end
+
+ return patch
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+ # Retrieve an array of commits between the source and the target
+ def commits_between
+ in_locked_and_timed_satellite do |merge_repo|
+ prepare_satellite!(merge_repo)
+ update_satellite_source_and_target!(merge_repo)
+ if (merge_request.for_fork?)
+ commits = merge_repo.commits_between("origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}")
+ else
+ raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]"
+ end
+ commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) }
+ return commits
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
+ private
# Merges the source_branch into the target_branch in the satellite.
#
# Note: it will clear out the satellite before doing anything
@@ -54,18 +126,35 @@ module Gitlab
# Returns false if the merge produced conflicts
# Returns true otherwise
def merge_in_satellite!(repo)
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from Gitolite
- repo.git.checkout({raise: true, timeout: true, b: true}, merge_request.target_branch, "origin/#{merge_request.target_branch}")
+ update_satellite_source_and_target!(repo)
- # merge the source branch from Gitolite into the satellite
+ # merge the source branch into the satellite
# will raise CommandFailed when merge fails
- repo.git.pull({raise: true, timeout: true, no_ff: true}, :origin, merge_request.source_branch)
+ if merge_request.for_fork?
+ repo.git.pull(default_options({no_ff: true}), 'source', merge_request.source_branch)
+ else
+ repo.git.pull(default_options({no_ff: true}), 'origin', merge_request.source_branch)
+ end
rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
+ handle_exception(ex)
end
+
+ # Assumes a satellite exists that is a fresh clone of the projects repo, prepares satellite for merges, diffs etc
+ def update_satellite_source_and_target!(repo)
+ if merge_request.for_fork?
+ repo.remote_add('source', merge_request.source_project.repository.path_to_repo)
+ repo.remote_fetch('source')
+ repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}")
+ else
+ # We can't trust the input here being branch names, we can't always check it out because it could be a relative ref i.e. HEAD~3
+ # we could actually remove the if true, because it should never ever happen (as long as the satellite has been prepared)
+ repo.git.checkout(default_options, "#{merge_request.source_branch}")
+ repo.git.checkout(default_options, "#{merge_request.target_branch}")
+ end
+ rescue Grit::Git::CommandFailed => ex
+ handle_exception(ex)
+ end
+
end
end
end
diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb
index e7f7a7673b5..6cb7814fae5 100644
--- a/lib/gitlab/satellite/satellite.rb
+++ b/lib/gitlab/satellite/satellite.rb
@@ -1,5 +1,5 @@
module Gitlab
- class SatelliteNotExistError < StandardError; end
+ class SatelliteNotExistError < StandardError; end
module Satellite
class Satellite
@@ -24,8 +24,11 @@ module Gitlab
def clear_and_update!
raise_no_satellite unless exists?
- delete_heads!
+ File.exists? path
+ @repo = nil
clear_working_dir!
+ delete_heads!
+ remove_remotes!
update_from_source!
end
@@ -55,16 +58,18 @@ module Gitlab
raise_no_satellite unless exists?
File.open(lock_file, "w+") do |f|
- f.flock(File::LOCK_EX)
-
- Dir.chdir(path) do
- return yield
+ begin
+ f.flock File::LOCK_EX
+ Dir.chdir(path) { return yield }
+ ensure
+ f.flock File::LOCK_UN
end
end
end
def lock_file
- Rails.root.join("tmp", "satellite_#{project.id}.lock")
+ create_locks_dir unless File.exists?(lock_files_dir)
+ File.join(lock_files_dir, "satellite_#{project.id}.lock")
end
def path
@@ -99,20 +104,44 @@ module Gitlab
if heads.include? PARKING_BRANCH
repo.git.checkout({}, PARKING_BRANCH)
else
- repo.git.checkout({b: true}, PARKING_BRANCH)
+ repo.git.checkout(default_options({b: true}), PARKING_BRANCH)
end
# remove the parking branch from the list of heads ...
heads.delete(PARKING_BRANCH)
# ... and delete all others
- heads.each { |head| repo.git.branch({D: true}, head) }
+ heads.each { |head| repo.git.branch(default_options({D: true}), head) }
+ end
+
+ # Deletes all remotes except origin
+ #
+ # This ensures we have no remote name clashes or issues updating branches when
+ # working with the satellite.
+ def remove_remotes!
+ remotes = repo.git.remote.split(' ')
+ remotes.delete('origin')
+ remotes.each { |name| repo.git.remote(default_options,'rm', name)}
end
# Updates the satellite from Gitolite
#
# Note: this will only update remote branches (i.e. origin/*)
def update_from_source!
- repo.git.fetch({timeout: true}, :origin)
+ repo.git.fetch(default_options, :origin)
+ end
+
+ def default_options(options = {})
+ {raise: true, timeout: true}.merge(options)
+ end
+
+ # Create directory for storing
+ # satellites lock files
+ def create_locks_dir
+ FileUtils.mkdir_p(lock_files_dir)
+ end
+
+ def lock_files_dir
+ @lock_files_dir ||= File.join(Gitlab.config.satellites.path, "tmp")
end
end
end
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
index 7f833867e39..89604162304 100644
--- a/lib/gitlab/theme.rb
+++ b/lib/gitlab/theme.rb
@@ -1,12 +1,18 @@
module Gitlab
class Theme
+ BASIC = 1
+ MARS = 2
+ MODERN = 3
+ GRAY = 4
+ COLOR = 5
+
def self.css_class_by_id(id)
themes = {
- 1 => "ui_basic",
- 2 => "ui_mars",
- 3 => "ui_modern",
- 4 => "ui_gray",
- 5 => "ui_color"
+ BASIC => "ui_basic",
+ MARS => "ui_mars",
+ MODERN => "ui_modern",
+ GRAY => "ui_gray",
+ COLOR => "ui_color"
}
id ||= 1
diff --git a/lib/gitlab/user_team_manager.rb b/lib/gitlab/user_team_manager.rb
deleted file mode 100644
index a8ff4a3d94d..00000000000
--- a/lib/gitlab/user_team_manager.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# UserTeamManager class
-#
-# Used for manage User teams with project repositories
-module Gitlab
- class UserTeamManager
- class << self
- def assign(team, project, access)
- project = Project.find(project) unless project.is_a? Project
- searched_project = team.user_team_project_relationships.find_by_project_id(project.id)
-
- unless searched_project.present?
- team.user_team_project_relationships.create(project_id: project.id, greatest_access: access)
- update_team_users_access_in_project(team, project)
- end
- end
-
- def resign(team, project)
- project = Project.find(project) unless project.is_a? Project
-
- team.user_team_project_relationships.with_project(project).destroy_all
-
- update_team_users_access_in_project(team, project)
- end
-
- def update_team_user_membership(team, member, options)
- updates = {}
-
- if options[:default_projects_access] && options[:default_projects_access] != team.default_projects_access(member)
- updates[:permission] = options[:default_projects_access]
- end
-
- if options[:group_admin].to_s != team.admin?(member).to_s
- updates[:group_admin] = options[:group_admin].present?
- end
-
- unless updates.blank?
- user_team_relationship = team.user_team_user_relationships.find_by_user_id(member)
- if user_team_relationship.update_attributes(updates)
- if updates[:permission]
- rebuild_project_permissions_to_member(team, member)
- end
- true
- else
- false
- end
- else
- true
- end
- end
-
- def update_project_greates_access(team, project, permission)
- project_relation = team.user_team_project_relationships.find_by_project_id(project)
- if permission != team.max_project_access(project)
- if project_relation.update_attributes(greatest_access: permission)
- update_team_users_access_in_project(team, project)
- true
- else
- false
- end
- else
- true
- end
- end
-
- def rebuild_project_permissions_to_member(team, member)
- team.projects.each do |project|
- update_team_user_access_in_project(team, member, project)
- end
- end
-
- def update_team_users_access_in_project(team, project)
- members = team.members
- members.each do |member|
- update_team_user_access_in_project(team, member, project)
- end
- end
-
- def update_team_user_access_in_project(team, user, project)
- granted_access = max_teams_member_permission_in_project(user, project)
-
- project_team_user = UsersProject.find_by_user_id_and_project_id(user.id, project.id)
- project_team_user.destroy if project_team_user.present?
-
- # project_team_user.project_access != granted_access
- project.team << [user, granted_access] if granted_access > 0
- end
-
- def max_teams_member_permission_in_project(user, project, teams = nil)
- result_access = 0
-
- user_teams = project.user_teams.with_member(user)
-
- teams ||= user_teams
-
- if teams.any?
- teams.each do |team|
- granted_access = max_team_member_permission_in_project(team, user, project)
- result_access = [granted_access, result_access].max
- end
- end
- result_access
- end
-
- def max_team_member_permission_in_project(team, user, project)
- member_access = team.default_projects_access(user)
- team_access = team.user_team_project_relationships.find_by_project_id(project.id).greatest_access
-
- [team_access, member_access].min
- end
-
- def add_member_into_team(team, user, access, admin)
- user = User.find(user) unless user.is_a? User
-
- team.user_team_user_relationships.create(user_id: user.id, permission: access, group_admin: admin)
- team.projects.each do |project|
- update_team_user_access_in_project(team, user, project)
- end
- end
-
- def remove_member_from_team(team, user)
- user = User.find(user) unless user.is_a? User
-
- team.user_team_user_relationships.with_user(user).destroy_all
- other_teams = []
- team.projects.each do |project|
- other_teams << project.user_teams.with_member(user)
- end
- other_teams.uniq
- unless other_teams.any?
- UsersProject.in_projects(team.projects).with_user(user).destroy_all
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
new file mode 100644
index 00000000000..6ee41e85cc9
--- /dev/null
+++ b/lib/gitlab/version_info.rb
@@ -0,0 +1,54 @@
+module Gitlab
+ class VersionInfo
+ include Comparable
+
+ attr_reader :major, :minor, :patch
+
+ def self.parse(str)
+ if str && m = str.match(/(\d+)\.(\d+)\.(\d+)/)
+ VersionInfo.new(m[1].to_i, m[2].to_i, m[3].to_i)
+ else
+ VersionInfo.new
+ end
+ end
+
+ def initialize(major = 0, minor = 0, patch = 0)
+ @major = major
+ @minor = minor
+ @patch = patch
+ end
+
+ def <=>(other)
+ return unless other.is_a? VersionInfo
+ return unless valid? && other.valid?
+
+ if other.major < @major
+ 1
+ elsif @major < other.major
+ -1
+ elsif other.minor < @minor
+ 1
+ elsif @minor < other.minor
+ -1
+ elsif other.patch < @patch
+ 1
+ elsif @patch < other.patch
+ -1
+ else
+ 0
+ end
+ end
+
+ def to_s
+ if valid?
+ "%d.%d.%d" % [@major, @minor, @patch]
+ else
+ "Unknown"
+ end
+ end
+
+ def valid?
+ @major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
+ end
+ end
+end
diff --git a/lib/gitolited.rb b/lib/gitolited.rb
deleted file mode 100644
index a7fc4148106..00000000000
--- a/lib/gitolited.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# == Gitolited mixin
-#
-# Provide a shortcut to Gitlab::Shell instance by gitlab_shell
-#
-# Used by Project, UsersProject, etc
-#
-module Gitolited
- def gitlab_shell
- Gitlab::Shell.new
- end
-end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 4f2c86e2d41..d9c2d3b626d 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -11,7 +11,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
def block_code(code, language)
options = { options: {encoding: 'utf-8'} }
- options.merge!(lexer: language.downcase) if Pygments::Lexer.find(language)
+ lexer = Pygments::Lexer.find(language) # language can be an alias
+ options.merge!(lexer: lexer.aliases[0].downcase) if lexer # downcase is required
# New lines are placed to fix an rendering issue
# with code wrapped inside <h1> tag for next case:
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
new file mode 100755
index 00000000000..0d2f8418bcf
--- /dev/null
+++ b/lib/support/deploy/deploy.sh
@@ -0,0 +1,44 @@
+# This is deploy script we use to update staging server
+# You can always modify it for your needs :)
+
+# If any command return non-zero status - stop deploy
+set -e
+
+echo 'Deploy: Stoping sidekiq..'
+cd /home/git/gitlab/ && sudo -u git -H bundle exec rake sidekiq:stop RAILS_ENV=production
+
+echo 'Deploy: Show deploy index page'
+sudo -u git -H cp /home/git/gitlab/public/deploy.html /home/git/gitlab/public/index.html
+
+echo 'Deploy: Starting backup...'
+cd /home/git/gitlab/ && sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+
+echo 'Deploy: Stop GitLab server'
+sudo service gitlab stop
+
+echo 'Deploy: Get latest code'
+cd /home/git/gitlab/
+
+# clean working directory
+sudo -u git -H git stash
+
+# change branch to
+sudo -u git -H git pull origin master
+
+echo 'Deploy: Bundle and migrate'
+
+# change it to your needs
+sudo -u git -H bundle --without postgres
+
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# return stashed changes (if necessary)
+# sudo -u git -H git stash pop
+
+
+echo 'Deploy: Starting GitLab server...'
+sudo service gitlab start
+
+sleep 10
+sudo -u git -H rm /home/git/gitlab/public/index.html
+echo 'Deploy: Done'
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
new file mode 100755
index 00000000000..0248284f8d5
--- /dev/null
+++ b/lib/support/init.d/gitlab
@@ -0,0 +1,262 @@
+#! /bin/sh
+
+# GITLAB
+# Maintainer: @randx
+# Authors: rovanion.luckey@gmail.com, @randx
+# App Version: 6.0
+
+### BEGIN INIT INFO
+# Provides: gitlab
+# Required-Start: $local_fs $remote_fs $network $syslog redis-server
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: GitLab git repository management
+# Description: GitLab git repository management
+### END INIT INFO
+
+### Environment variables
+RAILS_ENV="production"
+
+# Script variable names should be lower-case not to conflict with internal
+# /bin/sh variables such as PATH, EDITOR or SHELL.
+app_root="/home/git/gitlab"
+app_user="git"
+unicorn_conf="$app_root/config/unicorn.rb"
+pid_path="$app_root/tmp/pids"
+socket_path="$app_root/tmp/sockets"
+web_server_pid_path="$pid_path/unicorn.pid"
+sidekiq_pid_path="$pid_path/sidekiq.pid"
+
+
+
+### Here ends user configuration ###
+
+
+# Switch to the app_user if it is not he/she who is running the script.
+if [ "$USER" != "$app_user" ]; then
+ sudo -u "$app_user" -H -i $0 "$@"; exit;
+fi
+
+# Switch to the gitlab path, if it fails exit with an error.
+if ! cd "$app_root" ; then
+ echo "Failed to cd into $app_root, exiting!"; exit 1
+fi
+
+### Init Script functions
+
+check_pids(){
+ if ! mkdir -p "$pid_path"; then
+ echo "Could not create the path $pid_path needed to store the pids."
+ exit 1
+ fi
+ # If there exists a file which should hold the value of the Unicorn pid: read it.
+ if [ -f "$web_server_pid_path" ]; then
+ wpid=$(cat "$web_server_pid_path")
+ else
+ wpid=0
+ fi
+ if [ -f "$sidekiq_pid_path" ]; then
+ spid=$(cat "$sidekiq_pid_path")
+ else
+ spid=0
+ fi
+}
+
+# We use the pids in so many parts of the script it makes sense to always check them.
+# Only after start() is run should the pids change. Sidekiq sets it's own pid.
+check_pids
+
+
+# Checks whether the different parts of the service are already running or not.
+check_status(){
+ check_pids
+ # If the web server is running kill -0 $wpid returns true, or rather 0.
+ # Checks of *_status should only check for == 0 or != 0, never anything else.
+ if [ $wpid -ne 0 ]; then
+ kill -0 "$wpid" 2>/dev/null
+ web_status="$?"
+ else
+ web_status="-1"
+ fi
+ if [ $spid -ne 0 ]; then
+ kill -0 "$spid" 2>/dev/null
+ sidekiq_status="$?"
+ else
+ sidekiq_status="-1"
+ fi
+}
+
+# Check for stale pids and remove them if necessary
+check_stale_pids(){
+ check_status
+ # If there is a pid it is something else than 0, the service is running if
+ # *_status is == 0.
+ if [ "$wpid" != "0" -a "$web_status" != "0" ]; then
+ echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran."
+ if ! rm "$web_server_pid_path"; then
+ echo "Unable to remove stale pid, exiting"
+ exit 1
+ fi
+ fi
+ if [ "$spid" != "0" -a "$sidekiq_status" != "0" ]; then
+ echo "Removing stale Sidekiq web server pid. This is most likely caused by the Sidekiq crashing the last time it ran."
+ if ! rm "$sidekiq_pid_path"; then
+ echo "Unable to remove stale pid, exiting"
+ exit 1
+ fi
+ fi
+}
+
+# If no parts of the service is running, bail out.
+exit_if_not_running(){
+ check_stale_pids
+ if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
+ echo "GitLab is not running."
+ exit
+ fi
+}
+
+# Starts Unicorn and Sidekiq.
+start() {
+ check_stale_pids
+
+ # Then check if the service is running. If it is: don't start again.
+ if [ "$web_status" = "0" ]; then
+ echo "The Unicorn web server already running with pid $wpid, not restarting."
+ else
+ echo "Starting the GitLab Unicorn web server..."
+ # Remove old socket if it exists
+ rm -f "$socket_path"/gitlab.socket 2>/dev/null
+ # Start the webserver
+ bundle exec unicorn_rails -D -c "$unicorn_conf" -E "$RAILS_ENV"
+ fi
+
+ # If sidekiq is already running, don't start it again.
+ if [ "$sidekiq_status" = "0" ]; then
+ echo "The Sidekiq job dispatcher is already running with pid $spid, not restarting"
+ else
+ echo "Starting the GitLab Sidekiq event dispatcher..."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start
+ # We are sleeping a bit here because sidekiq is slow at writing it's pid
+ sleep 2
+ fi
+
+ # Finally check the status to tell wether or not GitLab is running
+ status
+}
+
+# Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them.
+stop() {
+ exit_if_not_running
+ # If the Unicorn web server is running, tell it to stop;
+ if [ "$web_status" = "0" ]; then
+ kill -QUIT "$wpid" &
+ echo "Stopping the GitLab Unicorn web server..."
+ stopping=true
+ else
+ echo "The Unicorn web was not running, doing nothing."
+ fi
+ # And do the same thing for the Sidekiq.
+ if [ "$sidekiq_status" = "0" ]; then
+ printf "Stopping Sidekiq job dispatcher."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop &
+ stopping=true
+ else
+ echo "The Sidekiq was not running, must have run out of breath."
+ fi
+
+
+ # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script.
+ while [ "$stopping" = "true" ]; do
+ sleep 1
+ check_status
+ if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
+ printf "."
+ else
+ printf "\n"
+ break
+ fi
+ done
+ sleep 1
+ # Cleaning up unused pids
+ rm "$web_server_pid_path" 2>/dev/null
+ # rm "$sidekiq_pid_path" # Sidekiq seems to be cleaning up it's own pid.
+
+ status
+}
+
+# Returns the status of GitLab and it's components
+status() {
+ check_status
+ if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then
+ echo "GitLab is not running."
+ return
+ fi
+ if [ "$web_status" = "0" ]; then
+ echo "The GitLab Unicorn webserver with pid $wpid is running."
+ else
+ printf "The GitLab Unicorn webserver is \033[31mnot running\033[0m.\n"
+ fi
+ if [ "$sidekiq_status" = "0" ]; then
+ echo "The GitLab Sidekiq job dispatcher with pid $spid is running."
+ else
+ printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n"
+ fi
+ if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then
+ printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
+ fi
+}
+
+reload(){
+ exit_if_not_running
+ if [ "$wpid" = "0" ];then
+ echo "The GitLab Unicorn Web server is not running thus its configuration can't be reloaded."
+ exit 1
+ fi
+ printf "Reloading GitLab Unicorn configuration... "
+ kill -USR2 "$wpid"
+ echo "Done."
+ echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:stop
+ echo "Starting Sidekiq..."
+ RAILS_ENV=$RAILS_ENV bundle exec rake sidekiq:start
+ # Waiting 2 seconds for sidekiq to write it.
+ sleep 2
+ status
+}
+
+restart(){
+ check_status
+ if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then
+ stop
+ fi
+ start
+}
+
+
+## Finally the input handling.
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ restart
+ ;;
+ reload|force-reload)
+ reload
+ ;;
+ status)
+ status
+ ;;
+ *)
+ echo "Usage: service gitlab {start|stop|restart|reload|status}"
+ exit 1
+ ;;
+esac
+
+exit
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
new file mode 100644
index 00000000000..3e929c52990
--- /dev/null
+++ b/lib/support/nginx/gitlab
@@ -0,0 +1,39 @@
+# GITLAB
+# Maintainer: @randx
+# App Version: 5.0
+
+upstream gitlab {
+ server unix:/home/git/gitlab/tmp/sockets/gitlab.socket;
+}
+
+server {
+ listen *:80 default_server; # e.g., listen 192.168.1.1:80; In most cases *:80 is a good idea
+ server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com;
+ server_tokens off; # don't show the version number, a security best practice
+ root /home/git/gitlab/public;
+
+ # individual nginx logs for this gitlab vhost
+ access_log /var/log/nginx/gitlab_access.log;
+ error_log /var/log/nginx/gitlab_error.log;
+
+ location / {
+ # serve static files from defined root folder;.
+ # @gitlab is a named location for the upstream fallback, see below
+ try_files $uri $uri/index.html $uri.html @gitlab;
+ }
+
+ # if a file, which is not found in the root folder is requested,
+ # then the proxy pass the request to the upsteam (gitlab unicorn)
+ location @gitlab {
+ proxy_read_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
+ proxy_connect_timeout 300; # https://github.com/gitlabhq/gitlabhq/issues/694
+ proxy_redirect off;
+
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+
+ proxy_pass http://gitlab;
+ }
+}
+
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
new file mode 100644
index 00000000000..8320b9b2576
--- /dev/null
+++ b/lib/tasks/cache.rake
@@ -0,0 +1,6 @@
+namespace :cache do
+ desc "GITLAB | Clear redis cache"
+ task :clear => :environment do
+ Rails.cache.clear
+ end
+end
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
new file mode 100644
index 00000000000..7d3602211c1
--- /dev/null
+++ b/lib/tasks/dev.rake
@@ -0,0 +1,10 @@
+namespace :dev do
+ desc "GITLAB | Setup developer environment (db, fixtures)"
+ task :setup => :environment do
+ ENV['force'] = 'yes'
+ Rake::Task["db:setup"].invoke
+ Rake::Task["db:seed_fu"].invoke
+ Rake::Task["gitlab:shell:setup"].invoke
+ end
+end
+
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 214ce720e7a..2eff1260b61 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -4,210 +4,75 @@ namespace :gitlab do
namespace :backup do
# Create backup of GitLab system
desc "GITLAB | Create a backup of the GitLab system"
- task :create => :environment do
+ task create: :environment do
warn_user_is_not_gitlab
Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke
+ Rake::Task["gitlab:backup:uploads:create"].invoke
- Dir.chdir(Gitlab.config.backup.path)
-
- # saving additional informations
- s = {}
- s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
- s[:backup_created_at] = "#{Time.now}"
- s[:gitlab_version] = %x{git rev-parse HEAD}.gsub(/\n/,"")
- s[:tar_version] = %x{tar --version | head -1}.gsub(/\n/,"")
-
- File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file|
- file << s.to_yaml.gsub(/^---\n/,'')
- end
-
- # create archive
- print "Creating backup archive: #{Time.now.to_i}_gitlab_backup.tar ... "
- if Kernel.system("tar -cf #{Time.now.to_i}_gitlab_backup.tar repositories/ db/ backup_information.yml")
- puts "done".green
- else
- puts "failed".red
- end
-
- # cleanup: remove tmp files
- print "Deleting tmp directories ... "
- if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
- puts "done".green
- else
- puts "failed".red
- end
-
- # delete backups
- print "Deleting old backups ... "
- if Gitlab.config.backup.keep_time > 0
- file_list = Dir.glob("*_gitlab_backup.tar").map { |f| f.split(/_/).first.to_i }
- file_list.sort.each do |timestamp|
- if Time.at(timestamp) < (Time.now - Gitlab.config.backup.keep_time)
- %x{rm #{timestamp}_gitlab_backup.tar}
- end
- end
- puts "done".green
- else
- puts "skipping".yellow
- end
+ backup = Backup::Manager.new
+ backup.pack
+ backup.cleanup
+ backup.remove_old
end
# Restore backup of GitLab system
desc "GITLAB | Restore a previously created backup"
- task :restore => :environment do
+ task restore: :environment do
warn_user_is_not_gitlab
- Dir.chdir(Gitlab.config.backup.path)
-
- # check for existing backups in the backup dir
- file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i }
- puts "no backups found" if file_list.count == 0
- if file_list.count > 1 && ENV["BACKUP"].nil?
- puts "Found more than one backup, please specify which one you want to restore:"
- puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup"
- exit 1
- end
-
- tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
-
- unless File.exists?(tar_file)
- puts "The specified backup doesn't exist!"
- exit 1
- end
-
- print "Unpacking backup ... "
- unless Kernel.system("tar -xf #{tar_file}")
- puts "failed".red
- exit 1
- else
- puts "done".green
- end
-
- settings = YAML.load_file("backup_information.yml")
- ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
-
- # restoring mismatching backups can lead to unexpected problems
- if settings[:gitlab_version] != %x{git rev-parse HEAD}.gsub(/\n/,"")
- puts "GitLab version mismatch:".red
- puts " Your current HEAD differs from the HEAD in the backup!".red
- puts " Please switch to the following revision and try again:".red
- puts " revision: #{settings[:gitlab_version]}".red
- exit 1
- end
+ backup = Backup::Manager.new
+ backup.unpack
Rake::Task["gitlab:backup:db:restore"].invoke
Rake::Task["gitlab:backup:repo:restore"].invoke
+ Rake::Task["gitlab:backup:uploads:restore"].invoke
+ Rake::Task["gitlab:shell:setup"].invoke
- # cleanup: remove tmp files
- print "Deleting tmp directories ... "
- if Kernel.system("rm -rf repositories/ db/ backup_information.yml")
- puts "done".green
- else
- puts "failed".red
- end
+ backup.cleanup
end
- ################################################################################
- ################################# invoked tasks ################################
-
- ################################# REPOSITORIES #################################
-
namespace :repo do
- task :create => :environment do
- backup_path_repo = File.join(Gitlab.config.backup.path, "repositories")
- FileUtils.mkdir_p(backup_path_repo) until Dir.exists?(backup_path_repo)
+ task create: :environment do
puts "Dumping repositories ...".blue
-
- Project.find_each(:batch_size => 1000) do |project|
- print " * #{project.path_with_namespace} ... "
-
- if project.empty_repo?
- puts "[SKIPPED]".cyan
- next
- end
-
- # Create namespace dir if missing
- FileUtils.mkdir_p(File.join(backup_path_repo, project.namespace.path)) if project.namespace
-
- # Build a destination path for backup
- path_to_bundle = File.join(backup_path_repo, project.path_with_namespace + ".bundle")
-
- if Kernel.system("cd #{project.repository.path_to_repo} > /dev/null 2>&1 && git bundle create #{path_to_bundle} --all > /dev/null 2>&1")
- puts "[DONE]".green
- else
- puts "[FAILED]".red
- end
- end
+ Backup::Repository.new.dump
+ puts "done".green
end
- task :restore => :environment do
- backup_path_repo = File.join(Gitlab.config.backup.path, "repositories")
- repos_path = Gitlab.config.gitlab_shell.repos_path
-
- puts "Restoring repositories ... "
-
- Project.find_each(:batch_size => 1000) do |project|
- print "#{project.path_with_namespace} ... "
-
- if project.namespace
- project.namespace.ensure_dir_exist
- end
-
- # Build a backup path
- path_to_bundle = File.join(backup_path_repo, project.path_with_namespace + ".bundle")
-
- if Kernel.system("git clone --bare #{path_to_bundle} #{project.repository.path_to_repo} > /dev/null 2>&1")
- puts "[DONE]".green
- else
- puts "[FAILED]".red
- end
- end
+ task restore: :environment do
+ puts "Restoring repositories ...".blue
+ Backup::Repository.new.restore
+ puts "done".green
end
end
- ###################################### DB ######################################
-
namespace :db do
- task :create => :environment do
- backup_path_db = File.join(Gitlab.config.backup.path, "db")
- FileUtils.mkdir_p(backup_path_db) unless Dir.exists?(backup_path_db)
-
- puts "Dumping database tables ... ".blue
- ActiveRecord::Base.connection.tables.each do |tbl|
- print " * #{tbl.yellow} ... "
- count = 1
- File.open(File.join(backup_path_db, tbl + ".yml"), "w+") do |file|
- ActiveRecord::Base.connection.select_all("SELECT * FROM `#{tbl}`").each do |line|
- line.delete_if{|k,v| v.blank?}
- output = {tbl + '_' + count.to_s => line}
- file << output.to_yaml.gsub(/^---\n/,'') + "\n"
- count += 1
- end
- puts "done".green
- end
- end
+ task create: :environment do
+ puts "Dumping database ... ".blue
+ Backup::Database.new.dump
+ puts "done".green
end
- task :restore => :environment do
- backup_path_db = File.join(Gitlab.config.backup.path, "db")
+ task restore: :environment do
+ puts "Restoring database ... ".blue
+ Backup::Database.new.restore
+ puts "done".green
+ end
+ end
- puts "Restoring database tables (loading fixtures) ... "
- Rake::Task["db:reset"].invoke
+ namespace :uploads do
+ task create: :environment do
+ puts "Dumping uploads ... ".blue
+ Backup::Uploads.new.dump
+ puts "done".green
+ end
- Dir.glob(File.join(backup_path_db, "*.yml") ).each do |dir|
- fixture_file = File.basename(dir, ".*" )
- print "#{fixture_file.yellow} ... "
- if File.size(dir) > 0
- ActiveRecord::Fixtures.create_fixtures(backup_path_db, fixture_file)
- puts "done".green
- else
- puts "skipping".yellow
- end
- end
+ task restore: :environment do
+ puts "Restoring uploads ... ".blue
+ Backup::Uploads.new.restore
+ puts "done".green
end
end
-
end # namespace end: backup
end # namespace end: gitlab
diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake
index eb1a7559dbd..c270232edba 100644
--- a/lib/tasks/gitlab/bulk_add_permission.rake
+++ b/lib/tasks/gitlab/bulk_add_permission.rake
@@ -1,9 +1,9 @@
namespace :gitlab do
namespace :import do
desc "GITLAB | Add all users to all projects (admin users are added as masters)"
- task :all_users_to_all_projects => :environment do |t, args|
- user_ids = User.where(:admin => false).pluck(:id)
- admin_ids = User.where(:admin => true).pluck(:id)
+ task all_users_to_all_projects: :environment do |t, args|
+ user_ids = User.where(admin: false).pluck(:id)
+ admin_ids = User.where(admin: true).pluck(:id)
projects_ids = Project.pluck(:id)
puts "Importing #{user_ids.size} users into #{projects_ids.size} projects"
@@ -21,4 +21,4 @@ namespace :gitlab do
UsersProject.add_users_into_projects(project_ids, Array.wrap(user.id), UsersProject::DEVELOPER)
end
end
-end \ No newline at end of file
+end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 6a138396087..6e2a59f62ac 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -22,7 +22,10 @@ namespace :gitlab do
check_tmp_writable
check_init_script_exists
check_init_script_up_to_date
+ check_projects_have_namespace
check_satellites_exist
+ check_redis_version
+ check_git_version
finished_checking "GitLab"
end
@@ -136,13 +139,15 @@ namespace :gitlab do
def check_init_script_up_to_date
print "Init script up-to-date? ... "
+ recipe_path = Rails.root.join("lib/support/init.d/", "gitlab")
script_path = "/etc/init.d/gitlab"
+
unless File.exists?(script_path)
puts "can't check because of previous errors".magenta
return
end
- recipe_content = `curl https://raw.github.com/gitlabhq/gitlab-recipes/master/init.d/gitlab 2>/dev/null`
+ recipe_content = File.read(recipe_path)
script_content = File.read(script_path)
if recipe_content == script_content
@@ -217,7 +222,7 @@ namespace :gitlab do
puts "no".red
try_fixing_it(
"sudo chown -R gitlab #{log_path}",
- "sudo chmod -R rwX #{log_path}"
+ "sudo chmod -R u+rwX #{log_path}"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -237,7 +242,7 @@ namespace :gitlab do
puts "no".red
try_fixing_it(
"sudo chown -R gitlab #{tmp_path}",
- "sudo chmod -R rwX #{tmp_path}"
+ "sudo chmod -R u+rwX #{tmp_path}"
)
for_more_information(
see_installation_guide_section "GitLab"
@@ -245,6 +250,23 @@ namespace :gitlab do
fix_and_rerun
end
end
+
+ def check_redis_version
+ print "Redis version >= 2.0.0? ... "
+
+ if run_and_match("redis-cli --version", /redis-cli 2.\d.\d/)
+ puts "yes".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "Update your redis server to a version >= 2.0.0"
+ )
+ for_more_information(
+ "gitlab-public-wiki/wiki/Trouble-Shooting-Guide in section sidekiq"
+ )
+ fix_and_rerun
+ end
+ end
end
@@ -255,7 +277,6 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "Environment"
- check_issue_1059_shell_profile_error
check_gitlab_git_config
check_python2_exists
check_python2_version
@@ -294,30 +315,6 @@ namespace :gitlab do
end
end
- # see https://github.com/gitlabhq/gitlabhq/issues/1059
- def check_issue_1059_shell_profile_error
- gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
- print "Has no \"-e\" in ~#{gitlab_shell_ssh_user}/.profile ... "
-
- profile_file = File.join(gitlab_shell_user_home, ".profile")
-
- unless File.read(profile_file) =~ /^-e PATH/
- puts "yes".green
- else
- puts "no".red
- try_fixing_it(
- "Open #{profile_file}",
- "Find the line starting with \"-e PATH\"",
- "Remove \"-e \" so the line starts with PATH"
- )
- for_more_information(
- see_installation_guide_section("Gitlab Shell"),
- "https://github.com/gitlabhq/gitlabhq/issues/1059"
- )
- fix_and_rerun
- end
- end
-
def check_python2_exists
print "Has python2? ... "
@@ -368,19 +365,21 @@ namespace :gitlab do
namespace :gitlab_shell do
- desc "GITLAB | Check the configuration of Gitlab Shell"
+ desc "GITLAB | Check the configuration of GitLab Shell"
task check: :environment do
warn_user_is_not_gitlab
- start_checking "Gitlab Shell"
+ start_checking "GitLab Shell"
+ check_gitlab_shell
check_repo_base_exists
check_repo_base_is_not_symlink
check_repo_base_user_and_group
check_repo_base_permissions
- check_post_receive_hook_is_up_to_date
- check_repos_post_receive_hooks_is_link
+ check_update_hook_is_up_to_date
+ check_repos_update_hooks_is_link
+ check_gitlab_shell_self_test
- finished_checking "Gitlab Shell"
+ finished_checking "GitLab Shell"
end
@@ -388,10 +387,10 @@ namespace :gitlab do
########################
- def check_post_receive_hook_is_up_to_date
- print "post-receive hook up-to-date? ... "
+ def check_update_hook_is_up_to_date
+ print "update hook up-to-date? ... "
- hook_file = "post-receive"
+ hook_file = "update"
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file)
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
@@ -415,12 +414,12 @@ namespace :gitlab do
puts "no".red
puts "#{repo_base_path} is missing".red
try_fixing_it(
- "This should have been created when setting up Gitlab Shell.",
+ "This should have been created when setting up GitLab Shell.",
"Make sure it's set correctly in config/gitlab.yml",
- "Make sure Gitlab Shell is installed correctly."
+ "Make sure GitLab Shell is installed correctly."
)
for_more_information(
- see_installation_guide_section "Gitlab Shell"
+ see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
@@ -465,7 +464,7 @@ namespace :gitlab do
"find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s"
)
for_more_information(
- see_installation_guide_section "Gitlab Shell"
+ see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
@@ -482,25 +481,27 @@ namespace :gitlab do
return
end
- if File.stat(repo_base_path).uid == uid_for(gitlab_shell_ssh_user) &&
- File.stat(repo_base_path).gid == gid_for(gitlab_shell_owner_group)
+ uid = uid_for(gitlab_shell_ssh_user)
+ gid = gid_for(gitlab_shell_owner_group)
+ if File.stat(repo_base_path).uid == uid && File.stat(repo_base_path).gid == gid
puts "yes".green
else
puts "no".red
+ puts " User id for #{gitlab_shell_ssh_user}: #{uid}. Groupd id for #{gitlab_shell_owner_group}: #{gid}".blue
try_fixing_it(
"sudo chown -R #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group} #{repo_base_path}"
)
for_more_information(
- see_installation_guide_section "Gitlab Shell"
+ see_installation_guide_section "GitLab Shell"
)
fix_and_rerun
end
end
- def check_repos_post_receive_hooks_is_link
- print "post-receive hooks in repos are links: ... "
+ def check_repos_update_hooks_is_link
+ print "update hooks in repos are links: ... "
- hook_file = "post-receive"
+ hook_file = "update"
gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path
gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file)
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
@@ -540,7 +541,7 @@ namespace :gitlab do
File.realpath(project_hook_file) == File.realpath(gitlab_shell_hook_file)
puts "ok".green
else
- puts "not a link to Gitlab Shell's hook".red
+ puts "not a link to GitLab Shell's hook".red
try_fixing_it(
"sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}"
)
@@ -553,6 +554,49 @@ namespace :gitlab do
end
end
+ def check_gitlab_shell_self_test
+ gitlab_shell_repo_base = File.expand_path('gitlab-shell', gitlab_shell_user_home)
+ check_cmd = File.expand_path('bin/check', gitlab_shell_repo_base)
+ puts "Running #{check_cmd}"
+ if system(check_cmd, chdir: gitlab_shell_repo_base)
+ puts 'gitlab-shell self-check successful'.green
+ else
+ puts 'gitlab-shell self-check failed'.red
+ try_fixing_it(
+ 'Make sure GitLab is running;',
+ 'Check the gitlab-shell configuration file:',
+ sudo_gitlab("editor #{File.expand_path('config.yml', gitlab_shell_repo_base)}")
+ )
+ fix_and_rerun
+ end
+ end
+
+ def check_projects_have_namespace
+ print "projects have namespace: ... "
+
+ unless Project.count > 0
+ puts "can't check, you have no projects".magenta
+ return
+ end
+ puts ""
+
+ Project.find_each(batch_size: 100) do |project|
+ print "#{project.name_with_namespace.yellow} ... "
+
+ if project.namespace
+ puts "yes".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "Migrate global projects"
+ )
+ for_more_information(
+ "doc/update/5.4-to-6.0.md in section \"#global-projects\""
+ )
+ fix_and_rerun
+ end
+ end
+ end
# Helper methods
########################
@@ -582,6 +626,7 @@ namespace :gitlab do
start_checking "Sidekiq"
check_sidekiq_running
+ only_one_sidekiq_running
finished_checking "Sidekiq"
end
@@ -593,7 +638,7 @@ namespace :gitlab do
def check_sidekiq_running
print "Running? ... "
- if run_and_match("ps aux | grep -i sidekiq", /sidekiq \d\.\d\.\d.+$/)
+ if sidekiq_process_match
puts "yes".green
else
puts "no".red
@@ -607,6 +652,29 @@ namespace :gitlab do
fix_and_rerun
end
end
+
+ def only_one_sidekiq_running
+ sidekiq_match = sidekiq_process_match
+ return unless sidekiq_match
+
+ print 'Number of Sidekiq processes ... '
+ if sidekiq_match.length == 1
+ puts '1'.green
+ else
+ puts "#{sidekiq_match.length}".red
+ try_fixing_it(
+ 'sudo service gitlab stop',
+ 'sudo pkill -f sidekiq',
+ 'sleep 10 && sudo pkill -9 -f sidekiq',
+ 'sudo service gitlab start'
+ )
+ fix_and_rerun
+ end
+ end
+
+ def sidekiq_process_match
+ run_and_match("ps ux | grep -i sidekiq", /(sidekiq \d+\.\d+\.\d+.+$)/)
+ end
end
@@ -658,4 +726,34 @@ namespace :gitlab do
puts " #{step}"
end
end
+
+ def check_gitlab_shell
+ required_version = Gitlab::VersionInfo.new(1, 7, 1)
+ current_version = Gitlab::VersionInfo.parse(gitlab_shell_version)
+
+ print "GitLab Shell version >= #{required_version} ? ... "
+ if current_version.valid? && required_version <= current_version
+ puts "OK (#{current_version})".green
+ else
+ puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".red
+ end
+ end
+
+ def check_git_version
+ required_version = Gitlab::VersionInfo.new(1, 7, 10)
+ current_version = Gitlab::VersionInfo.parse(run("#{Gitlab.config.git.bin_path} --version"))
+
+ puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
+ print "Git version >= #{required_version} ? ... "
+
+ if current_version.valid? && required_version <= current_version
+ puts "yes (#{current_version})".green
+ else
+ puts "no".red
+ try_fixing_it(
+ "Update your git to a version >= #{required_version} from #{current_version}"
+ )
+ fix_and_rerun
+ end
+ end
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index d8ee56e5523..4aaab11340f 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -1,7 +1,7 @@
namespace :gitlab do
namespace :cleanup do
desc "GITLAB | Cleanup | Clean namespaces"
- task :dirs => :environment do
+ task dirs: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
@@ -43,8 +43,8 @@ namespace :gitlab do
end
end
- desc "GITLAB | Cleanup | Clean respositories"
- task :repos => :environment do
+ desc "GITLAB | Cleanup | Clean repositories"
+ task repos: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
diff --git a/lib/tasks/gitlab/enable_namespaces.rake b/lib/tasks/gitlab/enable_namespaces.rake
index a33639a0013..927748c0fd5 100644
--- a/lib/tasks/gitlab/enable_namespaces.rake
+++ b/lib/tasks/gitlab/enable_namespaces.rake
@@ -42,7 +42,7 @@ namespace :gitlab do
username = user.email.match(/^[^@]*/)[0]
username.gsub!("+", ".")
- # return username if no mathes
+ # return username if no matches
return username unless User.find_by_username(username)
# look for same username
@@ -99,7 +99,7 @@ namespace :gitlab do
end
begin
- Gitlab::ProjectMover.new(project, '', group.path).execute
+ project.transfer(group.path)
puts "moved to #{new_path}".green
rescue
puts "failed moving to #{new_path}".red
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index bddbd7ef855..8fa89270854 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -2,53 +2,76 @@ namespace :gitlab do
namespace :import do
# How to use:
#
- # 1. copy your bare repos under git base_path
+ # 1. copy your bare repos under git repos_path
# 2. run bundle exec rake gitlab:import:repos RAILS_ENV=production
#
# Notes:
# * project owner will be a first admin
# * existing projects will be skipped
#
- desc "GITLAB | Import bare repositories from git_host -> base_path into GitLab project instance"
- task :repos => :environment do
+ desc "GITLAB | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance"
+ task repos: :environment do
git_base_path = Gitlab.config.gitlab_shell.repos_path
- repos_to_import = Dir.glob(git_base_path + '/*')
+ repos_to_import = Dir.glob(git_base_path + '/**/*.git')
namespaces = Namespace.pluck(:path)
repos_to_import.each do |repo_path|
- repo_name = File.basename repo_path
+ # strip repo base path
+ repo_path[0..git_base_path.length] = ''
- # Skip if group or user
- next if namespaces.include?(repo_name)
+ path = repo_path.sub(/\.git$/, '')
+ name = File.basename path
+ group_name = File.dirname path
+ group_name = nil if group_name == '.'
- # skip if not git repo
- next unless repo_name =~ /.git$/
+ # Skip if group or user
+ next if namespaces.include?(name)
- next if repo_name == 'gitolite-admin.git'
+ puts "Processing #{repo_path}".yellow
- path = repo_name.sub(/\.git$/, '')
+ if path =~ /.wiki\Z/
+ puts " * Skipping wiki repo"
+ next
+ end
project = Project.find_with_namespace(path)
- puts "Processing #{repo_name}".yellow
-
if project
- puts " * #{project.name} (#{repo_name}) exists"
+ puts " * #{project.name} (#{repo_path}) exists"
else
user = User.admins.first
project_params = {
- :name => path,
+ name: name,
+ path: name
}
+ # find group namespace
+ if group_name
+ group = Group.find_by_path(group_name)
+ # create group namespace
+ if !group
+ group = Group.new(:name => group_name)
+ group.path = group_name
+ group.owner = user
+ if group.save
+ puts " * Created Group #{group.name} (#{group.id})".green
+ else
+ puts " * Failed trying to create group #{group.name}".red
+ end
+ end
+ # set project group
+ project_params[:namespace_id] = group.id
+ end
+
project = Projects::CreateContext.new(user, project_params).execute
if project.valid?
- puts " * Created #{project.name} (#{repo_name})".green
+ puts " * Created #{project.name} (#{repo_path})".green
else
- puts " * Failed trying to create #{project.name} (#{repo_name})".red
+ puts " * Failed trying to create #{project.name} (#{repo_path})".red
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index c44016ef6e8..ea83efcd887 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -40,8 +40,8 @@ namespace :gitlab do
puts ""
puts "GitLab information".yellow
- puts "Version:\t#{Gitlab::Version}"
- puts "Revision:\t#{Gitlab::Revision}"
+ puts "Version:\t#{Gitlab::VERSION}"
+ puts "Revision:\t#{Gitlab::REVISION}"
puts "Directory:\t#{Rails.root}"
puts "DB Adapter:\t#{database_adapter}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
@@ -54,7 +54,7 @@ namespace :gitlab do
# check Gitolite version
- gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.repos_path}/../gitlab-shell/VERSION"
+ gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.hooks_path}/../VERSION"
if File.readable?(gitlab_shell_version_file)
gitlab_shell_version = File.read(gitlab_shell_version_file)
end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index bc0742564d0..2b730774e06 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -1,16 +1,18 @@
namespace :gitlab do
desc "GITLAB | Setup production application"
- task :setup => :environment do
- setup
+ task setup: :environment do
+ setup_db
end
- def setup
+ def setup_db
warn_user_is_not_gitlab
- puts "This will create the necessary database tables and seed the database."
- puts "You will lose any previous data stored in the database."
- ask_to_continue
- puts ""
+ unless ENV['force'] == 'yes'
+ puts "This will create the necessary database tables and seed the database."
+ puts "You will lose any previous data stored in the database."
+ ask_to_continue
+ puts ""
+ end
Rake::Task["db:setup"].invoke
Rake::Task["db:seed_fu"].invoke
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0ab8df1d094..0d7a390bc92 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -25,12 +25,14 @@ namespace :gitlab do
def setup
warn_user_is_not_gitlab
- puts "This will rebuild an authorized_keys file."
- puts "You will lose any data stored in /home/git/.ssh/authorized_keys."
- ask_to_continue
- puts ""
+ unless ENV['force'] == 'yes'
+ puts "This will rebuild an authorized_keys file."
+ puts "You will lose any data stored in authorized_keys file."
+ ask_to_continue
+ puts ""
+ end
- system("echo '# Managed by gitlab-shell' > /home/git/.ssh/authorized_keys")
+ Gitlab::Shell.new.remove_all_keys
Key.find_each(batch_size: 1000) do |key|
if Gitlab::Shell.new.add_key(key.shell_id, key.key)
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index cb4e34cc0d7..ac2c4577c77 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -16,7 +16,7 @@ namespace :gitlab do
# Check which OS is running
#
# It will primarily use lsb_relase to determine the OS.
- # It has fallbacks to Debian, SuSE and OS X.
+ # It has fallbacks to Debian, SuSE, OS X and systems running systemd.
def os_name
os_name = run("lsb_release -irs")
os_name ||= if File.readable?('/etc/system-release')
@@ -32,13 +32,16 @@ namespace :gitlab do
os_name ||= if os_x_version = run("sw_vers -productVersion")
"Mac OS X #{os_x_version}"
end
+ os_name ||= if File.readable?('/etc/os-release')
+ File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1]
+ end
os_name.try(:squish!)
end
# Prompt the user to input something
#
# message - the message to display before input
- # choices - array of strings of acceptible answers or nil for any answer
+ # choices - array of strings of acceptable answers or nil for any answer
#
# Returns the user's answer
def prompt(message, choices = nil)
@@ -49,10 +52,10 @@ namespace :gitlab do
answer
end
- # Runs the given command and matches the output agains the given pattern
+ # Runs the given command and matches the output against the given pattern
#
# Returns nil if nothing matched
- # Retunrs the MatchData if the pattern matched
+ # Returns the MatchData if the pattern matched
#
# see also #run
# see also String#match
@@ -77,7 +80,11 @@ namespace :gitlab do
end
def gid_for(group_name)
- Etc.getgrnam(group_name).gid
+ begin
+ Etc.getgrnam(group_name).gid
+ rescue ArgumentError # no group
+ "group #{group_name} doesn't exist"
+ end
end
def warn_user_is_not_gitlab
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index ad1bfb2e4b3..03b3fc5ea20 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -1,4 +1,4 @@
namespace :gitlab do
desc "GITLAB | Run both spinach and rspec"
- task :test => ['spinach', 'spec']
+ task test: ['spinach', 'spec']
end
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
new file mode 100644
index 00000000000..33271e1a2bb
--- /dev/null
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -0,0 +1,48 @@
+desc "GITLAB | Build internal ids for issues and merge requests"
+task migrate_iids: :environment do
+ puts 'Issues'.yellow
+ Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
+ begin
+ issue.set_iid
+ if issue.update_attribute(:iid, issue.iid)
+ print '.'
+ else
+ print 'F'
+ end
+ rescue
+ print 'F'
+ end
+ end
+
+ puts 'done'
+ puts 'Merge Requests'.yellow
+ MergeRequest.where(iid: nil).find_each(batch_size: 100) do |mr|
+ begin
+ mr.set_iid
+ if mr.update_attribute(:iid, mr.iid)
+ print '.'
+ else
+ print 'F'
+ end
+ rescue => ex
+ print 'F'
+ end
+ end
+
+ puts 'done'
+ puts 'Milestones'.yellow
+ Milestone.where(iid: nil).find_each(batch_size: 100) do |m|
+ begin
+ m.set_iid
+ if m.update_attribute(:iid, m.iid)
+ print '.'
+ else
+ print 'F'
+ end
+ rescue
+ print 'F'
+ end
+ end
+
+ puts 'done'
+end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index cf99951e027..d0e9dfe46a1 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,19 +1,19 @@
namespace :sidekiq do
desc "GITLAB | Stop sidekiq"
task :stop do
- run "bundle exec sidekiqctl stop #{pidfile}"
+ system "bundle exec sidekiqctl stop #{pidfile}"
end
desc "GITLAB | Start sidekiq"
task :start do
- run "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
+ system "nohup bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1 &"
end
-
+
desc "GITLAB | Start sidekiq with launchd on Mac OS X"
task :launchd do
- run "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
+ system "bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e #{Rails.env} -P #{pidfile} >> #{Rails.root.join("log", "sidekiq.log")} 2>&1"
end
-
+
def pidfile
Rails.root.join("tmp", "pids", "sidekiq.pid")
end
diff --git a/lib/tasks/travis.rake b/lib/tasks/travis.rake
index 6b434830803..bc1b8aadbc5 100644
--- a/lib/tasks/travis.rake
+++ b/lib/tasks/travis.rake
@@ -1,5 +1,5 @@
desc "Travis run tests"
-task :travis => [
+task travis: [
:spinach,
:spec
]