Commit b6aa7cbc authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents ef7c0945 87f9c475
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
......
......@@ -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)
......
......@@ -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)
......
......@@ -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
......
......@@ -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) ->
......
......@@ -8,6 +8,11 @@ header {
&.navbar-empty {
background: #FFF;
border-bottom: 1px solid #EEE;
.center-logo {
margin: 8px 0;
text-align: center;
}
}
&.navbar-gitlab {
......
......@@ -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;
......
......@@ -19,6 +19,10 @@
}
}
.referenced-users {
margin-right: 0;
}
.issues-filters,
.dash-projects-filters,
.check-all-holder {
......
.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 {
......
......@@ -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;
......
......@@ -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
......@@ -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
......
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
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......@@ -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
......@@ -22,5 +22,3 @@
= render "layouts/flash"
.clearfix
= yield
= yield :embedded_scripts
......@@ -8,3 +8,5 @@
= render "layouts/header/public", title: header_title
= render 'layouts/page', sidebar: sidebar
= yield :scripts_body
%header.navbar.navbar-fixed-top.navbar-empty
.container
%h4.center
.center-logo
= image_tag 'logo-white.png', width: 32, height: 32
......@@ -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"
......@@ -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
......
%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))}
......@@ -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,
......
......@@ -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}";
......@@ -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}";
......@@ -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({
......
......@@ -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
......
......@@ -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}";
......@@ -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}";
......@@ -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}";
......@@ -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
......
......@@ -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
......
......@@ -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'] ||= []
......
......@@ -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.
......@@ -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)
# "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)
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment