Commit ab4763b1 authored by Bryce Johnson's avatar Bryce Johnson

Merge branch 'master' into issues-filters-reset-btn

parents 5a62d811 ad599eb6
...@@ -31,15 +31,20 @@ v 8.12.0 (unreleased) ...@@ -31,15 +31,20 @@ v 8.12.0 (unreleased)
- Remove prefixes from transition CSS property (ClemMakesApps) - Remove prefixes from transition CSS property (ClemMakesApps)
- Add Sentry logging to API calls - Add Sentry logging to API calls
- Add BroadcastMessage API - Add BroadcastMessage API
- Use 'git update-ref' for safer web commits !6130
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps) - Remove unused mixins (ClemMakesApps)
- Add search to all issue board lists - Add search to all issue board lists
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Use JavaScript tooltips for mentions !5301 (winniehell) - Use JavaScript tooltips for mentions !5301 (winniehell)
- Add hover state to todos !5361 (winniehell)
- Fix icon alignment of star and fork buttons !5451 (winniehell)
- Fix alignment of icon buttons !5887 (winniehell)
- Fix markdown help references (ClemMakesApps) - Fix markdown help references (ClemMakesApps)
- Add last commit time to repo view (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps)
- Fix accessibility and visibility of project list dropdown button !6140 - Fix accessibility and visibility of project list dropdown button !6140
- Fix missing flash messages on service edit page (airatshigapov)
- Added project specific enable/disable setting for LFS !5997 - Added project specific enable/disable setting for LFS !5997
- Don't expose a user's token in the `/api/v3/user` API (!6047) - Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
...@@ -85,6 +90,7 @@ v 8.11.5 (unreleased) ...@@ -85,6 +90,7 @@ v 8.11.5 (unreleased)
- Fix member expiration date picker after update - Fix member expiration date picker after update
- Fix suggested colors options for new labels in the admin area. !6138 - Fix suggested colors options for new labels in the admin area. !6138
- Fix GitLab import button - Fix GitLab import button
- Remove gitorious from import_sources
v 8.11.4 v 8.11.4
- Fix resolving conflicts on forks. !6082 - Fix resolving conflicts on forks. !6082
...@@ -98,6 +104,10 @@ v 8.11.4 ...@@ -98,6 +104,10 @@ v 8.11.4
- Creating an issue through our API now emails label subscribers !5720 - Creating an issue through our API now emails label subscribers !5720
- Block concurrent updates for Pipeline - Block concurrent updates for Pipeline
- Don't create groups for unallowed users when importing projects - Don't create groups for unallowed users when importing projects
- Fix resolving conflicts on forks
- Fix diff commenting on merge requests created prior to 8.10
- Don't create groups for unallowed users when importing projects
- Scope webhooks/services that will run for confidential issues
- Fix issue boards leak private label names and descriptions - Fix issue boards leak private label names and descriptions
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
- Remove gitorious. !5866 - Remove gitorious. !5866
...@@ -180,9 +190,6 @@ v 8.11.0 ...@@ -180,9 +190,6 @@ v 8.11.0
- Update `timeago` plugin to use multiple string/locale settings - Update `timeago` plugin to use multiple string/locale settings
- Remove unused images (ClemMakesApps) - Remove unused images (ClemMakesApps)
- Get issue and merge request description templates from repositories - Get issue and merge request description templates from repositories
- Add hover state to todos !5361 (winniehell)
- Fix icon alignment of star and fork buttons !5451 (winniehell)
- Fix alignment of icon buttons !5887 (winniehell)
- Enforce 2FA restrictions on API authentication endpoints !5820 - Enforce 2FA restrictions on API authentication endpoints !5820
- Limit git rev-list output count to one in forced push check - Limit git rev-list output count to one in forced push check
- Show deployment status on merge requests with external URLs - Show deployment status on merge requests with external URLs
......
...@@ -10,21 +10,24 @@ ...@@ -10,21 +10,24 @@
ImporterStatus.prototype.initStatusPage = function() { ImporterStatus.prototype.initStatusPage = function() {
$('.js-add-to-import').off('click').on('click', (function(_this) { $('.js-add-to-import').off('click').on('click', (function(_this) {
return function(e) { return function(e) {
var $btn, $namespace_input, $target_field, $tr, id, new_namespace; var $btn, $namespace_input, $target_field, $tr, id, target_namespace;
$btn = $(e.currentTarget); $btn = $(e.currentTarget);
$tr = $btn.closest('tr'); $tr = $btn.closest('tr');
$target_field = $tr.find('.import-target'); $target_field = $tr.find('.import-target');
$namespace_input = $target_field.find('input'); $namespace_input = $target_field.find('input');
id = $tr.attr('id').replace('repo_', ''); id = $tr.attr('id').replace('repo_', '');
new_namespace = null; target_namespace = null;
if ($namespace_input.length > 0) { if ($namespace_input.length > 0) {
new_namespace = $namespace_input.prop('value'); target_namespace = $namespace_input.prop('value');
$target_field.empty().append(new_namespace + "/" + ($target_field.data('project_name'))); $target_field.empty().append(target_namespace + "/" + ($target_field.data('project_name')));
} }
$btn.disable().addClass('is-loading'); $btn.disable().addClass('is-loading');
return $.post(_this.import_url, { return $.post(_this.import_url, {
repo_id: id, repo_id: id,
new_namespace: new_namespace target_namespace: target_namespace
}, { }, {
dataType: 'script' dataType: 'script'
}); });
......
...@@ -13,7 +13,7 @@ module ServiceParams ...@@ -13,7 +13,7 @@ module ServiceParams
# `issue_events` and `merge_request_events` (singular!) # `issue_events` and `merge_request_events` (singular!)
# See app/helpers/services_helper.rb for how we # See app/helpers/services_helper.rb for how we
# make those event names plural as special case. # make those event names plural as special case.
:issues_events, :merge_requests_events, :issues_events, :confidential_issues_events, :merge_requests_events,
:notify_only_broken_builds, :notify_only_broken_pipelines, :notify_only_broken_builds, :notify_only_broken_pipelines,
:add_pusher, :send_from_committer_email, :disable_diffs, :add_pusher, :send_from_committer_email, :disable_diffs,
:external_wiki_url, :notify, :color, :external_wiki_url, :notify, :color,
......
class Import::BaseController < ApplicationController class Import::BaseController < ApplicationController
private private
def get_or_create_namespace def find_or_create_namespace(name, owner)
return current_user.namespace if name == owner
return current_user.namespace unless current_user.can_create_group?
begin begin
namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user) name = params[:target_namespace].presence || name
namespace = Group.create!(name: name, path: name, owner: current_user)
namespace.add_owner(current_user) namespace.add_owner(current_user)
namespace
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
namespace = Namespace.find_by_path_or_name(@target_namespace) Namespace.find_by_path_or_name(name)
unless current_user.can?(:create_projects, namespace)
@already_been_taken = true
return false
end
end end
namespace
end end
end end
...@@ -35,23 +35,20 @@ class Import::BitbucketController < Import::BaseController ...@@ -35,23 +35,20 @@ class Import::BitbucketController < Import::BaseController
end end
def create def create
@repo_id = params[:repo_id] || "" @repo_id = params[:repo_id].to_s
repo = client.project(@repo_id.gsub("___", "/")) repo = client.project(@repo_id.gsub('___', '/'))
@project_name = repo["slug"] @project_name = repo['slug']
@target_namespace = find_or_create_namespace(repo['owner'], client.user['user']['username'])
repo_owner = repo["owner"]
repo_owner = current_user.username if repo_owner == client.user["user"]["username"]
@target_namespace = params[:new_namespace].presence || repo_owner
namespace = get_or_create_namespace || (render and return)
unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user, access_params).execute
@access_denied = true render 'deploy_key' and return
render
return
end end
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute if current_user.can?(:create_projects, @target_namespace)
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
else
render 'unauthorized'
end
end end
private private
......
...@@ -41,14 +41,13 @@ class Import::GithubController < Import::BaseController ...@@ -41,14 +41,13 @@ class Import::GithubController < Import::BaseController
@repo_id = params[:repo_id].to_i @repo_id = params[:repo_id].to_i
repo = client.repo(@repo_id) repo = client.repo(@repo_id)
@project_name = repo.name @project_name = repo.name
@target_namespace = find_or_create_namespace(repo.owner.login, client.user.login)
repo_owner = repo.owner.login if current_user.can?(:create_projects, @target_namespace)
repo_owner = current_user.username if repo_owner == client.user.login @project = Gitlab::GithubImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
@target_namespace = params[:new_namespace].presence || repo_owner else
render 'unauthorized'
namespace = get_or_create_namespace || (render and return) end
@project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
end end
private private
......
...@@ -26,15 +26,14 @@ class Import::GitlabController < Import::BaseController ...@@ -26,15 +26,14 @@ class Import::GitlabController < Import::BaseController
def create def create
@repo_id = params[:repo_id].to_i @repo_id = params[:repo_id].to_i
repo = client.project(@repo_id) repo = client.project(@repo_id)
@project_name = repo["name"] @project_name = repo['name']
@target_namespace = find_or_create_namespace(repo['namespace']['path'], client.user['username'])
repo_owner = repo["namespace"]["path"] if current_user.can?(:create_projects, @target_namespace)
repo_owner = current_user.username if repo_owner == client.user["username"] @project = Gitlab::GitlabImport::ProjectCreator.new(repo, @target_namespace, current_user, access_params).execute
@target_namespace = params[:new_namespace].presence || repo_owner else
render 'unauthorized'
namespace = get_or_create_namespace || (render and return) end
@project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user, access_params).execute
end end
private private
......
...@@ -59,6 +59,7 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -59,6 +59,7 @@ class Projects::HooksController < Projects::ApplicationController
:pipeline_events, :pipeline_events,
:enable_ssl_verification, :enable_ssl_verification,
:issues_events, :issues_events,
:confidential_issues_events,
:merge_requests_events, :merge_requests_events,
:note_events, :note_events,
:push_events, :push_events,
......
...@@ -20,9 +20,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -20,9 +20,8 @@ class Projects::ServicesController < Projects::ApplicationController
def update def update
if @service.update_attributes(service_params[:service]) if @service.update_attributes(service_params[:service])
redirect_to( redirect_to(
edit_namespace_project_service_path(@project.namespace, @project, edit_namespace_project_service_path(@project.namespace, @project, @service.to_param),
@service.to_param, notice: notice: 'Successfully updated.'
'Successfully updated.')
) )
else else
render 'edit' render 'edit'
......
module ImportHelper module ImportHelper
def import_project_target(owner, name)
namespace = current_user.can_create_group? ? owner : current_user.namespace_path
"#{namespace}/#{name}"
end
def github_project_link(path_with_namespace) def github_project_link(path_with_namespace)
link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank' link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank'
end end
......
...@@ -8,7 +8,9 @@ module ServicesHelper ...@@ -8,7 +8,9 @@ module ServicesHelper
when "note" when "note"
"Event will be triggered when someone adds a comment" "Event will be triggered when someone adds a comment"
when "issue" when "issue"
"Event will be triggered when an issue is created/updated/merged" "Event will be triggered when an issue is created/updated/closed"
when "confidential_issue"
"Event will be triggered when a confidential issue is created/updated/closed"
when "merge_request" when "merge_request"
"Event will be triggered when a merge request is created/updated/merged" "Event will be triggered when a merge request is created/updated/merged"
when "build" when "build"
...@@ -19,7 +21,7 @@ module ServicesHelper ...@@ -19,7 +21,7 @@ module ServicesHelper
end end
def service_event_field_name(event) def service_event_field_name(event)
event = event.pluralize if %w[merge_request issue].include?(event) event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events" "#{event}_events"
end end
end end
...@@ -2,6 +2,7 @@ class ProjectHook < WebHook ...@@ -2,6 +2,7 @@ class ProjectHook < WebHook
belongs_to :project belongs_to :project
scope :issue_hooks, -> { where(issues_events: true) } scope :issue_hooks, -> { where(issues_events: true) }
scope :confidential_issue_hooks, -> { where(confidential_issues_events: true) }
scope :note_hooks, -> { where(note_events: true) } scope :note_hooks, -> { where(note_events: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) }
scope :build_hooks, -> { where(build_events: true) } scope :build_hooks, -> { where(build_events: true) }
......
...@@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base
default_value_for :push_events, true default_value_for :push_events, true
default_value_for :issues_events, false default_value_for :issues_events, false
default_value_for :confidential_issues_events, false
default_value_for :note_events, false default_value_for :note_events, false
default_value_for :merge_requests_events, false default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false default_value_for :tag_push_events, false
......
...@@ -39,7 +39,7 @@ class HipchatService < Service ...@@ -39,7 +39,7 @@ class HipchatService < Service
end end
def supported_events def supported_events
%w(push issue merge_request note tag_push build) %w(push issue confidential_issue merge_request note tag_push build)
end end
def execute(data) def execute(data)
......
...@@ -44,7 +44,7 @@ class SlackService < Service ...@@ -44,7 +44,7 @@ class SlackService < Service
end end
def supported_events def supported_events
%w(push issue merge_request note tag_push build wiki_page) %w(push issue confidential_issue merge_request note tag_push build wiki_page)
end end
def execute(data) def execute(data)
......
...@@ -149,7 +149,7 @@ class Repository ...@@ -149,7 +149,7 @@ class Repository
return false unless target return false unless target
GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
rugged.branches.create(branch_name, target) update_ref!(ref, target, oldrev)
end end
after_create_branch after_create_branch
...@@ -181,7 +181,7 @@ class Repository ...@@ -181,7 +181,7 @@ class Repository
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
rugged.branches.delete(branch_name) update_ref!(ref, newrev, oldrev)
end end
after_remove_branch after_remove_branch
...@@ -215,6 +215,21 @@ class Repository ...@@ -215,6 +215,21 @@ class Repository
rugged.references.exist?(ref) rugged.references.exist?(ref)
end end
def update_ref!(name, newrev, oldrev)
# We use 'git update-ref' because libgit2/rugged currently does not
# offer 'compare and swap' ref updates. Without compare-and-swap we can
# (and have!) accidentally reset the ref to an earlier state, clobbering
# commits. See also https://github.com/libgit2/libgit2/issues/1534.
command = %w[git update-ref --stdin -z]
_, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
end
return if status.zero?
raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.")
end
# Makes sure a commit is kept around when Git garbage collection runs. # Makes sure a commit is kept around when Git garbage collection runs.
# Git GC will delete commits from the repository that are no longer in any # Git GC will delete commits from the repository that are no longer in any
# branches or tags, but we want to keep some of these commits around, for # branches or tags, but we want to keep some of these commits around, for
...@@ -1014,15 +1029,10 @@ class Repository ...@@ -1014,15 +1029,10 @@ class Repository
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
update_autocrlf_option update_autocrlf_option
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
target_branch = find_branch(branch) target_branch = find_branch(branch)
was_empty = empty? was_empty = empty?
if !was_empty && target_branch
oldrev = target_branch.target.id
end
# Make commit # Make commit
newrev = yield(ref) newrev = yield(ref)
...@@ -1030,24 +1040,15 @@ class Repository ...@@ -1030,24 +1040,15 @@ class Repository
raise CommitError.new('Failed to create commit') raise CommitError.new('Failed to create commit')
end end
oldrev = rugged.lookup(newrev).parent_ids.first || Gitlab::Git::BLANK_SHA
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty || !target_branch update_ref!(ref, newrev, oldrev)
# Create branch
rugged.references.create(ref, newrev)
if was_empty || !target_branch
# If repo was empty expire cache # If repo was empty expire cache
after_create if was_empty after_create if was_empty
after_create_branch after_create_branch
else
# Update head
current_head = find_branch(branch).target.id
# Make sure target branch was not changed during pre-receive hook
if current_head == oldrev
rugged.references.update(ref, newrev)
else
raise CommitError.new('Commit was rejected because branch received new push')
end
end end
end end
......
...@@ -7,6 +7,7 @@ class Service < ActiveRecord::Base ...@@ -7,6 +7,7 @@ class Service < ActiveRecord::Base
default_value_for :active, false default_value_for :active, false
default_value_for :push_events, true default_value_for :push_events, true
default_value_for :issues_events, true default_value_for :issues_events, true
default_value_for :confidential_issues_events, true
default_value_for :merge_requests_events, true default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true default_value_for :tag_push_events, true
default_value_for :note_events, true default_value_for :note_events, true
...@@ -33,6 +34,7 @@ class Service < ActiveRecord::Base ...@@ -33,6 +34,7 @@ class Service < ActiveRecord::Base
scope :push_hooks, -> { where(push_events: true, active: true) } scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
scope :issue_hooks, -> { where(issues_events: true, active: true) } scope :issue_hooks, -> { where(issues_events: true, active: true) }
scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) }
...@@ -100,7 +102,7 @@ class Service < ActiveRecord::Base ...@@ -100,7 +102,7 @@ class Service < ActiveRecord::Base
end end
def supported_events def supported_events
%w(push tag_push issue merge_request wiki_page) %w(push tag_push issue confidential_issue merge_request wiki_page)
end end
def execute(data) def execute(data)
......
...@@ -15,8 +15,9 @@ module Issues ...@@ -15,8 +15,9 @@ module Issues
def execute_hooks(issue, action = 'open') def execute_hooks(issue, action = 'open')
issue_data = hook_data(issue, action) issue_data = hook_data(issue, action)
issue.project.execute_hooks(issue_data, :issue_hooks) hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
issue.project.execute_services(issue_data, :issue_hooks) issue.project.execute_hooks(issue_data, hooks_scope)
issue.project.execute_services(issue_data, hooks_scope)
end end
end end
end end
- if @already_been_taken - if @project.persisted?
:plain
tr = $("tr#repo_#{@repo_id}")
target_field = tr.find(".import-target")
import_button = tr.find(".btn-import")
origin_target = target_field.text()
project_name = "#{@project_name}"
origin_namespace = "#{@target_namespace}"
target_field.empty()
target_field.append("<p class='alert alert-danger'>This namespace already been taken! Please choose another one</p>")
target_field.append("<input type='text' name='target_namespace' />")
target_field.append("/" + project_name)
target_field.data("project_name", project_name)
target_field.find('input').prop("value", origin_namespace)
import_button.enable().removeClass('is-loading')
- elsif @access_denied
:plain
job = $("tr#repo_#{@repo_id}")
job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
- elsif @project.persisted?
:plain :plain
job = $("tr#repo_#{@repo_id}") job = $("tr#repo_#{@repo_id}")
job.attr("id", "project_#{@project.id}") job.attr("id", "project_#{@project.id}")
......
:plain
tr = $("tr#repo_#{@repo_id}")
target_field = tr.find(".import-target")
import_button = tr.find(".btn-import")
origin_target = target_field.text()
project_name = "#{@project_name}"
origin_namespace = "#{@target_namespace.path}"
target_field.empty()
target_field.append("<p class='alert alert-danger'>This namespace has already been taken! Please choose another one.</p>")
target_field.append("<input type='text' name='target_namespace' />")
target_field.append("/" + project_name)
target_field.data("project_name", project_name)
target_field.find('input').prop("value", origin_namespace)
import_button.enable().removeClass('is-loading')
:plain
job = $("tr#repo_#{@repo_id}")
job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>")
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
%td %td
= link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
%td.import-target %td.import-target
= "#{repo["owner"]}/#{repo["slug"]}" = import_project_target(repo['owner'], repo['slug'])
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import Import
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
%td %td
= github_project_link(repo.full_name) = github_project_link(repo.full_name)
%td.import-target %td.import-target
= repo.full_name = import_project_target(repo.owner.login, repo.name)
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import Import
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
%td %td
= link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
%td.import-target %td.import-target
= repo["path_with_namespace"] = import_project_target(repo['namespace']['path'], repo['name'])
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import Import
......
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
%br %br
%span.descr %span.descr
Builds need to be configured to enable this feature. Builds need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.col-md-8.col-lg-7 .col-md-8.col-lg-7
%strong.light-header= hook.url %strong.light-header= hook.url
%div %div
- %w(push_events tag_push_events issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger| - %w(push_events tag_push_events issues_events confidential_issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger|
- if hook.send(trigger) - if hook.send(trigger)
%span.label.label-gray.deploy-project-label= trigger.titleize %span.label.label-gray.deploy-project-label= trigger.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5 .col-md-4.col-lg-5.text-right-lg.prepend-top-5
......
...@@ -47,8 +47,9 @@ ...@@ -47,8 +47,9 @@
Note that pushing to GitLab requires write access to this repository. Note that pushing to GitLab requires write access to this repository.
%p %p
%strong Tip: %strong Tip:
= succeed '.' do
You can also checkout merge requests locally by You can also checkout merge requests locally by
%a{href: 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/workflow/merge_requests.md#checkout-merge-requests-locally', target: '_blank'} following these guidelines = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank'
:javascript :javascript
$(function(){ $(function(){
......
...@@ -51,6 +51,13 @@ ...@@ -51,6 +51,13 @@
%strong Issues events %strong Issues events
%p.light %p.light
This URL will be triggered when an issue is created/updated/merged This URL will be triggered when an issue is created/updated/merged
%li
= f.check_box :confidential_issues_events, class: 'pull-left'
.prepend-left-20
= f.label :confidential_issues_events, class: 'list-label' do
%strong Confidential Issues events
%p.light
This URL will be triggered when a confidential issue is created/updated/merged
%li %li
= f.check_box :merge_requests_events, class: 'pull-left' = f.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20 .prepend-left-20
......
class AddConfidentialIssuesEventsToWebHooks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default :web_hooks, :confidential_issues_events, :boolean, default: false, allow_null: false
end
def down
remove_column :web_hooks, :confidential_issues_events
end
end
class AddConfidentialIssuesEventsToServices < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default :services, :confidential_issues_events, :boolean, default: true, allow_null: false
end
def down
remove_column :services, :confidential_issues_events
end
end
class SetConfidentialIssuesEventsOnWebhooks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
update_column_in_batches(:web_hooks, :confidential_issues_events, true) do |table, query|
query.where(table[:issues_events].eq(true))
end
end
def down
# noop
end
end
class DropGitoriousFieldFromApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# After the deploy the caches will be cold anyway
DOWNTIME = false
def up
require 'yaml'
import_sources = connection.execute('SELECT import_sources FROM application_settings;')
yaml = if Gitlab::Database.postgresql?
import_sources.values[0][0]
else
return unless import_sources.first
import_sources.first[0]
end
yaml = YAML.safe_load(yaml)
yaml.delete 'gitorious'
# No need for a WHERE clause as there is only one
connection.execute("UPDATE application_settings SET import_sources = #{update_yaml(yaml)}")
end
def down
# noop, gitorious still yields a 404 anyway
end
private
def connection
ActiveRecord::Base.connection
end
def update_yaml(yaml)
connection.quote(YAML.dump(yaml))
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160831223750) do ActiveRecord::Schema.define(version: 20160901141443) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -922,6 +922,7 @@ ActiveRecord::Schema.define(version: 20160831223750) do ...@@ -922,6 +922,7 @@ ActiveRecord::Schema.define(version: 20160831223750) do
t.boolean "default", default: false t.boolean "default", default: false
t.boolean "wiki_page_events", default: true t.boolean "wiki_page_events", default: true
t.boolean "pipeline_events", default: false, null: false t.boolean "pipeline_events", default: false, null: false
t.boolean "confidential_issues_events", default: true, null: false
end end
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
...@@ -1137,6 +1138,7 @@ ActiveRecord::Schema.define(version: 20160831223750) do ...@@ -1137,6 +1138,7 @@ ActiveRecord::Schema.define(version: 20160831223750) do
t.boolean "wiki_page_events", default: false, null: false t.boolean "wiki_page_events", default: false, null: false
t.string "token" t.string "token"
t.boolean "pipeline_events", default: false, null: false t.boolean "pipeline_events", default: false, null: false
t.boolean "confidential_issues_events", default: false, null: false
end end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
......
...@@ -30,7 +30,8 @@ This is the universal solution which works with any type of executor ...@@ -30,7 +30,8 @@ This is the universal solution which works with any type of executor
## SSH keys when using the Docker executor ## SSH keys when using the Docker executor
You will first need to create an SSH key pair. For more information, follow the You will first need to create an SSH key pair. For more information, follow the
instructions to [generate an SSH key](../../ssh/README.md). instructions to [generate an SSH key](../../ssh/README.md). Do not add a comment
to the SSH key, or the `before_script` will prompt for a passphrase.
Then, create a new **Secret Variable** in your project settings on GitLab Then, create a new **Secret Variable** in your project settings on GitLab
following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY` following **Settings > Variables**. As **Key** add the name `SSH_PRIVATE_KEY`
......
...@@ -25,7 +25,3 @@ Add all the information that you'd like to include in your file: ...@@ -25,7 +25,3 @@ Add all the information that you'd like to include in your file:
Add a commit message based on what you just added and then click on "commit changes": Add a commit message based on what you just added and then click on "commit changes":
![Commit changes](basicsimages/commit_changes.png) ![Commit changes](basicsimages/commit_changes.png)
### Note
Besides its regular files, every directory needs a README.md or README.html file which works like an index, telling
what the directory is about. It's the first document you'll find when you open a directory.
...@@ -23,9 +23,9 @@ Create merge requests and review code. ...@@ -23,9 +23,9 @@ Create merge requests and review code.
- [Fork a project and contribute to it](../workflow/forking_workflow.md) - [Fork a project and contribute to it](../workflow/forking_workflow.md)
- [Create a new merge request](../gitlab-basics/add-merge-request.md) - [Create a new merge request](../gitlab-basics/add-merge-request.md)
- [Automatically close issues from merge requests](../customization/issue_closing.md) - [Automatically close issues from merge requests](../customization/issue_closing.md)
- [Automatically merge when your builds succeed](../workflow/merge_when_build_succeeds.md) - [Automatically merge when your builds succeed](../user/project/merge_requests/merge_when_build_succeeds.md)
- [Revert any commit](../workflow/revert_changes.md) - [Revert any commit](../user/project/merge_requests/revert_changes.md)
- [Cherry-pick any commit](../workflow/cherry_pick_changes.md) - [Cherry-pick any commit](../user/project/merge_requests/cherry_pick_changes.md)
## Test and Deploy ## Test and Deploy
......
# Merge Requests
Merge requests allow you to exchange changes you made to source code and
collaborate with other people on the same project.
## Authorization for merge requests
There are two main ways to have a merge request flow with GitLab:
1. Working with [protected branches][] in a single repository
1. Working with forks of an authoritative project
[Learn more about the authorization for merge requests.](merge_requests/authorization_for_merge_requests.md)
## Cherry-pick changes
Cherry-pick any commit in the UI by simply clicking the **Cherry-pick** button
in a merged merge requests or a commit.
[Learn more about cherry-picking changes.](merge_requests/cherry_pick_changes.md)
## Merge when build succeeds
When reviewing a merge request that looks ready to merge but still has one or
more CI builds running, you can set it to be merged automatically when all
builds succeed. This way, you don't have to wait for the builds to finish and
remember to merge the request manually.
[Learn more about merging when build succeeds.](merge_requests/merge_when_build_succeeds.md)
## Resolve discussion comments in merge requests reviews
Keep track of the progress during a code review with resolving comments.
Resolving comments prevents you from forgetting to address feedback and lets
you hide discussions that are no longer relevant.
[Read more about resolving discussion comments in merge requests reviews.](merge_requests/merge_request_discussion_resolution.md)
## Resolve conflicts
When a merge request has conflicts, GitLab may provide the option to resolve
those conflicts in the GitLab UI.
[Learn more about resolving merge conflicts in the UI.](merge_requests/resolve_conflicts.md)
## Revert changes
GitLab implements Git's powerful feature to revert any commit with introducing
a **Revert** button in merge requests and commit details.
[Learn more about reverting changes in the UI](merge_requests/revert_changes.md)
## Merge requests versions
Every time you push to a branch that is tied to a merge request, a new version
of merge request diff is created. When you visit a merge request that contains
more than one pushes, you can select and compare the versions of those merge
request diffs.
[Read more about the merge requests versions.](merge_requests/versions.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 as a **Work In Progress**.
[Learn more about settings a merge request as "Work In Progress".](merge_requests/work_in_progress_merge_requests.md)
## Ignore whitespace changes in Merge Request diff view
If you click the **Hide whitespace changes** button, you can see the diff
without whitespace changes (if there are any). This is also working when on a
specific commit page.
![MR diff](merge_requests/img/merge_request_diff.png)
>**Tip:**
You can append `?w=1` while on the diffs page of a merge request to ignore any
whitespace changes.
## Tips
Here are some tips that will help you be more efficient with merge requests in
the command line.
> **Note:**
This section might move in its own document in the future.
### Checkout merge requests locally
A merge request contains all the history from a repository, plus the additional
commits added to the branch associated with the merge request. Here's a few
tricks to checkout a merge request locally.
#### Checkout locally by adding a git alias
Add the following alias to your `~/.gitconfig`:
```
[alias]
mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
```
Now you can check out a particular merge request from any repository and any
remote. For example, to check out the merge request with ID 5 as shown in GitLab
from the `upstream` remote, do:
```
git mr upstream 5
```
This will fetch the merge request into a local `mr-upstream-5` branch and check
it out.
#### Checkout locally by modifying `.git/config` for a given repository
Locate the section for your GitLab remote in the `.git/config` file. It looks
like this:
```
[remote "origin"]
url = https://gitlab.com/gitlab-org/gitlab-ce.git
fetch = +refs/heads/*:refs/remotes/origin/*
```
You can open the file with:
```
git config -e
```
Now add the following line to the above section:
```
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
```
In the end, it should look like this:
```
[remote "origin"]
url = https://gitlab.com/gitlab-org/gitlab-ce.git
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
```
Now you can fetch all the merge requests:
```
git fetch origin
...
From https://gitlab.com/gitlab-org/gitlab-ce.git
* [new ref] refs/merge-requests/1/head -> origin/merge-requests/1
* [new ref] refs/merge-requests/2/head -> origin/merge-requests/2
...
```
And to check out a particular merge request:
```
git checkout origin/merge-requests/1
```
[protected branches]: protected_branches.md
# Authorization for Merge requests
There are two main ways to have a merge request flow with GitLab:
1. Working with [protected branches] in a single repository.
1. Working with forks of an authoritative project.
## Protected branch flow
With the protected branch flow everybody works within the same GitLab project.
The project maintainers get Master access and the regular developers get
Developer access.
The maintainers mark the authoritative branches as 'Protected'.
The developers push feature branches to the project and create merge requests
to have their feature branches reviewed and merged into one of the protected
branches.
By default, only users with Master access can merge changes into a protected
branch.
**Advantages**
- Fewer projects means less clutter.
- Developers need to consider only one remote repository.
**Disadvantages**
- Manual setup of protected branch required for each new project
## Forking workflow
With the forking workflow the maintainers get Master access and the regular
developers get Reporter access to the authoritative repository, which prohibits
them from pushing any changes to it.
Developers create forks of the authoritative project and push their feature
branches to their own forks.
To get their changes into master they need to create a merge request across
forks.
**Advantages**
- In an appropriately configured GitLab group, new projects automatically get
the required access restrictions for regular developers: fewer manual steps
to configure authorization for new projects.
**Disadvantages**
- The project need to keep their forks up to date, which requires more advanced
Git skills (managing multiple remotes).
[protected branches]: ../protected_branches.md
# Cherry-pick changes
> [Introduced][ce-3514] in GitLab 8.7.
---
GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
with introducing a **Cherry-pick** button in Merge Requests and commit details.
## Cherry-picking a Merge Request
After the Merge Request has been merged, a **Cherry-pick** button will be available
to cherry-pick the changes introduced by that Merge Request:
![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png)
---
You can cherry-pick the changes directly into the selected branch or you can opt to
create a new Merge Request with the cherry-pick changes:
![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png)
## Cherry-picking a Commit
You can cherry-pick a Commit from the Commit details page:
![Cherry-pick commit](img/cherry_pick_changes_commit.png)
---
Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes
directly into the target branch or create a new Merge Request to cherry-pick the
changes:
![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png)
---
Please note that when cherry-picking merge commits, the mainline will always be the
first parent. If you want to use a different mainline then you need to do that
from the command line.
Here is a quick example to cherry-pick a merge commit using the second parent as the
mainline:
```bash
git cherry-pick -m 2 7a39eb0
```
[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request"
[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation"
# Merge When Build Succeeds
When reviewing a merge request that looks ready to merge but still has one or
more CI builds running, you can set it to be merged automatically when all
builds succeed. This way, you don't have to wait for the builds to finish and
remember to merge the request manually.
![Enable](img/merge_when_build_succeeds_enable.png)
When you hit the "Merge When Build Succeeds" button, the status of the merge
request will be updated to represent the impending merge. If you cannot wait
for the build to succeed and want to merge immediately, this option is available
in the dropdown menu on the right of the main button.
Both team developers and the author of the merge request have the option to
cancel the automatic merge if they find a reason why it shouldn't be merged
after all.
![Status](img/merge_when_build_succeeds_status.png)
When the build succeeds, the merge request will automatically be merged. When
the build fails, the author gets a chance to retry any failed builds, or to
push new commits to fix the failure.
When the builds are retried and succeed on the second try, the merge request
will automatically be merged after all. When the merge request is updated with
new commits, the automatic merge is automatically canceled to allow the new
changes to be reviewed.
## Only allow merge requests to be merged if the build succeeds
> **Note:**
You need to have builds configured to enable this feature.
You can prevent merge requests from being merged if their build did not succeed.
Navigate to your project's settings page, select the
**Only allow merge requests to be merged if the build succeeds** check box and
hit **Save** for the changes to take effect.
![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png)
From now on, every time the build fails you will not be able to merge the merge
request from the UI, until you make the build pass.
![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png)
# Reverting changes
> [Introduced][ce-1990] in GitLab 8.5.
---
GitLab implements Git's powerful feature to [revert any commit][git-revert]
with introducing a **Revert** button in Merge Requests and commit details.
## Reverting a Merge Request
_**Note:** The **Revert** button will only be available for Merge Requests
created since GitLab 8.5. However, you can still revert a Merge Request
by reverting the merge commit from the list of Commits page._
After the Merge Request has been merged, a **Revert** button will be available
to revert the changes introduced by that Merge Request:
![Revert Merge Request](img/revert_changes_mr.png)
---
You can revert the changes directly into the selected branch or you can opt to
create a new Merge Request with the revert changes:
![Revert Merge Request modal](img/revert_changes_mr_modal.png)
---
After the Merge Request has been reverted, the **Revert** button will not be
available anymore.
## Reverting a Commit
You can revert a Commit from the Commit details page:
![Revert commit](img/revert_changes_commit.png)
---
Similar to reverting a Merge Request, you can opt to revert the changes
directly into the target branch or create a new Merge Request to revert the
changes:
![Revert commit modal](img/revert_changes_commit_modal.png)
---
After the Commit has been reverted, the **Revert** button will not be available
anymore.
Please note that when reverting merge commits, the mainline will always be the
first parent. If you want to use a different mainline then you need to do that
from the command line.
Here is a quick example to revert a merge commit using the second parent as the
mainline:
```bash
git revert -m 2 7a39eb0
```
[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request"
[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation"
# Merge requests versions
> Will be [introduced][ce-5467] in GitLab 8.12.
Every time you push to a branch that is tied to a merge request, a new version
of merge request diff is created. When you visit a merge request that contains
more than one pushes, you can select and compare the versions of those merge
request diffs.
By default, the latest version of changes is shown. However, you
can select an older one from version dropdown.
![Merge Request Versions](img/versions.png)
---
>**Note:**
Merge request versions are based on push not on commit. So, if you pushed 5
commits in a single push, it will be a single option in the dropdown. If you
pushed 5 times, that will count for 5 options.
[ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467
# "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](img/wip_blocked_accept_button.png)
To mark a merge request a Work In Progress, simply start its title with `[WIP]`
or `WIP:`.
![Mark as WIP](img/wip_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](img/wip_unmark_as_wip.png)
# GitLab Web Editor
Sometimes it's easier to make quick changes directly from the GitLab interface
than to clone the project and use the Git command line tool. In this feature
highlight we look at how you can create a new file, directory, branch or
tag from the file browser. All of these actions are available from a single
dropdown menu.
## Create a file
From a project's files page, click the '+' button to the right of the branch selector.
Choose **New file** from the dropdown.
![New file dropdown menu](img/web_editor_new_file_dropdown.png)
---
Enter a file name in the **File name** box. Then, add file content in the editor
area. Add a descriptive commit message and choose a branch. The branch field
will default to the branch you were viewing in the file browser. If you enter
a new branch name, a checkbox will appear allowing you to start a new merge
request after you commit the changes.
When you are satisfied with your new file, click **Commit Changes** at the bottom.
![Create file editor](img/web_editor_new_file_editor.png)
### Template dropdowns
When starting a new project, there are some common files which the new project
might need too. Therefore a message will be displayed by GitLab to make this
easy for you.
![First file for your project](img/web_editor_template_dropdown_first_file.png)
When clicking on either `LICENSE` or `.gitignore`, a dropdown will be displayed
to provide you with a template which might be suitable for your project.
![MIT license selected](img/web_editor_template_dropdown_mit_license.png)
The license, changelog, contribution guide, or `.gitlab-ci.yml` file could also
be added through a button on the project page. In the example below the license
has already been created, which creates a link to the license itself.
![New file button](img/web_editor_template_dropdown_buttons.png)
>**Note:**
The **Set up CI** button will not appear on an empty repository. You have to at
least add a file in order for the button to show up.
## Upload a file
The ability to create a file is great when the content is text. However, this
doesn't work well for binary data such as images, PDFs or other file types. In
this case you need to upload a file.
From a project's files page, click the '+' button to the right of the branch
selector. Choose **Upload file** from the dropdown.
![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png)
---
Once the upload dialog pops up there are two ways to upload your file. Either
drag and drop a file on the pop up or use the **click to upload** link. A file
preview will appear once you have selected a file to upload.
Enter a commit message, choose a branch, and click **Upload file** when you are
ready.
![Upload file dialog](img/web_editor_upload_file_dialog.png)
## Create a directory
To keep files in the repository organized it is often helpful to create a new
directory.
From a project's files page, click the '+' button to the right of the branch selector.
Choose **New directory** from the dropdown.
![New directory dropdown](img/web_editor_new_directory_dropdown.png)
---
In the new directory dialog enter a directory name, a commit message and choose
the target branch. Click **Create directory** to finish.
![New directory dialog](img/web_editor_new_directory_dialog.png)
## Create a new branch
There are multiple ways to create a branch from GitLab's web interface.
### Create a new branch from an issue
> [Introduced][ce-2808] in GitLab 8.6.
In case your development workflow dictates to have an issue for every merge
request, you can quickly create a branch right on the issue page which will be
tied with the issue itself. You can see a **New Branch** button after the issue
description, unless there is already a branch with the same name or a referenced
merge request.
![New Branch Button](img/new_branch_from_issue.png)
Once you click it, a new branch will be created that diverges from the default
branch of your project, by default `master`. The branch name will be based on
the title of the issue and as suffix it will have its ID. Thus, the example
screenshot above will yield a branch named
`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`.
After the branch is created, you can edit files in the repository to fix
the issue. When a merge request is created based on the newly created branch,
the description field will automatically display the [issue closing pattern]
`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the
merge request is merged.
### Create a new branch from a project's dashboard
If you want to make changes to several files before creating a new merge
request, you can create a new branch up front. From a project's files page,
choose **New branch** from the dropdown.
![New branch dropdown](img/web_editor_new_branch_dropdown.png)
---
Enter a new **Branch name**. Optionally, change the **Create from** field
to choose which branch, tag or commit SHA this new branch will originate from.
This field will autocomplete if you start typing an existing branch or tag.
Click **Create branch** and you will be returned to the file browser on this new
branch.
![New branch page](img/web_editor_new_branch_page.png)
---
You can now make changes to any files, as needed. When you're ready to merge
the changes back to master you can use the widget at the top of the screen.
This widget only appears for a period of time after you create the branch or
modify files.
![New push widget](img/web_editor_new_push_widget.png)
## Create a new tag
Tags are useful for marking major milestones such as production releases,
release candidates, and more. You can create a tag from a branch or a commit
SHA. From a project's files page, choose **New tag** from the dropdown.
![New tag dropdown](img/web_editor_new_tag_dropdown.png)
---
Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you
would like to create this new tag. You can optionally add a message and
release notes. The release notes section supports markdown format and you can
also upload an attachment. Click **Create tag** and you will be taken to the tag
list page.
![New tag page](img/web_editor_new_tag_page.png)
## Tips
When creating or uploading a new file, or creating a new directory, you can
trigger a new merge request rather than committing directly to master. Enter
a new branch name in the **Target branch** field. You will notice a checkbox
appear that is labeled **Start a new merge request with these changes**. After
you commit the changes you will be taken to a new merge request form.
![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png)
![New file button](basicsimages/file_button.png)
[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
[issue closing pattern]: ../customization/issue_closing.md
# Workflow # Workflow
- [Authorization for merge requests](authorization_for_merge_requests.md)
- [Change your time zone](timezone.md) - [Change your time zone](timezone.md)
- [Description templates](../user/project/description_templates.md) - [Description templates](../user/project/description_templates.md)
- [Feature branch workflow](workflow.md) - [Feature branch workflow](workflow.md)
...@@ -18,14 +17,18 @@ ...@@ -18,14 +17,18 @@
- [Slash commands](../user/project/slash_commands.md) - [Slash commands](../user/project/slash_commands.md)
- [Sharing a project with a group](share_with_group.md) - [Sharing a project with a group](share_with_group.md)
- [Share projects with other groups](share_projects_with_other_groups.md) - [Share projects with other groups](share_projects_with_other_groups.md)
- [Web Editor](web_editor.md) - [Web Editor](../user/project/repository/web_editor.md)
- [Releases](releases.md) - [Releases](releases.md)
- [Milestones](milestones.md) - [Milestones](milestones.md)
- [Merge Requests](merge_requests.md) - [Merge Requests](../user/project/merge_requests.md)
- [Revert changes](revert_changes.md) - [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md)
- [Cherry-pick changes](cherry_pick_changes.md) - [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md) - [Merge when build succeeds](../user/project/merge_requests/merge_when_build_succeeds.md)
- [Merge When Build Succeeds](merge_when_build_succeeds.md) - [Resolve discussion comments in merge requests reviews](../user/project/merge_requests/merge_request_discussion_resolution.md)
- [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md)
- [Revert changes in the UI](../user/project/merge_requests/revert_changes.md)
- [Merge requests versions](../user/project/merge_requests/versions.md)
- ["Work In Progress" merge requests](../user/project/merge_requests/work_in_progress_merge_requests.md)
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md) - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
- [Importing from SVN, GitHub, BitBucket, etc](importing/README.md) - [Importing from SVN, GitHub, BitBucket, etc](importing/README.md)
- [Todos](todos.md) - [Todos](todos.md)
# Authorization for Merge requests This document was moved to [user/project/merge_requests/authorization_for_merge_requests](../user/project/merge_requests/authorization_for_merge_requests.md)
There are two main ways to have a merge request flow with GitLab: working with protected branches in a single repository, or working with forks of an authoritative project.
## Protected branch flow
With the protected branch flow everybody works within the same GitLab project.
The project maintainers get Master access and the regular developers get Developer access.
The maintainers mark the authoritative branches as 'Protected'.
The developers push feature branches to the project and create merge requests to have their feature branches reviewed and merged into one of the protected branches.
Only users with Master access can merge changes into a protected branch.
### Advantages
- fewer projects means less clutter
- developers need to consider only one remote repository
### Disadvantages
- manual setup of protected branch required for each new project
## Forking workflow
With the forking workflow the maintainers get Master access and the regular developers get Reporter access to the authoritative repository, which prohibits them from pushing any changes to it.
Developers create forks of the authoritative project and push their feature branches to their own forks.
To get their changes into master they need to create a merge request across forks.
### Advantages
- in an appropriately configured GitLab group, new projects automatically get the required access restrictions for regular developers: fewer manual steps to configure authorization for new projects
### Disadvantages
- the project need to keep their forks up to date, which requires more advanced Git skills (managing multiple remotes)
# Cherry-pick changes This document was moved to [user/project/merge_requests/cherry_pick_changes](../user/project/merge_requests/cherry_pick_changes.md).
> [Introduced][ce-3514] in GitLab 8.7.
---
GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
with introducing a **Cherry-pick** button in Merge Requests and commit details.
## Cherry-picking a Merge Request
After the Merge Request has been merged, a **Cherry-pick** button will be available
to cherry-pick the changes introduced by that Merge Request:
![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png)
---
You can cherry-pick the changes directly into the selected branch or you can opt to
create a new Merge Request with the cherry-pick changes:
![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png)
## Cherry-picking a Commit
You can cherry-pick a Commit from the Commit details page:
![Cherry-pick commit](img/cherry_pick_changes_commit.png)
---
Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes
directly into the target branch or create a new Merge Request to cherry-pick the
changes:
![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png)
---
Please note that when cherry-picking merge commits, the mainline will always be the
first parent. If you want to use a different mainline then you need to do that
from the command line.
Here is a quick example to cherry-pick a merge commit using the second parent as the
mainline:
```bash
git cherry-pick -m 2 7a39eb0
```
[ce-3514]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3514 "Cherry-pick button Merge Request"
[git-cherry-pick]: https://git-scm.com/docs/git-cherry-pick "Git cherry-pick documentation"
# Merge Requests This document was moved to [user/project/merge_requests](../user/project/merge_requests.md).
Merge requests allow you to exchange changes you made to source code
## Only allow merge requests to be merged if the build succeeds
You can prevent merge requests from being merged if their build did not succeed
in the project settings page.
![only_allow_merge_if_build_succeeds](merge_requests/only_allow_merge_if_build_succeeds.png)
Navigate to project settings page and select the `Only allow merge requests to be merged if the build succeeds` check box.
Please note that you need to have builds configured to enable this feature.
## Checkout merge requests locally
### By adding a git alias
Add the following alias to your `~/.gitconfig`:
```
[alias]
mr = !sh -c 'git fetch $1 merge-requests/$2/head:mr-$1-$2 && git checkout mr-$1-$2' -
```
Now you can check out a particular merge request from any repository and any remote, e.g. to check out a merge request number 5 as shown in GitLab from the `upstream` remote, do:
```
$ git mr upstream 5
```
This will fetch the merge request into a local `mr-upstream-5` branch and check it out.
### By modifying `.git/config` for a given repository
Locate the section for your GitLab remote in the `.git/config` file. It looks like this:
```
[remote "origin"]
url = https://gitlab.com/gitlab-org/gitlab-ce.git
fetch = +refs/heads/*:refs/remotes/origin/*
```
Now add the line `fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*` to this section.
It should look like this:
```
[remote "origin"]
url = https://gitlab.com/gitlab-org/gitlab-ce.git
fetch = +refs/heads/*:refs/remotes/origin/*
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
```
Now you can fetch all the merge requests:
```
$ git fetch origin
From https://gitlab.com/gitlab-org/gitlab-ce.git
* [new ref] refs/merge-requests/1/head -> origin/merge-requests/1
* [new ref] refs/merge-requests/2/head -> origin/merge-requests/2
...
```
To check out a particular merge request:
```
$ git checkout origin/merge-requests/1
```
## Ignore whitespace changes in Merge Request diff view
![MR diff](merge_requests/merge_request_diff.png)
If you click the "Hide whitespace changes" button, you can see the diff without whitespace changes.
![MR diff without whitespace](merge_requests/merge_request_diff_without_whitespace.png)
It is also working on commits compare view.
![Commit Compare](merge_requests/commit_compare.png)
## Merge Requests versions
Every time you push to merge request branch, a new version of merge request diff
is created. When you visit the merge request page you see latest version of changes.
However you can select an older one from version dropdown
![Merge Request Versions](merge_requests/versions.png)
# Merge When Build Succeeds This document was moved to [user/project/merge_requests/merge_when_build_succeeds](../user/project/merge_requests/merge_when_build_succeeds.md).
When reviewing a merge request that looks ready to merge but still has one or more CI builds running, you can set it to be merged automatically when all builds succeed. This way, you don't have to wait for the builds to finish and remember to merge the request manually.
![Enable](merge_when_build_succeeds/enable.png)
When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait for the build to succeed and want to merge immediately, this option is available in the dropdown menu on the right of the main button.
Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged after all.
![Status](merge_when_build_succeeds/status.png)
When the build succeeds, the merge request will automatically be merged. When the build fails, the author gets a chance to retry any failed builds, or to push new commits to fix the failure.
When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with new commits, the automatic merge is automatically canceled to allow the new changes to be reviewed.
# Reverting changes This document was moved to [user/project/merge_requests/revert_changes](../user/project/merge_requests/revert_changes.md).
> [Introduced][ce-1990] in GitLab 8.5.
---
GitLab implements Git's powerful feature to [revert any commit][git-revert]
with introducing a **Revert** button in Merge Requests and commit details.
## Reverting a Merge Request
_**Note:** The **Revert** button will only be available for Merge Requests
created since GitLab 8.5. However, you can still revert a Merge Request
by reverting the merge commit from the list of Commits page._
After the Merge Request has been merged, a **Revert** button will be available
to revert the changes introduced by that Merge Request:
![Revert Merge Request](img/revert_changes_mr.png)
---
You can revert the changes directly into the selected branch or you can opt to
create a new Merge Request with the revert changes:
![Revert Merge Request modal](img/revert_changes_mr_modal.png)
---
After the Merge Request has been reverted, the **Revert** button will not be
available anymore.
## Reverting a Commit
You can revert a Commit from the Commit details page:
![Revert commit](img/revert_changes_commit.png)
---
Similar to reverting a Merge Request, you can opt to revert the changes
directly into the target branch or create a new Merge Request to revert the
changes:
![Revert commit modal](img/revert_changes_commit_modal.png)
---
After the Commit has been reverted, the **Revert** button will not be available
anymore.
Please note that when reverting merge commits, the mainline will always be the
first parent. If you want to use a different mainline then you need to do that
from the command line.
Here is a quick example to revert a merge commit using the second parent as the
mainline:
```bash
git revert -m 2 7a39eb0
```
[ce-1990]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1990 "Revert button Merge Request"
[git-revert]: https://git-scm.com/docs/git-revert "Git revert documentation"
# GitLab Web Editor This document was moved to [user/project/repository/web_editor](../user/project/repository/web_editor.md).
Sometimes it's easier to make quick changes directly from the GitLab interface
than to clone the project and use the Git command line tool. In this feature
highlight we look at how you can create a new file, directory, branch or
tag from the file browser. All of these actions are available from a single
dropdown menu.
## Create a file
From a project's files page, click the '+' button to the right of the branch selector.
Choose **New file** from the dropdown.
![New file dropdown menu](img/web_editor_new_file_dropdown.png)
---
Enter a file name in the **File name** box. Then, add file content in the editor
area. Add a descriptive commit message and choose a branch. The branch field
will default to the branch you were viewing in the file browser. If you enter
a new branch name, a checkbox will appear allowing you to start a new merge
request after you commit the changes.
When you are satisfied with your new file, click **Commit Changes** at the bottom.
![Create file editor](img/web_editor_new_file_editor.png)
## Upload a file
The ability to create a file is great when the content is text. However, this
doesn't work well for binary data such as images, PDFs or other file types. In
this case you need to upload a file.
From a project's files page, click the '+' button to the right of the branch
selector. Choose **Upload file** from the dropdown.
![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png)
---
Once the upload dialog pops up there are two ways to upload your file. Either
drag and drop a file on the pop up or use the **click to upload** link. A file
preview will appear once you have selected a file to upload.
Enter a commit message, choose a branch, and click **Upload file** when you are
ready.
![Upload file dialog](img/web_editor_upload_file_dialog.png)
## Create a directory
To keep files in the repository organized it is often helpful to create a new
directory.
From a project's files page, click the '+' button to the right of the branch selector.
Choose **New directory** from the dropdown.
![New directory dropdown](img/web_editor_new_directory_dropdown.png)
---
In the new directory dialog enter a directory name, a commit message and choose
the target branch. Click **Create directory** to finish.
![New directory dialog](img/web_editor_new_directory_dialog.png)
## Create a new branch
There are multiple ways to create a branch from GitLab's web interface.
### Create a new branch from an issue
> [Introduced][ce-2808] in GitLab 8.6.
In case your development workflow dictates to have an issue for every merge
request, you can quickly create a branch right on the issue page which will be
tied with the issue itself. You can see a **New Branch** button after the issue
description, unless there is already a branch with the same name or a referenced
merge request.
![New Branch Button](img/new_branch_from_issue.png)
Once you click it, a new branch will be created that diverges from the default
branch of your project, by default `master`. The branch name will be based on
the title of the issue and as suffix it will have its ID. Thus, the example
screenshot above will yield a branch named
`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`.
After the branch is created, you can edit files in the repository to fix
the issue. When a merge request is created based on the newly created branch,
the description field will automatically display the [issue closing pattern]
`Closes #ID`, where `ID` the ID of the issue. This will close the issue once the
merge request is merged.
### Create a new branch from a project's dashboard
If you want to make changes to several files before creating a new merge
request, you can create a new branch up front. From a project's files page,
choose **New branch** from the dropdown.
![New branch dropdown](img/web_editor_new_branch_dropdown.png)
---
Enter a new **Branch name**. Optionally, change the **Create from** field
to choose which branch, tag or commit SHA this new branch will originate from.
This field will autocomplete if you start typing an existing branch or tag.
Click **Create branch** and you will be returned to the file browser on this new
branch.
![New branch page](img/web_editor_new_branch_page.png)
---
You can now make changes to any files, as needed. When you're ready to merge
the changes back to master you can use the widget at the top of the screen.
This widget only appears for a period of time after you create the branch or
modify files.
![New push widget](img/web_editor_new_push_widget.png)
## Create a new tag
Tags are useful for marking major milestones such as production releases,
release candidates, and more. You can create a tag from a branch or a commit
SHA. From a project's files page, choose **New tag** from the dropdown.
![New tag dropdown](img/web_editor_new_tag_dropdown.png)
---
Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you
would like to create this new tag. You can optionally add a message and
release notes. The release notes section supports markdown format and you can
also upload an attachment. Click **Create tag** and you will be taken to the tag
list page.
![New tag page](img/web_editor_new_tag_page.png)
## Tips
When creating or uploading a new file, or creating a new directory, you can
trigger a new merge request rather than committing directly to master. Enter
a new branch name in the **Target branch** field. You will notice a checkbox
appear that is labeled **Start a new merge request with these changes**. After
you commit the changes you will be taken to a new merge request form.
![Start a new merge request with these changes](img/web_editor_start_new_merge_request.png)
[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
[issue closing pattern]: ../customization/issue_closing.md
# "Work In Progress" Merge Requests This document was moved to [user/project/merge_requests/work_in_progress_merge_requests](../user/project/merge_requests/work_in_progress_merge_requests.md).
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)
...@@ -21,9 +21,9 @@ module Gitlab ...@@ -21,9 +21,9 @@ module Gitlab
@cmd_output = "" @cmd_output = ""
@cmd_status = 0 @cmd_status = 0
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
# We are not using stdin so we should close it, in case the command we yield(stdin) if block_given?
# are running waits for input.
stdin.close stdin.close
@cmd_output << stdout.read @cmd_output << stdout.read
@cmd_output << stderr.read @cmd_output << stderr.read
@cmd_status = wait_thr.value.exitstatus @cmd_status = wait_thr.value.exitstatus
......
...@@ -146,13 +146,12 @@ describe Import::BitbucketController do ...@@ -146,13 +146,12 @@ describe Import::BitbucketController do
end end
context "when a namespace with the Bitbucket user's username doesn't exist" do context "when a namespace with the Bitbucket user's username doesn't exist" do
context "when current user can create namespaces" do
it "creates the namespace" do it "creates the namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator). expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).and_return(double(execute: true)) to receive(:new).and_return(double(execute: true))
post :create, format: :js expect { post :create, format: :js }.to change(Namespace, :count).by(1)
expect(Namespace.where(name: other_username).first).not_to be_nil
end end
it "takes the new namespace" do it "takes the new namespace" do
...@@ -163,6 +162,28 @@ describe Import::BitbucketController do ...@@ -163,6 +162,28 @@ describe Import::BitbucketController do
post :create, format: :js post :create, format: :js
end end
end end
context "when current user can't create namespaces" do
before do
user.update_attribute(:can_create_group, false)
end
it "doesn't create the namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, format: :js }.not_to change(Namespace, :count)
end
it "takes the current user's namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
end
end end
end end
end end
...@@ -181,13 +181,12 @@ describe Import::GithubController do ...@@ -181,13 +181,12 @@ describe Import::GithubController do
end end
context "when a namespace with the GitHub user's username doesn't exist" do context "when a namespace with the GitHub user's username doesn't exist" do
context "when current user can create namespaces" do
it "creates the namespace" do it "creates the namespace" do
expect(Gitlab::GithubImport::ProjectCreator). expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true)) to receive(:new).and_return(double(execute: true))
post :create, format: :js expect { post :create, format: :js }.to change(Namespace, :count).by(1)
expect(Namespace.where(name: other_username).first).not_to be_nil
end end
it "takes the new namespace" do it "takes the new namespace" do
...@@ -198,6 +197,28 @@ describe Import::GithubController do ...@@ -198,6 +197,28 @@ describe Import::GithubController do
post :create, format: :js post :create, format: :js
end end
end end
context "when current user can't create namespaces" do
before do
user.update_attribute(:can_create_group, false)
end
it "doesn't create the namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, format: :js }.not_to change(Namespace, :count)
end
it "takes the current user's namespace" do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(github_repo, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
end
end end
end end
end end
...@@ -136,13 +136,12 @@ describe Import::GitlabController do ...@@ -136,13 +136,12 @@ describe Import::GitlabController do
end end
context "when a namespace with the GitLab.com user's username doesn't exist" do context "when a namespace with the GitLab.com user's username doesn't exist" do
context "when current user can create namespaces" do
it "creates the namespace" do it "creates the namespace" do
expect(Gitlab::GitlabImport::ProjectCreator). expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).and_return(double(execute: true)) to receive(:new).and_return(double(execute: true))
post :create, format: :js expect { post :create, format: :js }.to change(Namespace, :count).by(1)
expect(Namespace.where(name: other_username).first).not_to be_nil
end end
it "takes the new namespace" do it "takes the new namespace" do
...@@ -153,6 +152,28 @@ describe Import::GitlabController do ...@@ -153,6 +152,28 @@ describe Import::GitlabController do
post :create, format: :js post :create, format: :js
end end
end end
context "when current user can't create namespaces" do
before do
user.update_attribute(:can_create_group, false)
end
it "doesn't create the namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).and_return(double(execute: true))
expect { post :create, format: :js }.not_to change(Namespace, :count)
end
it "takes the current user's namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, user.namespace, user, access_params).
and_return(double(execute: true))
post :create, format: :js
end
end
end
end end
end end
end end
...@@ -49,4 +49,20 @@ describe Projects::ServicesController do ...@@ -49,4 +49,20 @@ describe Projects::ServicesController do
let!(:referrer) { nil } let!(:referrer) { nil }
end end
end end
describe 'PUT #update' do
context 'on successful update' do
it 'sets the flash' do
expect(service).to receive(:to_param).and_return('hipchat')
put :update,
namespace_id: project.namespace.id,
project_id: project.id,
id: service.id,
service: { active: false }
expect(flash[:notice]).to eq 'Successfully updated.'
end
end
end
end end
require 'rails_helper' require 'rails_helper'
describe ImportHelper do describe ImportHelper do
describe '#import_project_target' do
let(:user) { create(:user) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
context 'when current user can create namespaces' do
it 'returns project namespace' do
user.update_attribute(:can_create_group, true)
expect(helper.import_project_target('asd', 'vim')).to eq 'asd/vim'
end
end
context 'when current user can not create namespaces' do
it "takes the current user's namespace" do
user.update_attribute(:can_create_group, false)
expect(helper.import_project_target('asd', 'vim')).to eq "#{user.namespace_path}/vim"
end
end
end
describe '#github_project_link' do describe '#github_project_link' do
context 'when provider does not specify a custom URL' do context 'when provider does not specify a custom URL' do
it 'uses default GitHub URL' do it 'uses default GitHub URL' do
......
...@@ -40,4 +40,13 @@ describe 'Gitlab::Popen', lib: true, no_db: true do ...@@ -40,4 +40,13 @@ describe 'Gitlab::Popen', lib: true, no_db: true do
it { expect(@status).to be_zero } it { expect(@status).to be_zero }
it { expect(@output).to include('spec') } it { expect(@output).to include('spec') }
end end
context 'use stdin' do
before do
@output, @status = @klass.new.popen(%w[cat]) { |stdin| stdin.write 'hello' }
end
it { expect(@status).to be_zero }
it { expect(@output).to eq('hello') }
end
end end
...@@ -443,31 +443,32 @@ describe Repository, models: true do ...@@ -443,31 +443,32 @@ describe Repository, models: true do
describe '#commit_with_hooks' do describe '#commit_with_hooks' do
let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
context 'when pre hooks were successful' do context 'when pre hooks were successful' do
before do before do
expect_any_instance_of(GitHooksService).to receive(:execute). expect_any_instance_of(GitHooksService).to receive(:execute).
with(user, repository.path_to_repo, old_rev, sample_commit.id, 'refs/heads/feature'). with(user, repository.path_to_repo, old_rev, new_rev, 'refs/heads/feature').
and_yield.and_return(true) and_yield.and_return(true)
end end
it 'runs without errors' do it 'runs without errors' do
expect do expect do
repository.commit_with_hooks(user, 'feature') { sample_commit.id } repository.commit_with_hooks(user, 'feature') { new_rev }
end.not_to raise_error end.not_to raise_error
end end
it 'ensures the autocrlf Git option is set to :input' do it 'ensures the autocrlf Git option is set to :input' do
expect(repository).to receive(:update_autocrlf_option) expect(repository).to receive(:update_autocrlf_option)
repository.commit_with_hooks(user, 'feature') { sample_commit.id } repository.commit_with_hooks(user, 'feature') { new_rev }
end end
context "when the branch wasn't empty" do context "when the branch wasn't empty" do
it 'updates the head' do it 'updates the head' do
expect(repository.find_branch('feature').target.id).to eq(old_rev) expect(repository.find_branch('feature').target.id).to eq(old_rev)
repository.commit_with_hooks(user, 'feature') { sample_commit.id } repository.commit_with_hooks(user, 'feature') { new_rev }
expect(repository.find_branch('feature').target.id).to eq(sample_commit.id) expect(repository.find_branch('feature').target.id).to eq(new_rev)
end end
end end
end end
...@@ -477,7 +478,7 @@ describe Repository, models: true do ...@@ -477,7 +478,7 @@ describe Repository, models: true do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do expect do
repository.commit_with_hooks(user, 'feature') { sample_commit.id } repository.commit_with_hooks(user, 'feature') { new_rev }
end.to raise_error(GitHooksService::PreReceiveError) end.to raise_error(GitHooksService::PreReceiveError)
end end
end end
...@@ -485,6 +486,7 @@ describe Repository, models: true do ...@@ -485,6 +486,7 @@ describe Repository, models: true do
context 'when target branch is different from source branch' do context 'when target branch is different from source branch' do
before do before do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, '']) allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
allow(repository).to receive(:update_ref!)
end end
it 'expires branch cache' do it 'expires branch cache' do
...@@ -495,7 +497,7 @@ describe Repository, models: true do ...@@ -495,7 +497,7 @@ describe Repository, models: true do
expect(repository).to receive(:expire_has_visible_content_cache) expect(repository).to receive(:expire_has_visible_content_cache)
expect(repository).to receive(:expire_branch_count_cache) expect(repository).to receive(:expire_branch_count_cache)
repository.commit_with_hooks(user, 'new-feature') { sample_commit.id } repository.commit_with_hooks(user, 'new-feature') { new_rev }
end end
end end
...@@ -1268,4 +1270,18 @@ describe Repository, models: true do ...@@ -1268,4 +1270,18 @@ describe Repository, models: true do
File.delete(path) File.delete(path)
end end
end end
describe '#update_ref!' do
it 'can create a ref' do
repository.update_ref!('refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
expect(repository.find_branch('foobar')).not_to be_nil
end
it 'raises CommitError when the ref update fails' do
expect do
repository.update_ref!('refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
end.to raise_error(Repository::CommitError)
end
end
end end
...@@ -18,12 +18,12 @@ describe Issues::CloseService, services: true do ...@@ -18,12 +18,12 @@ describe Issues::CloseService, services: true do
context "valid params" do context "valid params" do
before do before do
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = described_class.new(project, user, {}).execute(issue) described_class.new(project, user).execute(issue)
end end
end end
it { expect(@issue).to be_valid } it { expect(issue).to be_valid }
it { expect(@issue).to be_closed } it { expect(issue).to be_closed }
it 'sends email to user2 about assign of new issue' do it 'sends email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last email = ActionMailer::Base.deliveries.last
...@@ -32,7 +32,7 @@ describe Issues::CloseService, services: true do ...@@ -32,7 +32,7 @@ describe Issues::CloseService, services: true do
end end
it 'creates system note about issue reassign' do it 'creates system note about issue reassign' do
note = @issue.notes.last note = issue.notes.last
expect(note.note).to include "Status changed to closed" expect(note.note).to include "Status changed to closed"
end end
...@@ -44,23 +44,43 @@ describe Issues::CloseService, services: true do ...@@ -44,23 +44,43 @@ describe Issues::CloseService, services: true do
context 'current user is not authorized to close issue' do context 'current user is not authorized to close issue' do
before do before do
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = described_class.new(project, guest).execute(issue) described_class.new(project, guest).execute(issue)
end end
end end
it 'does not close the issue' do it 'does not close the issue' do
expect(@issue).to be_open expect(issue).to be_open
end end
end end
context "external issue tracker" do context 'when issue is not confidential' do
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
described_class.new(project, user).execute(issue)
end
end
context 'when issue is confidential' do
it 'executes confidential issue hooks' do
issue = create(:issue, :confidential, project: project)
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
described_class.new(project, user).execute(issue)
end
end
context 'external issue tracker' do
before do before do
allow(project).to receive(:default_issues_tracker?).and_return(false) allow(project).to receive(:default_issues_tracker?).and_return(false)
@issue = described_class.new(project, user, {}).execute(issue) described_class.new(project, user).execute(issue)
end end
it { expect(@issue).to be_valid } it { expect(issue).to be_valid }
it { expect(@issue).to be_opened } it { expect(issue).to be_opened }
it { expect(todo.reload).to be_pending } it { expect(todo.reload).to be_pending }
end end
end end
......
...@@ -72,6 +72,24 @@ describe Issues::CreateService, services: true do ...@@ -72,6 +72,24 @@ describe Issues::CreateService, services: true do
expect(issue.milestone).not_to eq milestone expect(issue.milestone).not_to eq milestone
end end
end end
it 'executes issue hooks when issue is not confidential' do
opts = { title: 'Title', description: 'Description', confidential: false }
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
described_class.new(project, user, opts).execute
end
it 'executes confidential issue hooks when issue is confidential' do
opts = { title: 'Title', description: 'Description', confidential: true }
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
described_class.new(project, user, opts).execute
end
end end
it_behaves_like 'new issuable record that supports slash commands' it_behaves_like 'new issuable record that supports slash commands'
......
require 'spec_helper' require 'spec_helper'
describe Issues::ReopenService, services: true do describe Issues::ReopenService, services: true do
let(:guest) { create(:user) } let(:project) { create(:empty_project) }
let(:issue) { create(:issue, :closed) } let(:issue) { create(:issue, :closed, project: project) }
let(:project) { issue.project }
describe '#execute' do
context 'when user is not authorized to reopen issue' do
before do before do
guest = create(:user)
project.team << [guest, :guest] project.team << [guest, :guest]
end
describe '#execute' do
context 'current user is not authorized to reopen issue' do
before do
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = described_class.new(project, guest).execute(issue) described_class.new(project, guest).execute(issue)
end end
end end
it 'does not reopen the issue' do it 'does not reopen the issue' do
expect(@issue).to be_closed expect(issue).to be_closed
end
end
context 'when user is authrized to reopen issue' do
let(:user) { create(:user) }
before do
project.team << [user, :master]
end
context 'when issue is not confidential' do
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :issue_hooks)
described_class.new(project, user).execute(issue)
end
end
context 'when issue is confidential' do
it 'executes confidential issue hooks' do
issue = create(:issue, :confidential, :closed, project: project)
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
described_class.new(project, user).execute(issue)
end
end end
end end
end end
......
...@@ -23,11 +23,15 @@ describe Issues::UpdateService, services: true do ...@@ -23,11 +23,15 @@ describe Issues::UpdateService, services: true do
describe 'execute' do describe 'execute' do
def find_note(starting_with) def find_note(starting_with)
@issue.notes.find do |note| issue.notes.find do |note|
note && note.note.start_with?(starting_with) note && note.note.start_with?(starting_with)
end end
end end
def update_issue(opts)
described_class.new(project, user, opts).execute(issue)
end
context "valid params" do context "valid params" do
before do before do
opts = { opts = {
...@@ -35,23 +39,20 @@ describe Issues::UpdateService, services: true do ...@@ -35,23 +39,20 @@ describe Issues::UpdateService, services: true do
description: 'Also please fix', description: 'Also please fix',
assignee_id: user2.id, assignee_id: user2.id,
state_event: 'close', state_event: 'close',
label_ids: [label.id], label_ids: [label.id]
confidential: true
} }
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = Issues::UpdateService.new(project, user, opts).execute(issue) update_issue(opts)
end end
@issue.reload
end end
it { expect(@issue).to be_valid } it { expect(issue).to be_valid }
it { expect(@issue.title).to eq('New title') } it { expect(issue.title).to eq('New title') }
it { expect(@issue.assignee).to eq(user2) } it { expect(issue.assignee).to eq(user2) }
it { expect(@issue).to be_closed } it { expect(issue).to be_closed }
it { expect(@issue.labels.count).to eq(1) } it { expect(issue.labels.count).to eq(1) }
it { expect(@issue.labels.first.title).to eq(label.name) } it { expect(issue.labels.first.title).to eq(label.name) }
it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
deliveries = ActionMailer::Base.deliveries deliveries = ActionMailer::Base.deliveries
...@@ -81,18 +82,35 @@ describe Issues::UpdateService, services: true do ...@@ -81,18 +82,35 @@ describe Issues::UpdateService, services: true do
expect(note).not_to be_nil expect(note).not_to be_nil
expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**' expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**'
end end
end
context 'when issue turns confidential' do
let(:opts) do
{
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close',
label_ids: [label.id],
confidential: true
}
end
it 'creates system note about confidentiality change' do it 'creates system note about confidentiality change' do
update_issue(confidential: true)
note = find_note('Made the issue confidential') note = find_note('Made the issue confidential')
expect(note).not_to be_nil expect(note).not_to be_nil
expect(note.note).to eq 'Made the issue confidential' expect(note.note).to eq 'Made the issue confidential'
end end
end
def update_issue(opts) it 'executes confidential issue hooks' do
@issue = Issues::UpdateService.new(project, user, opts).execute(issue) expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :confidential_issue_hooks)
@issue.reload expect(project).to receive(:execute_services).with(an_instance_of(Hash), :confidential_issue_hooks)
update_issue(confidential: true)
end
end end
context 'todos' do context 'todos' do
...@@ -100,7 +118,7 @@ describe Issues::UpdateService, services: true do ...@@ -100,7 +118,7 @@ describe Issues::UpdateService, services: true do
context 'when the title change' do context 'when the title change' do
before do before do
update_issue({ title: 'New title' }) update_issue(title: 'New title')
end end
it 'marks pending todos as done' do it 'marks pending todos as done' do
...@@ -110,7 +128,7 @@ describe Issues::UpdateService, services: true do ...@@ -110,7 +128,7 @@ describe Issues::UpdateService, services: true do
context 'when the description change' do context 'when the description change' do
before do before do
update_issue({ description: 'Also please fix' }) update_issue(description: 'Also please fix')
end end
it 'marks todos as done' do it 'marks todos as done' do
...@@ -120,7 +138,7 @@ describe Issues::UpdateService, services: true do ...@@ -120,7 +138,7 @@ describe Issues::UpdateService, services: true do
context 'when is reassigned' do context 'when is reassigned' do
before do before do
update_issue({ assignee: user2 }) update_issue(assignee: user2)
end end
it 'marks previous assignee todos as done' do it 'marks previous assignee todos as done' do
...@@ -144,7 +162,7 @@ describe Issues::UpdateService, services: true do ...@@ -144,7 +162,7 @@ describe Issues::UpdateService, services: true do
context 'when the milestone change' do context 'when the milestone change' do
before do before do
update_issue({ milestone: create(:milestone) }) update_issue(milestone: create(:milestone))
end end
it 'marks todos as done' do it 'marks todos as done' do
...@@ -154,7 +172,7 @@ describe Issues::UpdateService, services: true do ...@@ -154,7 +172,7 @@ describe Issues::UpdateService, services: true do
context 'when the labels change' do context 'when the labels change' do
before do before do
update_issue({ label_ids: [label.id] }) update_issue(label_ids: [label.id])
end end
it 'marks todos as done' do it 'marks todos as done' do
...@@ -165,6 +183,7 @@ describe Issues::UpdateService, services: true do ...@@ -165,6 +183,7 @@ describe Issues::UpdateService, services: true do
context 'when the issue is relabeled' do context 'when the issue is relabeled' do
let!(:non_subscriber) { create(:user) } let!(:non_subscriber) { create(:user) }
let!(:subscriber) do let!(:subscriber) do
create(:user).tap do |u| create(:user).tap do |u|
label.toggle_subscription(u) label.toggle_subscription(u)
...@@ -176,7 +195,7 @@ describe Issues::UpdateService, services: true do ...@@ -176,7 +195,7 @@ describe Issues::UpdateService, services: true do
opts = { label_ids: [label.id] } opts = { label_ids: [label.id] }
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = Issues::UpdateService.new(project, user, opts).execute(issue) @issue = described_class.new(project, user, opts).execute(issue)
end end
should_email(subscriber) should_email(subscriber)
...@@ -190,7 +209,7 @@ describe Issues::UpdateService, services: true do ...@@ -190,7 +209,7 @@ describe Issues::UpdateService, services: true do
opts = { label_ids: [label.id, label2.id] } opts = { label_ids: [label.id, label2.id] }
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = Issues::UpdateService.new(project, user, opts).execute(issue) @issue = described_class.new(project, user, opts).execute(issue)
end end
should_not_email(subscriber) should_not_email(subscriber)
...@@ -201,7 +220,7 @@ describe Issues::UpdateService, services: true do ...@@ -201,7 +220,7 @@ describe Issues::UpdateService, services: true do
opts = { label_ids: [label2.id] } opts = { label_ids: [label2.id] }
perform_enqueued_jobs do perform_enqueued_jobs do
@issue = Issues::UpdateService.new(project, user, opts).execute(issue) @issue = described_class.new(project, user, opts).execute(issue)
end end
should_not_email(subscriber) should_not_email(subscriber)
...@@ -210,13 +229,15 @@ describe Issues::UpdateService, services: true do ...@@ -210,13 +229,15 @@ describe Issues::UpdateService, services: true do
end end
end end
context 'when Issue has tasks' do context 'when issue has tasks' do
before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) } before do
update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
end
it { expect(@issue.tasks?).to eq(true) } it { expect(issue.tasks?).to eq(true) }
context 'when tasks are marked as completed' do context 'when tasks are marked as completed' do
before { update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) } before { update_issue(description: "- [x] Task 1\n- [X] Task 2") }
it 'creates system note about task status change' do it 'creates system note about task status change' do
note1 = find_note('Marked the task **Task 1** as completed') note1 = find_note('Marked the task **Task 1** as completed')
...@@ -229,8 +250,8 @@ describe Issues::UpdateService, services: true do ...@@ -229,8 +250,8 @@ describe Issues::UpdateService, services: true do
context 'when tasks are marked as incomplete' do context 'when tasks are marked as incomplete' do
before do before do
update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) update_issue(description: "- [x] Task 1\n- [X] Task 2")
update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
end end
it 'creates system note about task status change' do it 'creates system note about task status change' do
...@@ -244,8 +265,8 @@ describe Issues::UpdateService, services: true do ...@@ -244,8 +265,8 @@ describe Issues::UpdateService, services: true do
context 'when tasks position has been modified' do context 'when tasks position has been modified' do
before do before do
update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) update_issue(description: "- [x] Task 1\n- [X] Task 2")
update_issue({ description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2" }) update_issue(description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2")
end end
it 'does not create a system note' do it 'does not create a system note' do
...@@ -257,8 +278,8 @@ describe Issues::UpdateService, services: true do ...@@ -257,8 +278,8 @@ describe Issues::UpdateService, services: true do
context 'when a Task list with a completed item is totally replaced' do context 'when a Task list with a completed item is totally replaced' do
before do before do
update_issue({ description: "- [ ] Task 1\n- [X] Task 2" }) update_issue(description: "- [ ] Task 1\n- [X] Task 2")
update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three")
end end
it 'does not create a system note referencing the position the old item' do it 'does not create a system note referencing the position the old item' do
...@@ -269,7 +290,7 @@ describe Issues::UpdateService, services: true do ...@@ -269,7 +290,7 @@ describe Issues::UpdateService, services: true do
it 'does not generate a new note at all' do it 'does not generate a new note at all' do
expect do expect do
update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) update_issue(description: "- [ ] One\n- [ ] Two\n- [ ] Three")
end.not_to change { Note.count } end.not_to change { Note.count }
end end
end end
...@@ -277,7 +298,7 @@ describe Issues::UpdateService, services: true do ...@@ -277,7 +298,7 @@ describe Issues::UpdateService, services: true do
context 'updating labels' do context 'updating labels' do
let(:label3) { create(:label, project: project) } let(:label3) { create(:label, project: project) }
let(:result) { Issues::UpdateService.new(project, user, params).execute(issue).reload } let(:result) { described_class.new(project, user, params).execute(issue).reload }
context 'when add_label_ids and label_ids are passed' do context 'when add_label_ids and label_ids are passed' do
let(:params) { { label_ids: [label.id], add_label_ids: [label3.id] } } let(:params) { { label_ids: [label.id], add_label_ids: [label3.id] } }
......
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