summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Vosmaer <contact@jacobvosmaer.nl>2015-06-05 19:23:38 +0200
committerJacob Vosmaer <contact@jacobvosmaer.nl>2015-06-05 19:23:38 +0200
commit7c09da9e8f90e66db89e2a7bdab89961b24d6224 (patch)
tree55f142252c77cddf3a6e9f9c3608045f2c09664c
parent0e8296fd5684a58183529fb736a60a394cf4bdbb (diff)
parentb6aa7cbccd90680fd13916846af40b9628e16657 (diff)
downloadgitlab-ce-7c09da9e8f90e66db89e2a7bdab89961b24d6224.tar.gz
Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq
-rw-r--r--CHANGELOG3
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee52
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee20
-rw-r--r--app/assets/stylesheets/generic/header.scss5
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss16
-rw-r--r--app/assets/stylesheets/generic/mobile.scss4
-rw-r--r--app/assets/stylesheets/generic/zen.scss5
-rw-r--r--app/assets/stylesheets/pages/note_form.scss6
-rw-r--r--app/controllers/projects/blob_controller.rb60
-rw-r--r--app/controllers/projects_controller.rb12
-rw-r--r--app/services/files/base_service.rb77
-rw-r--r--app/services/files/create_service.rb50
-rw-r--r--app/services/files/delete_service.rb32
-rw-r--r--app/services/files/update_service.rb42
-rw-r--r--app/services/projects/participants_service.rb4
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/header/_empty.html.haml2
-rw-r--r--app/views/layouts/project.html.haml8
-rw-r--r--app/views/projects/_issuable_form.html.haml2
-rw-r--r--app/views/projects/_md_preview.html.haml37
-rw-r--r--app/views/projects/blob/new.html.haml11
-rw-r--r--app/views/projects/issues/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml6
-rw-r--r--app/views/projects/milestones/_form.html.haml2
-rw-r--r--app/views/projects/notes/_form.html.haml7
-rw-r--r--app/views/projects/wikis/_form.html.haml3
-rw-r--r--app/views/shared/_file_highlight.html.haml3
-rw-r--r--config/gitlab.yml.example3
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--doc/integration/bitbucket.md54
-rw-r--r--doc/workflow/README.md1
-rw-r--r--doc/workflow/wip_merge_requests.md13
-rw-r--r--doc/workflow/wip_merge_requests/blocked_accept_button.pngbin0 -> 65231 bytes
-rw-r--r--doc/workflow/wip_merge_requests/mark_as_wip.pngbin0 -> 41549 bytes
-rw-r--r--doc/workflow/wip_merge_requests/unmark_as_wip.pngbin0 -> 32151 bytes
-rw-r--r--docker/README.md8
-rw-r--r--lib/api/files.rb50
-rw-r--r--lib/gitlab/o_auth/user.rb63
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb166
45 files changed, 570 insertions, 280 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 3df8e7ea516..624dd3a5c48 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,8 +1,10 @@
Please view this file on the master branch, on stable branches it's out of date.
v 7.12.0 (unreleased)
+ - Fix timeout when rendering file with thousands of lines.
- Don't notify users mentioned in code blocks or blockquotes.
- Omit link to generate labels if user does not have access to create them (Stan Hu)
+ - Show warning when a comment will add 10 or more people to the discussion.
- Disable changing of the source branch in merge request update API (Stan Hu)
- Shorten merge request WIP text.
- Add option to disallow users from registering any application to use GitLab as an OAuth provider
@@ -47,6 +49,7 @@ v 7.12.0 (unreleased)
- When remove project - move repository and schedule it removal
- Improve group removing logic
- Trigger create-hooks on backup restore task
+ - Add option to automatically link omniauth and LDAP identities
v 7.11.4
- Fix missing bullets when creating lists
diff --git a/Gemfile b/Gemfile
index 0009a8affba..0c1fff0bc14 100644
--- a/Gemfile
+++ b/Gemfile
@@ -34,7 +34,7 @@ gem "browser"
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.2'
+gem "gitlab_git", '~> 7.2.3'
# Ruby/Rack Git Smart-HTTP Server Handler
# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
diff --git a/Gemfile.lock b/Gemfile.lock
index a341a5df409..9d87de7d4e0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -225,7 +225,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
- gitlab_git (7.2.2)
+ gitlab_git (7.2.3)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -713,7 +713,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
- gitlab_git (~> 7.2.2)
+ gitlab_git (~> 7.2.3)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index fca2a290e2d..a7476146010 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -10,12 +10,17 @@ class @DropzoneInput
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
+ markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
- form_textarea.bind 'paste', (event) =>
+ form_textarea.on 'paste', (event) =>
handlePaste(event)
+ form_textarea.on "input", ->
+ hideReferencedUsers()
+ form_textarea.on "blur", ->
+ renderMarkdown()
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -45,16 +50,7 @@ class @DropzoneInput
form.find(".md-write-holder").hide()
form.find(".md-preview-holder").show()
- preview = form.find(".js-md-preview")
- mdText = form.find(".markdown-area").val()
- if mdText.trim().length is 0
- preview.text "Nothing to preview."
- else
- preview.text "Loading..."
- $.post($(this).data("url"),
- md_text: mdText
- ).success (previewData) ->
- preview.html previewData
+ renderMarkdown()
# Write button
$(document).off "click", ".js-md-write-button"
@@ -133,6 +129,40 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
+ hideReferencedUsers = ->
+ referencedUsers = form.find(".referenced-users")
+ referencedUsers.hide()
+
+ renderReferencedUsers = (users) ->
+ referencedUsers = form.find(".referenced-users")
+
+ if referencedUsers.length
+ if users.length >= 10
+ referencedUsers.show()
+ referencedUsers.find(".js-referenced-users-count").text users.length
+ else
+ referencedUsers.hide()
+
+ renderMarkdown = ->
+ preview = form.find(".js-md-preview")
+ mdText = form.find(".markdown-area").val()
+ if mdText.trim().length is 0
+ preview.text "Nothing to preview."
+ hideReferencedUsers()
+ else
+ preview.text "Loading..."
+ $.ajax(
+ type: "POST",
+ url: markdown_preview_path,
+ data: {
+ text: mdText
+ },
+ dataType: "json"
+ ).success (data) ->
+ preview.html data.body
+
+ renderReferencedUsers data.references.users
+
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 4eb3f3c03f3..7967892f856 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -10,7 +10,7 @@ GitLab.GfmAutoComplete =
# Team Members
Members:
- template: '<li>${username} <small>${name}</small></li>'
+ template: '<li>${username} <small>${title}</small></li>'
# Issues and MergeRequests
Issues:
@@ -34,7 +34,13 @@ GitLab.GfmAutoComplete =
searchKey: 'search'
callbacks:
beforeSave: (members) ->
- $.map members, (m) -> name: m.name, username: m.username, search: "#{m.username} #{m.name}"
+ $.map members, (m) ->
+ title = m.name
+ title += " (#{m.count})" if m.count
+
+ username: m.username
+ title: sanitize(title)
+ search: sanitize("#{m.username} #{m.name}")
input.atwho
at: '#'
@@ -44,7 +50,10 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (issues) ->
- $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
+ $.map issues, (i) ->
+ id: i.iid
+ title: sanitize(i.title)
+ search: "#{i.iid} #{i.title}"
input.atwho
at: '!'
@@ -54,7 +63,10 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (merges) ->
- $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
+ $.map merges, (m) ->
+ id: m.iid
+ title: sanitize(m.title)
+ search: "#{m.iid} #{m.title}"
input.one 'focus', =>
$.getJSON(@dataSource).done (data) ->
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 71afccba001..3e738929a78 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -8,6 +8,11 @@ header {
&.navbar-empty {
background: #FFF;
border-bottom: 1px solid #EEE;
+
+ .center-logo {
+ margin: 8px 0;
+ text-align: center;
+ }
}
&.navbar-gitlab {
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index eb39b6bb7e9..f94677d1925 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -52,6 +52,22 @@
transition: opacity 200ms ease-in-out;
}
+.md-area {
+ position: relative;
+}
+
+.md-header ul {
+ float: left;
+}
+
+.referenced-users {
+ padding: 10px 0;
+ color: #999;
+ margin-left: 10px;
+ margin-top: 1px;
+ margin-right: 130px;
+}
+
.md-preview-holder {
background: #FFF;
border: 1px solid #ddd;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index 74108c1f086..e61b39f1ac3 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -19,6 +19,10 @@
}
}
+ .referenced-users {
+ margin-right: 0;
+ }
+
.issues-filters,
.dash-projects-filters,
.check-all-holder {
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
index bcb8bbe3134..7ab01187a02 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -1,6 +1,4 @@
.zennable {
- position: relative;
-
.zen-toggle-comment {
display: none;
}
@@ -8,8 +6,9 @@
.zen-enter-link {
color: #888;
position: absolute;
- top: -26px;
+ top: 0px;
right: 4px;
+ line-height: 40px;
}
.zen-leave-link {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index a0522030785..203f9374cee 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -39,11 +39,8 @@
.new_note, .edit_note {
.buttons {
- float: left;
margin-top: 8px;
- }
- .clearfix {
- margin-bottom: 0;
+ margin-bottom: 3px;
}
.note-preview-holder {
@@ -82,7 +79,6 @@
.note-form-actions {
background: #F9F9F9;
- height: 45px;
.note-form-option {
margin-top: 8px;
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index b762518d377..100d3d3b317 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -13,27 +13,20 @@ class Projects::BlobController < Projects::ApplicationController
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update]
- before_action :after_edit_path, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
+ before_action :editor_variables, except: [:show, :preview, :diff]
+ before_action :after_edit_path, only: [:edit, :update]
def new
commit unless @repository.empty?
end
def create
- file_path = File.join(@path, File.basename(params[:file_name]))
- result = Files::CreateService.new(
- @project,
- current_user,
- params.merge(new_branch: sanitized_new_branch_name),
- @ref,
- file_path
- ).execute
+ result = Files::CreateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- ref = sanitized_new_branch_name.presence || @ref
- redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
+ redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
else
flash[:alert] = result[:message]
render :new
@@ -48,22 +41,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
- result = Files::UpdateService.
- new(
- @project,
- current_user,
- params.merge(new_branch: sanitized_new_branch_name),
- @ref,
- @path
- ).execute
+ result = Files::UpdateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
-
- if from_merge_request
- from_merge_request.reload_code
- end
-
redirect_to after_edit_path
else
flash[:alert] = result[:message]
@@ -80,12 +61,11 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
+ result = Files::DeleteService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to namespace_project_tree_path(@project.namespace, @project,
- @ref)
+ redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
else
flash[:alert] = result[:message]
render :show
@@ -135,7 +115,6 @@ class Projects::BlobController < Projects::ApplicationController
@id = params[:id]
@ref, @path = extract_ref(@id)
-
rescue InvalidPathError
not_found!
end
@@ -145,8 +124,8 @@ class Projects::BlobController < Projects::ApplicationController
if from_merge_request
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
- elsif sanitized_new_branch_name.present?
- namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
+ elsif @target_branch.present?
+ namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
namespace_project_blob_path(@project.namespace, @project, @id)
end
@@ -160,4 +139,25 @@ class Projects::BlobController < Projects::ApplicationController
def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
end
+
+ def editor_variables
+ @current_branch = @ref
+ @target_branch = (sanitized_new_branch_name || @ref)
+
+ @file_path =
+ if action_name.to_s == 'create'
+ File.join(@path, File.basename(params[:file_name]))
+ else
+ @path
+ end
+
+ @commit_params = {
+ file_path: @file_path,
+ current_branch: @current_branch,
+ target_branch: @target_branch,
+ commit_message: params[:commit_message],
+ file_content: params[:content],
+ file_content_encoding: params[:encoding]
+ }
+ end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 4ca5fc65459..be5968cd7b0 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -151,7 +151,17 @@ class ProjectsController < ApplicationController
end
def markdown_preview
- render text: view_context.markdown(params[:md_text])
+ text = params[:text]
+
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+ ext.analyze(text)
+
+ render json: {
+ body: view_context.markdown(text),
+ references: {
+ users: ext.users.map(&:username)
+ }
+ }
end
private
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 4d02752454e..f587ee266da 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -1,11 +1,34 @@
module Files
class BaseService < ::BaseService
- attr_reader :ref, :path
+ class ValidationError < StandardError; end
- def initialize(project, user, params, ref, path = nil)
- @project, @current_user, @params = project, user, params.dup
- @ref = ref
- @path = path
+ def execute
+ @current_branch = params[:current_branch]
+ @target_branch = params[:target_branch]
+ @commit_message = params[:commit_message]
+ @file_path = params[:file_path]
+ @file_content = if params[:file_content_encoding] == 'base64'
+ Base64.decode64(params[:file_content])
+ else
+ params[:file_content]
+ end
+
+ # Validate parameters
+ validate
+
+ # Create new branch if it different from current_branch
+ if @target_branch != @current_branch
+ create_target_branch
+ end
+
+ if sha = commit
+ after_commit(sha, @target_branch)
+ success
+ else
+ error("Something went wrong. Your changes were not committed")
+ end
+ rescue ValidationError => ex
+ error(ex.message)
end
private
@@ -14,11 +37,51 @@ module Files
project.repository
end
- def after_commit(sha)
+ def after_commit(sha, branch)
commit = repository.commit(sha)
- full_ref = 'refs/heads/' + (params[:new_branch] || ref)
+ full_ref = 'refs/heads/' + branch
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
end
+
+ def current_branch
+ @current_branch ||= params[:current_branch]
+ end
+
+ def target_branch
+ @target_branch ||= params[:target_branch]
+ end
+
+ def raise_error(message)
+ raise ValidationError.new(message)
+ end
+
+ def validate
+ allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+
+ unless allowed
+ raise_error("You are not allowed to push into this branch")
+ end
+
+ unless project.empty_repo?
+ unless repository.branch_names.include?(@current_branch)
+ raise_error("You can only create files if you are on top of a branch")
+ end
+
+ if @current_branch != @target_branch
+ if repository.branch_names.include?(@target_branch)
+ raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
+ end
+ end
+ end
+ end
+
+ def create_target_branch
+ result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
+
+ unless result[:status] == :success
+ raise_error("Something went wrong when we tried to create #{@target_branch} for you")
+ end
+ end
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 0a80455bc6b..91d715b2d63 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -2,58 +2,28 @@ require_relative "base_service"
module Files
class CreateService < Files::BaseService
- def execute
- allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
+ def commit
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
+ end
- unless allowed
- return error("You are not allowed to create file in this branch")
- end
+ def validate
+ super
- file_name = File.basename(path)
- file_path = path
+ file_name = File.basename(@file_path)
unless file_name =~ Gitlab::Regex.file_name_regex
- return error(
+ raise_error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_name_regex_message
)
end
- if project.empty_repo?
- # everything is ok because repo does not have a commits yet
- else
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, file_path)
+ unless project.empty_repo?
+ blob = repository.blob_at_branch(@current_branch, @file_path)
if blob
- return error("Your changes could not be committed, because file with such name exists")
- end
- end
-
- content =
- if params[:encoding] == 'base64'
- Base64.decode64(params[:content])
- else
- params[:content]
+ raise_error("Your changes could not be committed, because file with such name exists")
end
-
- sha = repository.commit_file(
- current_user,
- file_path,
- content,
- params[:commit_message],
- params[:new_branch] || ref
- )
-
-
- if sha
- after_commit(sha)
- success
- else
- error("Your changes could not be committed, because the file has been changed")
end
end
end
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 2281777604c..27c881c3430 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -2,36 +2,8 @@ require_relative "base_service"
module Files
class DeleteService < Files::BaseService
- def execute
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
-
- unless allowed
- return error("You are not allowed to push into this branch")
- end
-
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, path)
-
- unless blob
- return error("You can only edit text files")
- end
-
- sha = repository.remove_file(
- current_user,
- path,
- params[:commit_message],
- ref
- )
-
- if sha
- after_commit(sha)
- success
- else
- error("Your changes could not be committed, because the file has been changed")
- end
+ def commit
+ repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 013cc1ee322..a20903c6f02 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -2,46 +2,8 @@ require_relative "base_service"
module Files
class UpdateService < Files::BaseService
- def execute
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
-
- unless allowed
- return error("You are not allowed to push into this branch")
- end
-
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, path)
-
- unless blob
- return error("You can only edit text files")
- end
-
- content =
- if params[:encoding] == 'base64'
- Base64.decode64(params[:content])
- else
- params[:content]
- end
-
- sha = repository.commit_file(
- current_user,
- path,
- content,
- params[:commit_message],
- params[:new_branch] || ref
- )
-
- after_commit(sha)
- success
- rescue Gitlab::Satellite::CheckoutFailed => ex
- error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
- rescue Gitlab::Satellite::CommitFailed => ex
- error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
- rescue Gitlab::Satellite::PushFailed => ex
- error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
+ def commit
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index b91590a1a90..0004a399f47 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -38,13 +38,13 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
- { username: group.path, name: "#{group.name} (#{count})" }
+ { username: group.path, name: group.name, count: count }
end
end
def all_members
count = project.team.members.flatten.count
- [{ username: "all", name: "All Project and Group Members (#{count})" }]
+ [{ username: "all", name: "All Project and Group Members", count: count }]
end
end
end
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index b1a57d9824e..4d8c5656a25 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -15,8 +15,10 @@
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
- = yield(:meta_tags)
+ = yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
= render 'layouts/bootlint' if Rails.env.development?
+
+ = yield :scripts_head
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c1283734d25..54fcbca6bc6 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -22,5 +22,3 @@
= render "layouts/flash"
.clearfix
= yield
-
-= yield :embedded_scripts
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 155825cc4c2..ff23913d7d6 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -8,3 +8,5 @@
= render "layouts/header/public", title: header_title
= render 'layouts/page', sidebar: sidebar
+
+ = yield :scripts_body
diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml
index 16fbf6d4020..a52a3c8f0ef 100644
--- a/app/views/layouts/header/_empty.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,4 @@
%header.navbar.navbar-fixed-top.navbar-empty
.container
- %h4.center
+ .center-logo
= image_tag 'logo-white.png', width: 32, height: 32
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 4aeb9d397d2..03c7ba8c73f 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -2,7 +2,13 @@
- header_title project_title(@project)
- sidebar "project" unless sidebar
-- content_for :embedded_scripts do
+- content_for :scripts_head do
+ -if current_user
+ :javascript
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
+ window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}";
+
+- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
= render template: "layouts/application"
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 2c5b24b8130..491e2107da4 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -24,7 +24,7 @@
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
- = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
classes: 'description form-control'
.col-sm-12.hint
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index b869fd6e12a..a831481cf80 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,13 +1,24 @@
-%ul.nav.nav-tabs
- %li.active
- = link_to '#md-write-holder', class: 'js-md-write-button' do
- Write
- %li
- = link_to '#md-preview-holder', class: 'js-md-preview-button',
- data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do
- Preview
-%div
- .md-write-holder
- = yield
- .md.md-preview-holder.hide
- .js-md-preview{class: (preview_class if defined?(preview_class))}
+.md-area
+ .md-header.clearfix
+ %ul.nav.nav-tabs
+ %li.active
+ = link_to '#md-write-holder', class: 'js-md-write-button' do
+ Write
+ %li
+ = link_to '#md-preview-holder', class: 'js-md-preview-button' do
+ Preview
+
+ - if defined?(referenced_users) && referenced_users
+ %span.referenced-users.pull-left.hide
+ = icon('exclamation-triangle')
+ You are about to add
+ %strong
+ %span.js-referenced-users-count 0
+ people
+ to the discussion. Proceed with caution.
+
+ %div
+ .md-write-holder
+ = yield
+ .md.md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 9b1d03b820e..f7ddf74b4fc 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -6,11 +6,12 @@
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ - unless @project.empty_repo?
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 7d7217eb2a8..8d2564be55e 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -10,5 +10,3 @@
$('#issue_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
});
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 1c7160bce5f..be73f087449 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -8,5 +8,3 @@
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
});
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 9a2edbf0a8c..6792104569b 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -51,8 +51,6 @@
e.preventDefault();
});
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
-
:javascript
var merge_request
merge_request = new MergeRequest({
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index 906cc11dc67..bfd4ab6f3d8 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -49,7 +49,7 @@
.automerge_widget.cannot_be_merged.hide
%h4
- This pull request contains merge conflicts that must be resolved.
+ This merge request contains merge conflicts that must be resolved.
You can try it manually on the
%strong
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
@@ -63,14 +63,14 @@
.automerge_widget.work_in_progress.hide
%h4
- This request cannot be merged because it is marked as <strong>Work In Progress</strong>.
+ This merge request cannot be accepted because it is marked as Work In Progress.
%p
%button.btn.disabled{:type => 'button'}
%i.fa.fa-warning
Accept Merge Request
&nbsp;
- When the merge request is ready, remove the "WIP" prefix from the title to allow merging.
+ When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted.
.automerge_widget.unchecked
%p
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 95b7070ce5c..5650607f31f 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -50,5 +50,3 @@
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 2ada6cb6700..f28b3e9b508 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -5,7 +5,7 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note,
classes: 'note_text js-note-text'
@@ -15,10 +15,7 @@
.error-alert
.note-form-actions
- .buttons
+ .buttons.clearfix
= f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
-
-:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 9fbfa0b1aeb..2a8ceaa2844 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -41,6 +41,3 @@
- else
= f.submit 'Create page', class: "btn-create btn"
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
-
-:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index fba69dd0f3f..86921f0a777 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -4,7 +4,8 @@
- blob.data.lines.to_a.size.times do |index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
- = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
+ / We're not using `link_to` because it is too slow once we get to thousands of lines.
+ %a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"}
%i.fa.fa-link
= i
:preserve
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index c7f22b9388b..787b3ccfc56 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -192,6 +192,9 @@ production: &base
allow_single_sign_on: false
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
+ # Look up new users in LDAP servers. If a match is found (same uid), automatically
+ # link the omniauth identity with the LDAP account. (default: false)
+ auto_link_ldap_user: false
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index c234bd69e9a..1bd14a3a89f 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -88,6 +88,9 @@ end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
+Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil?
+Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil?
+Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
Settings.omniauth['providers'] ||= []
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index d82e1f8b41b..6a0fa4ce015 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -68,6 +68,8 @@ Bitbucket will generate an application ID and secret key for you to use.
1. Save the configuration file.
+1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```).
+
1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a Bitbucket icon below the regular sign in form.
@@ -80,43 +82,59 @@ To allow projects to be imported directly into GitLab, Bitbucket requires two ex
Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
-### Step 1: Known hosts
+### Step 1: Public key
-To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
+To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
-1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+
+1. Create a new SSH key:
```sh
- ssh git@bitbucket.org
+ sudo -u git -H ssh-keygen
```
-1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
+ When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
+ Make sure to use an **empty passphrase**.
+
+1. Configure SSH client to use your new key:
+
+ Open the SSH configuration file of the git user.
```sh
- The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
- RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
- Are you sure you want to continue connecting (yes/no)?
+ sudo editor /home/git/.ssh/config
```
-1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+ Add a host configuration for `bitbucket.org`.
+
+ ```sh
+ Host bitbucket.org
+ IdentityFile ~/.ssh/bitbucket_rsa
+ User git
+ ```
-1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2:
+### Step 2: Known hosts
-### Step 2: Public key
+To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
-To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
+1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
-If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+ ```sh
+ sudo -u git -H ssh bitbucket.org
+ ```
-1. Create a new SSH key:
+1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
```sh
- sudo -u git -H ssh-keygen
+ The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
+ RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+ Are you sure you want to continue connecting (yes/no)?
```
- When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
- Make sure to use an **empty passphrase**.
+1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+
+1. Your GitLab server is now able to connect to Bitbucket over SSH.
-2. Restart GitLab to allow it to find the new public key.
+1. Restart GitLab to allow it to find the new public key.
You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 89005e51958..4775be15040 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -16,3 +16,4 @@
- [Change your time zone](timezone.md)
- [Keyboard shortcuts](shortcuts.md)
- [Web Editor](web_editor.md)
+- ["Work In Progress" Merge Requests](wip_merge_requests.md)
diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md
new file mode 100644
index 00000000000..46035a5e6b6
--- /dev/null
+++ b/doc/workflow/wip_merge_requests.md
@@ -0,0 +1,13 @@
+# "Work In Progress" Merge Requests
+
+To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**.
+
+![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png)
+
+To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`.
+
+![Mark as WIP](wip_merge_requests/mark_as_wip.png)
+
+To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix.
+
+![Unark as WIP](wip_merge_requests/unmark_as_wip.png)
diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png
new file mode 100644
index 00000000000..4791e5de972
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png
new file mode 100644
index 00000000000..8fa83a201ac
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/mark_as_wip.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png
new file mode 100644
index 00000000000..d45e68f31c5
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png
Binary files differ
diff --git a/docker/README.md b/docker/README.md
index a73ccd0dba0..46b21348364 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -122,7 +122,7 @@ You can find all available options in [Omnibus GitLab documentation](https://git
### Upgrade GitLab with app and data images
-To updgrade GitLab to new versions, stop running container, create new docker image and container from that image.
+To upgrade GitLab to new versions, stop running container, create new docker image and container from that image.
It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory:
@@ -141,10 +141,12 @@ sudo docker rmi gitlab-app:7.8.1
### Publish images to Dockerhub
-Login to Dockerhub with `sudo docker login` and run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name):
+- Ensure the containers are running
+- Login to Dockerhub with `sudo docker login`
+- Run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name):
```bash
-sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app:7.10.1 sytse/gitlab-app:7.10.1
+sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1
sudo docker push sytse/gitlab-app:7.10.1
sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab_data sytse/gitlab_data
sudo docker push sytse/gitlab_data
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e0ea6d7dd1d..c7b30cf2f07 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -3,6 +3,26 @@ module API
class Files < Grape::API
before { authenticate! }
+ helpers do
+ def commit_params(attrs)
+ {
+ file_path: attrs[:file_path],
+ current_branch: attrs[:branch_name],
+ target_branch: attrs[:branch_name],
+ commit_message: attrs[:commit_message],
+ file_content: attrs[:content],
+ file_content_encoding: attrs[:encoding]
+ }
+ end
+
+ def commit_response(attrs)
+ {
+ file_path: attrs[:file_path],
+ branch_name: attrs[:branch_name],
+ }
+ end
+ end
+
resource :projects do
# Get file from repository
# File content is Base64 encoded
@@ -73,17 +93,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(201)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
@@ -105,17 +119,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
http_status = result[:http_status] || 400
render_api_error!(result[:message], http_status)
@@ -138,17 +146,11 @@ module API
required_attributes! [:file_path, :branch_name, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index ba5caed6131..c4971b5bcc6 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -46,6 +46,10 @@ module Gitlab
def gl_user
@user ||= find_by_uid_and_provider
+ if auto_link_ldap_user?
+ @user ||= find_or_create_ldap_user
+ end
+
if signup_enabled?
@user ||= build_new_user
end
@@ -55,6 +59,46 @@ module Gitlab
protected
+ def find_or_create_ldap_user
+ return unless ldap_person
+
+ # If a corresponding person exists with same uid in a LDAP server,
+ # set up a Gitlab user with dual LDAP and Omniauth identities.
+ if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider)
+ # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account.
+ user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ else
+ # No account in Gitlab yet: create it and add the LDAP identity
+ user = build_new_user
+ user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn)
+ end
+
+ user
+ end
+
+ def auto_link_ldap_user?
+ Gitlab.config.omniauth.auto_link_ldap_user
+ end
+
+ def creating_linked_ldap_user?
+ auto_link_ldap_user? && ldap_person
+ end
+
+ def ldap_person
+ return @ldap_person if defined?(@ldap_person)
+
+ # looks for a corresponding person with same uid in any of the configured LDAP providers
+ @ldap_person = Gitlab::LDAP::Config.providers.find do |provider|
+ adapter = Gitlab::LDAP::Adapter.new(provider)
+
+ Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
+ end
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person
+ end
+
def needs_blocking?
new? && block_after_signup?
end
@@ -64,7 +108,11 @@ module Gitlab
end
def block_after_signup?
- Gitlab.config.omniauth.block_auto_created_users
+ if creating_linked_ldap_user?
+ ldap_config.block_auto_created_users
+ else
+ Gitlab.config.omniauth.block_auto_created_users
+ end
end
def auth_hash=(auth_hash)
@@ -84,10 +132,19 @@ module Gitlab
end
def user_attributes
+ # Give preference to LDAP for sensitive information when creating a linked account
+ if creating_linked_ldap_user?
+ username = ldap_person.username
+ email = ldap_person.email.first
+ else
+ username = auth_hash.username
+ email = auth_hash.email
+ end
+
{
name: auth_hash.name,
- username: ::Namespace.clean_path(auth_hash.username),
- email: auth_hash.email,
+ username: ::Namespace.clean_path(username),
+ email: email,
password: auth_hash.password,
password_confirmation: auth_hash.password,
password_automatically_set: true
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 44cdd1e4fab..2a982e8b107 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -13,6 +13,7 @@ describe Gitlab::OAuth::User do
email: 'john@mail.com'
}
end
+ let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
describe :persisted? do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
@@ -32,31 +33,94 @@ describe Gitlab::OAuth::User do
let(:provider) { 'twitter' }
describe 'signup' do
- context "with allow_single_sign_on enabled" do
- before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+ shared_examples "to verify compliance with allow_single_sign_on" do
+ context "with allow_single_sign_on enabled" do
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
- it "creates a user from Omniauth" do
- oauth_user.save
+ it "creates a user from Omniauth" do
+ oauth_user.save
- expect(gl_user).to be_valid
- identity = gl_user.identities.first
- expect(identity.extern_uid).to eql uid
- expect(identity.provider).to eql 'twitter'
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
+ end
+ end
+
+ context "with allow_single_sign_on disabled (Default)" do
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: false }
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
end
end
- context "with allow_single_sign_on disabled (Default)" do
- it "throws an error" do
- expect{ oauth_user.save }.to raise_error StandardError
+ context "with auto_link_ldap_user disabled (default)" do
+ before { Gitlab.config.omniauth.stub auto_link_ldap_user: false }
+ include_examples "to verify compliance with allow_single_sign_on"
+ end
+
+ context "with auto_link_ldap_user enabled" do
+ before { Gitlab.config.omniauth.stub auto_link_ldap_user: true }
+
+ context "and a corresponding LDAP person" do
+ before do
+ ldap_user.stub(:uid) { uid }
+ ldap_user.stub(:username) { uid }
+ ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] }
+ ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(oauth_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+
+ it "creates a user with dual LDAP and omniauth identities" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql uid
+ expect(gl_user.email).to eql 'johndoe@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'twitter', extern_uid: uid }
+ ])
+ end
+ end
+
+ context "and LDAP user has an account already" do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+ it "adds the omniauth identity to the LDAP account" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'twitter', extern_uid: uid }
+ ])
+ end
+ end
+ end
+
+ context "and no corresponding LDAP person" do
+ before { allow(oauth_user).to receive(:ldap_person).and_return(nil) }
+
+ include_examples "to verify compliance with allow_single_sign_on"
end
end
+
end
describe 'blocking' do
let(:provider) { 'twitter' }
before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
- context 'signup' do
+ context 'signup with omniauth only' do
context 'dont block on create' do
before { Gitlab.config.omniauth.stub block_auto_created_users: false }
@@ -78,6 +142,64 @@ describe Gitlab::OAuth::User do
end
end
+ context 'signup with linked omniauth and LDAP account' do
+ before do
+ Gitlab.config.omniauth.stub auto_link_ldap_user: true
+ ldap_user.stub(:uid) { uid }
+ ldap_user.stub(:username) { uid }
+ ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] }
+ ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(oauth_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+ context 'dont block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+
+ context 'dont block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+
+
context 'sign-in' do
before do
oauth_user.save
@@ -103,6 +225,26 @@ describe Gitlab::OAuth::User do
expect(gl_user).not_to be_blocked
end
end
+
+ context 'dont block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
end
end
end