Commit 59ed6e46 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge remote-tracking branch 'ce-com/master' into ce-to-ee

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parents 09a68a2e b2700e64
......@@ -363,4 +363,4 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client
gem 'gitaly', '~> 0.3.0'
gem 'gitaly', '~> 0.5.0'
......@@ -277,7 +277,7 @@ GEM
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
gitaly (0.3.0)
gitaly (0.5.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -940,7 +940,7 @@ DEPENDENCIES
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.0)
gitaly (~> 0.3.0)
gitaly (~> 0.5.0)
github-linguist (~> 4.7.0)
gitlab-elasticsearch-git (= 1.1.1)
gitlab-flowdock-git-hook (~> 1.0.1)
......
......@@ -263,7 +263,7 @@
});
/**
* Updates the search parameter of a URL given the parameter and values provided.
* Updates the search parameter of a URL given the parameter and value provided.
*
* If no search params are present we'll add it.
* If param for page is already present, we'll update it
......@@ -278,17 +278,24 @@
let search;
const locationSearch = window.location.search;
if (locationSearch.length === 0) {
search = `?${param}=${value}`;
}
if (locationSearch.length) {
const parameters = locationSearch.substring(1, locationSearch.length)
.split('&')
.reduce((acc, element) => {
const val = element.split('=');
acc[val[0]] = decodeURIComponent(val[1]);
return acc;
}, {});
if (locationSearch.indexOf(param) !== -1) {
const regex = new RegExp(param + '=\\d');
search = locationSearch.replace(regex, `${param}=${value}`);
}
parameters[param] = value;
if (locationSearch.length && locationSearch.indexOf(param) === -1) {
search = `${locationSearch}&${param}=${value}`;
const toString = Object.keys(parameters)
.map(val => `${val}=${encodeURIComponent(parameters[val])}`)
.join('&');
search = `?${toString}`;
} else {
search = `?${param}=${value}`;
}
return search;
......
......@@ -21,6 +21,7 @@ export default {
<li v-for="artifact in artifacts">
<a
rel="nofollow"
download
:href="artifact.path">
<i class="fa fa-download" aria-hidden="true"></i>
<span>Download {{artifact.name}} artifacts</span>
......
......@@ -146,6 +146,10 @@
display: block;
}
&.scrolling-tabs {
float: left;
}
li a {
padding: 16px 15px 11px;
}
......@@ -480,6 +484,10 @@
.inner-page-scroll-tabs {
position: relative;
.nav-links {
padding-bottom: 1px;
}
.fade-right {
@include fade(left, $white-light);
right: 0;
......
class Admin::AbuseReportsController < Admin::ApplicationController
def index
@abuse_reports = AbuseReport.order(id: :desc).page(params[:page])
@abuse_reports.includes(:reporter, :user)
end
def destroy
......
......@@ -17,6 +17,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort)
@members = @members.page(params[:page]).per(50)
@members.includes(:user)
@requesters = AccessRequestsFinder.new(@group).execute(current_user)
......
class Profiles::AccountsController < Profiles::ApplicationController
include AuthHelper
def show
@user = current_user
end
def unlink
provider = params[:provider]
current_user.identities.find_by(provider: provider).destroy unless provider.to_s == 'saml'
identity = current_user.identities.find_by(provider: provider)
return render_404 unless identity
if unlink_allowed?(provider)
identity.destroy
else
flash[:alert] = "You are not allowed to unlink your primary login account"
end
redirect_to profile_account_path
end
end
......@@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@collection_type = "MergeRequest"
@merge_requests = merge_requests_collection
@merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.includes(merge_request_diff: :merge_request)
@issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type)
if @merge_requests.out_of_range? && @merge_requests.total_pages != 0
......
......@@ -21,9 +21,9 @@ class Projects::MilestonesController < Projects::ApplicationController
@sort = params[:sort] || 'due_date_asc'
@milestones = @milestones.sort(@sort)
@milestones = @milestones.includes(:project)
respond_to do |format|
format.html do
@milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page])
end
format.json do
......
......@@ -25,12 +25,12 @@ class RegistrationsController < Devise::RegistrationsController
end
def destroy
Users::DestroyService.new(current_user).execute(current_user)
DeleteUserWorker.perform_async(current_user.id, current_user.id)
respond_to do |format|
format.html do
session.try(:destroy)
redirect_to new_user_session_path, notice: "Account successfully removed."
redirect_to new_user_session_path, notice: "Account scheduled for removal."
end
end
end
......
......@@ -6,20 +6,16 @@ class SearchController < ApplicationController
layout 'search'
def show
if params[:project_id].present?
@project = Project.find_by(id: params[:project_id])
@project = nil unless can?(current_user, :download_code, @project)
end
search_service = SearchService.new(current_user, params)
if params[:group_id].present?
@group = Group.find_by(id: params[:group_id])
@group = nil unless can?(current_user, :read_group, @group)
end
@project = search_service.project
@group = search_service.group
return if params[:search].blank?
@search_term = params[:search]
<<<<<<< HEAD
@scope = params[:scope]
@show_snippets = params[:snippets].eql? 'true'
......@@ -46,6 +42,12 @@ class SearchController < ApplicationController
end
@search_objects = @search_results.objects(@scope, params[:page])
=======
@scope = search_service.scope
@show_snippets = search_service.show_snippets?
@search_results = search_service.search_results
@search_objects = search_service.search_objects
>>>>>>> ce-com/master
check_single_commit_result
end
......
......@@ -79,7 +79,16 @@ class SessionsController < Devise::SessionsController
def store_redirect_path
redirect_uri =
if request.referer.present? && (params['redirect_to_referer'] == 'yes')
<<<<<<< HEAD
URI(request.referer)
=======
referer_uri = URI(request.referer)
if referer_uri.host == Gitlab.config.gitlab.host
referer_uri.request_uri
else
request.fullpath
end
>>>>>>> ce-com/master
else
URI(request.url)
end
......
......@@ -80,5 +80,9 @@ module AuthHelper
(current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
end
def unlink_allowed?(provider)
%w(saml cas3).exclude?(provider.to_s)
end
extend self
end
......@@ -148,10 +148,13 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
<<<<<<< HEAD
validates :minimum_mirror_sync_time,
presence: true,
inclusion: { in: Gitlab::Mirror::SYNC_TIME_OPTIONS.values }
=======
>>>>>>> ce-com/master
validates_each :restricted_visibility_levels do |record, attr, value|
value&.each do |level|
unless Gitlab::VisibilityLevel.options.has_value?(level)
......@@ -258,6 +261,7 @@ class ApplicationSetting < ActiveRecord::Base
two_factor_grace_period: 48,
user_default_external: false,
polling_interval_multiplier: 1
<<<<<<< HEAD
}
end
......@@ -269,6 +273,8 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: true,
minimum_mirror_sync_time: Gitlab::Mirror::FIFTEEN,
repository_size_limit: 0
=======
>>>>>>> ce-com/master
}
end
......
......@@ -210,7 +210,7 @@ module Ci
end
def stuck?
builds.pending.any?(&:stuck?)
builds.pending.includes(:project).any?(&:stuck?)
end
def retryable?
......
......@@ -1065,7 +1065,13 @@ class Repository
end
def is_ancestor?(ancestor_id, descendant_id)
merge_base(ancestor_id, descendant_id) == ancestor_id
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id)
else
merge_base_commit(ancestor_id, descendant_id) == ancestor_id
end
end
end
def empty_repo?
......
......@@ -31,5 +31,9 @@ module Search
Gitlab::SearchResults.new(current_user, projects, params[:search])
end
end
def scope
@scope ||= %w[issues merge_requests milestones].delete(params[:scope]) { 'projects' }
end
end
end
......@@ -21,5 +21,9 @@ module Search
params[:repository_ref])
end
end
def scope
@scope ||= %w[notes issues merge_requests milestones wiki_blobs commits].delete(params[:scope]) { 'blobs' }
end
end
end
......@@ -16,5 +16,9 @@ module Search
Gitlab::SnippetSearchResults.new(snippets, params[:search])
end
end
def scope
@scope ||= %w[snippet_titles].delete(params[:scope]) { 'snippet_blobs' }
end
end
end
class SearchService
include Gitlab::Allowable
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
end
def project
return @project if defined?(@project)
@project =
if params[:project_id].present?
the_project = Project.find_by(id: params[:project_id])
can?(current_user, :download_code, the_project) ? the_project : nil
else
nil
end
end
def group
return @group if defined?(@group)
@group =
if params[:group_id].present?
the_group = Group.find_by(id: params[:group_id])
can?(current_user, :read_group, the_group) ? the_group : nil
else
nil
end
end
def show_snippets?
return @show_snippets if defined?(@show_snippets)
@show_snippets = params[:snippets] == 'true'
end
delegate :scope, to: :search_service
def search_results
@search_results ||= search_service.execute
end
def search_objects
@search_objects ||= search_results.objects(scope, params[:page])
end
private
def search_service
@search_service ||=
if project
Search::ProjectService.new(project, current_user, params)
elsif show_snippets?
Search::SnippetService.new(current_user, params)
else
Search::GlobalService.new(current_user, params)
end
end
attr_reader :current_user, :params
end
......@@ -212,7 +212,7 @@ class TodoService
# Only update those that are not really on that state
todos = todos.where.not(state: state)
todos_ids = todos.pluck(:id)
todos.update_all(state: state)
todos.unscope(:order).update_all(state: state)
current_user.update_todos_count_cache
todos_ids
end
......
......@@ -20,10 +20,10 @@ module Users
Groups::DestroyService.new(group, current_user).execute
end
user.personal_projects.each do |project|
user.personal_projects.with_deleted.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end
move_issues_to_ghost_user(user)
......
......@@ -656,6 +656,7 @@
The default value (1) is a reasonable choice for the majority of GitLab
installations. Set to 0 to completely disable polling.
<<<<<<< HEAD
- if Gitlab::Geo.license_allows?
%fieldset
%legend GitLab Geo
......@@ -669,5 +670,7 @@
The amount of seconds after which a request to get a secondary node
status will time out.
=======
>>>>>>> ce-com/master
.form-actions
= f.submit 'Save', class: 'btn btn-save'
......@@ -35,6 +35,15 @@
%li
= link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
- if current_user.can_create_project?
%li
= link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('hashtag fw')
......@@ -50,6 +59,7 @@
= icon('check-circle fw')
%span.badge.todos-count
= todos_count_format(todos_pending_count)
<<<<<<< HEAD
- if current_user.can_create_project?
%li
= link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
......@@ -65,6 +75,8 @@
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
=======
>>>>>>> ce-com/master
%li.header-user.dropdown
= link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
......
......@@ -2,7 +2,7 @@
Project #{@project.name} was exported successfully.
%p
The project export can be downloaded from:
= link_to download_export_namespace_project_url(@project.namespace, @project) do
= link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '', do
= @project.name_with_namespace + " export"
%p
The download link will expire in 24 hours.
......@@ -75,12 +75,12 @@
.provider-btn-image
= provider_image_tag(provider)
- if auth_active?(provider)
- if provider.to_s == 'saml'
%a.provider-btn
Active
- else
- if unlink_allowed?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
Disconnect
- else
%a.provider-btn
Active
- else
= link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
Connect
......
- empty_repo = @project.empty_repo?
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
%div{ class: container_class }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
%h1.project-title
......
......@@ -3,7 +3,7 @@
.top-block.row-content-block.clearfix
.pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default download' do
rel: 'nofollow', download: '', class: 'btn btn-default download' do
= icon('download')
Download artifacts archive
......
......@@ -33,7 +33,7 @@
= link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
- if @build.artifacts_metadata?
......
......@@ -94,7 +94,7 @@
%td
.pull-right
- if can?(current_user, :read_build, build) && build.artifacts?
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do
= link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download')
- if can?(current_user, :update_build, build)
- if build.active?
......
......@@ -174,7 +174,7 @@
- if @project.export_project_path
= link_to 'Download export', download_export_namespace_project_path(@project.namespace, @project),
method: :get, class: "btn btn-default"
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
= link_to 'Generate new export', generate_new_export_namespace_project_path(@project.namespace, @project),
method: :post, class: "btn btn-default"
- else
......
......@@ -16,7 +16,7 @@
= render "home_panel"
- if current_user && can?(current_user, :download_code, @project)
%nav.project-stats{ class: container_class }
%nav.project-stats.limit-container-width{ class: container_class }
%ul.nav
%li
= link_to project_files_path(@project) do
......@@ -77,11 +77,11 @@
Set up auto deploy
- if @repository.commit
%div{ class: container_class }
.limit-container-width{ class: container_class }
.project-last-commit
= render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project
%div{ class: container_class }
.limit-container-width{ class: container_class }
- if @project.archived?
.text-warning.center.prepend-top-20
%p
......
---
title: Limit line length for project home page
merge_request:
author:
---
title: Fix symlink icon in project tree
merge_request: 9780
author: mhasbini
---
title: Prevent users from disconnecting GitLab account from CAS
merge_request: 10282
author:
---
title: Fix GitHub Importer for PRs of deleted forked repositories
merge_request: 9992
author:
---
title: Fix redirection after login when the referer have params
merge_request:
author: mhasbini
---
title: Fixes method not replacing URL parameters correctly and breaking pipelines
pagination
merge_request:
author:
---
title: Move issue, mr, todos next to profile dropdown in top nav
merge_request:
author:
---
title: Use Gitaly for Repository#is_ancestor
merge_request: 9864
author:
---
title: Fix race condition where a namespace would be deleted before a project was
deleted
merge_request:
author:
---
title: Relax constraint on Wiki IDs, since subdirectories can contain spaces
merge_request:
author:
---
title: Remove unnecessary ORDER BY clause when updating todos
merge_request:
author: mhasbini
WIKI_SLUG_ID = { id: /\S+/ }.freeze unless defined? WIKI_SLUG_ID
scope(controller: :wikis) do
scope(path: 'wikis', as: :wikis) do
get :git_access
......@@ -8,7 +6,7 @@ scope(controller: :wikis) do
post '/', to: 'wikis#create'
end
scope(path: 'wikis/*id', as: :wiki, constraints: WIKI_SLUG_ID, format: false) do
scope(path: 'wikis/*id', as: :wiki, format: false) do
get :edit
get :history
post :preview_markdown
......
......@@ -122,6 +122,7 @@ ActiveRecord::Schema.define(version: 20170329124448) do
t.integer "unique_ips_limit_per_user"
t.integer "unique_ips_limit_time_window"
t.boolean "unique_ips_limit_enabled", default: false, null: false
<<<<<<< HEAD
t.integer "minimum_mirror_sync_time", default: 15, null: false
t.string "default_artifacts_expire_in", default: "0", null: false
t.string "elasticsearch_url", default: "http://localhost:9200"
......@@ -157,6 +158,9 @@ ActiveRecord::Schema.define(version: 20170329124448) do
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
=======
t.decimal "polling_interval_multiplier", default: 1.0, null: false
>>>>>>> ce-com/master
end
add_index "approvers", ["target_id", "target_type"], name: "index_approvers_on_target_id_and_target_type", using: :btree
......
......@@ -352,7 +352,7 @@ Example values:
export CI_JOB_ID="50"
export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_COMMIT_REF_NAME="master"
export CI_REPOSITORY_URL="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
export CI_REPOSITORY_URL="https://gitlab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
export CI_COMMIT_TAG="1.0.0"
export CI_JOB_NAME="spec:other"
export CI_JOB_STAGE="test"
......
......@@ -120,6 +120,14 @@ There are new configuration options available for [`gitlab.yml`][yaml]. View the
git diff origin/8-2-stable:config/gitlab.yml.example origin/8-3-stable:config/gitlab.yml.example
```
#### GitLab default file
The value of the `gitlab_workhorse_options` variable should be updated within the default gitlab file (`/etc/default/gitlab`) according to the following diff:
```sh
git diff origin/8-2-stable:lib/support/init.d/gitlab.default.example origin/8-3-stable:lib/support/init.d/gitlab.default.example
```
#### Nginx configuration
GitLab 8.3 introduces major changes in the NGINX configuration.
......
......@@ -3,6 +3,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
include SharedPaths
include SharedProject
include SharedUser
include WaitForAjax
step '"John Doe" is a developer of project "Shop"' do
project.team << [john_doe, :developer]
......@@ -138,6 +139,8 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
step 'I should be directed to the corresponding page' do
page.should have_css('.identifier', text: 'Merge Request !1')
# Merge request page loads and issues a number of Ajax requests
wait_for_ajax
end
def should_see_todo(position, title, body, state: :pending)
......
......@@ -230,7 +230,7 @@ module API
expose :id, :name, :type, :path
expose :mode do |obj, options|
filemode = obj.mode.to_s(8)
filemode = obj.mode
filemode = "0" + filemode if filemode.length < 6
filemode
end
......
......@@ -109,6 +109,7 @@ module API
requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."
requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
end
<<<<<<< HEAD
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
# GitLab-EE specific settings
optional :help_text, type: String, desc: 'GitLab server administrator information'
......@@ -126,6 +127,10 @@ module API
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.'
optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)'
=======
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.'
>>>>>>> ce-com/master
at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,
:default_group_visibility, :restricted_visibility_levels, :import_sources,
:enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit,
......@@ -141,10 +146,14 @@ module API
:akismet_enabled, :admin_notification_email, :sentry_enabled,
:repository_checks_enabled, :koding_enabled, :housekeeping_enabled, :terminal_max_session_time, :plantuml_enabled,
:version_check_enabled, :email_author_in_body, :html_emails_enabled,
<<<<<<< HEAD
:polling_interval_multiplier,
# GitLab-EE specific settings
:help_text, :elasticsearch_indexing, :usage_ping_enabled,
:repository_storages, :repository_size_limit
=======
:housekeeping_enabled, :terminal_max_session_time, :polling_interval_multiplier
>>>>>>> ce-com/master
end
put "application/settings" do
attrs = declared_params(include_missing: false)
......
......@@ -295,7 +295,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
::Users::DestroyService.new(current_user).execute(user)
DeleteUserWorker.perform_async(current_user.id, user.id)
end
desc 'Block a user. Available only for admins.'
......
......@@ -411,6 +411,11 @@ module Gitlab
rugged.merge_base(from, to)
end
# Returns true is +from+ is direct ancestor to +to+, otherwise false
def is_ancestor?(from, to)
Gitlab::GitalyClient::Commit.is_ancestor(self, from, to)
end
# Return an array of Diff objects that represent the diff
# between +from+ and +to+. See Diff::filter_diff_options for the allowed
# diff options. The +options+ hash can also include :break_rewrites to
......
......@@ -33,7 +33,7 @@ module Gitlab
root_id: root_tree.oid,
name: entry[:name],
type: entry[:type],
mode: entry[:filemode],
mode: entry[:filemode].to_s(8),
path: path ? File.join(path, entry[:name]) : entry[:name],
commit_id: sha,
)
......
......@@ -21,6 +21,20 @@ module Gitlab
Gitlab::Git::DiffCollection.new(stub.commit_diff(request), options)
end
def is_ancestor(repository, ancestor_id, child_id)
project = Project.find_by_path(repository.path)
channel = GitalyClient.get_channel(project.repository_storage)
stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: channel)
repo = Gitaly::Repository.new(path: repository.path_to_repo)
request = Gitaly::CommitIsAncestorRequest.new(
repository: repo,
ancestor_id: ancestor_id,
child_id: child_id
)
stub.commit_is_ancestor(request).value
end
end
end
end
......
......@@ -11,6 +11,14 @@ module Gitlab
sha.present? && ref.present?
end
def user
raw_data.user&.login || 'unknown'
end
def short_sha
Commit.truncate_sha(sha)
end
private
def branch_exists?
......
module Gitlab
module GithubImport
class PullRequestFormatter < IssuableFormatter
delegate :exists?, :project, :ref, :repo, :sha, to: :source_branch, prefix: true
delegate :exists?, :project, :ref, :repo, :sha, to: :target_branch, prefix: true
delegate :user, :project, :ref, :repo, :sha, to: :source_branch, prefix: true
delegate :user, :exists?, :project, :ref, :repo, :sha, :short_sha, to: :target_branch, prefix: true
def attributes
{
......@@ -37,13 +37,20 @@ module Gitlab
end
def source_branch_name
@source_branch_name ||= begin
if cross_project?
"pull/#{number}/#{source_branch_repo.full_name}/#{source_branch_ref}"
@source_branch_name ||=
if cross_project? || !source_branch_exists?
source_branch_name_prefixed
else
source_branch_exists? ? source_branch_ref : "pull/#{number}/#{source_branch_ref}"
source_branch_ref
end
end
def source_branch_name_prefixed
"gh-#{target_branch_short_sha}/#{number}/#{source_branch_user}/#{source_branch_ref}"
end
def source_branch_exists?
!cross_project? && source_branch.exists?
end
def target_branch
......@@ -51,13 +58,17 @@ module Gitlab
end
def target_branch_name
@target_branch_name ||= begin
target_branch_exists? ? target_branch_ref : "pull/#{number}/#{target_branch_ref}"
@target_branch_name ||= target_branch_exists? ? target_branch_ref : target_branch_name_prefixed
end
def target_branch_name_prefixed
"gl-#{target_branch_short_sha}/#{number}/#{target_branch_user}/#{target_branch_ref}"
end
def cross_project?
source_branch.repo.id != target_branch.repo.id
return true if source_branch_repo.nil?
source_branch_repo.id != target_branch_repo.id
end
def opened?
......
......@@ -17,14 +17,22 @@ module Gitlab
class << self
def git_http_ok(repository, user)
repo_path = repository.path_to_repo
params = {
GL_ID: Gitlab::GlId.gl_id(user),
RepoPath: repository.path_to_repo,
RepoPath: repo_path,
}
if Gitlab.config.gitaly.enabled
address = Gitlab::GitalyClient.get_address(repository.project.repository_storage)
storage = repository.project.repository_storage
address = Gitlab::GitalyClient.get_address(storage)
params[:GitalySocketPath] = URI(address).path
# TODO: use GitalyClient code to assemble the Repository message
params[:Repository] = Gitaly::Repository.new(
path: repo_path,
storage_name: storage,
relative_path: Gitlab::RepoPath.strip_storage_path(repo_path),
).to_h
end
params
......
require 'spec_helper'
describe Profiles::AccountsController do
let(:user) { create(:omniauth_user, provider: 'saml') }
describe 'DELETE unlink' do
let(:user) { create(:omniauth_user) }
before do
sign_in(user)
end
it 'does not allow to unlink SAML connected account' do
it 'renders 404 if someone tries to unlink a non existent provider' do
delete :unlink, provider: 'github'
expect(response).to have_http_status(404)
end
[:saml, :cas3].each do |provider|
describe "#{provider} provider" do
let(:user) { create(:omniauth_user, provider: provider.to_s) }
it "does not allow to unlink connected account" do
identity = user.identities.last
delete :unlink, provider: 'saml'
updated_user = User.find(user.id)
delete :unlink, provider: provider.to_s
expect(response).to have_http_status(302)
expect(updated_user.identities.size).to eq(1)
expect(updated_user.identities).to include(identity)
expect(user.reload.identities).to include(identity)
end
end
end
it 'does allow to delete other linked accounts' do
user.identities.create(provider: 'twitter', extern_uid: 'twitter_123')
[:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider|
describe "#{provider} provider" do
let(:user) { create(:omniauth_user, provider: provider.to_s) }
expect { delete :unlink, provider: 'twitter' }.to change(Identity.all, :size).by(-1)
it 'allows to unlink connected account' do
identity = user.identities.last
delete :unlink, provider: provider.to_s
expect(response).to have_http_status(302)
expect(user.reload.identities).not_to include(identity)
end
end
end
end
end
......@@ -68,4 +68,20 @@ describe RegistrationsController do
end
end
end
describe '#destroy' do
let(:user) { create(:user) }
before do
sign_in(user)
end
it 'schedules the user for destruction' do
expect(DeleteUserWorker).to receive(:perform_async).with(user.id, user.id)
post(:destroy)
expect(response.status).to eq(302)
end
end
end
......@@ -219,4 +219,20 @@ describe SessionsController do
end
end
end
describe '#new' do
before do
@request.env['devise.mapping'] = Devise.mappings[:user]
end
it 'redirects correctly for referer on same host with params' do
search_path = '/search?search=seed_project'
allow(controller.request).to receive(:referer).
and_return('http://%{host}%{path}' % { host: Gitlab.config.gitlab.host, path: search_path })
get(:new, redirect_to_referer: :yes)
expect(controller.stored_location_for(:redirect)).to eq(search_path)
end
end
end
......@@ -9,7 +9,7 @@ describe IssuesFinder do
let(:label) { create(:label, project: project2) }
let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone, title: 'gitlab') }
let(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'gitlab') }
let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2, title: 'tanuki', description: 'tanuki') }
describe '#execute' do
let(:closed_issue) { create(:issue, author: user2, assignee: user2, project: project2, state: 'closed') }
......
......@@ -62,4 +62,18 @@ describe AuthHelper do
end
end
end
describe 'unlink_allowed?' do
[:saml, :cas3].each do |provider|
it "returns true if the provider is #{provider}" do
expect(helper.unlink_allowed?(provider)).to be false
end
end
[:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider|
it "returns false if the provider is #{provider}" do
expect(helper.unlink_allowed?(provider)).to be true
end
end
end
end
......@@ -91,6 +91,10 @@ describe('Environment', () => {
});
describe('pagination', () => {
afterEach(() => {
window.history.pushState({}, null, '');
});
it('should render pagination', (done) => {
setTimeout(() => {
expect(
......
......@@ -46,6 +46,10 @@ require('~/lib/utils/common_utils');
spyOn(window.document, 'getElementById').and.callThrough();
});
afterEach(() => {
window.history.pushState({}, null, '');
});
function expectGetElementIdToHaveBeenCalledWith(elementId) {
expect(window.document.getElementById).toHaveBeenCalledWith(elementId);
}
......@@ -75,11 +79,56 @@ require('~/lib/utils/common_utils');
});
});
describe('gl.utils.setParamInURL', () => {
afterEach(() => {
window.history.pushState({}, null, '');
});
it('should return the parameter', () => {
window.history.replaceState({}, null, '');
expect(gl.utils.setParamInURL('page', 156)).toBe('?page=156');
expect(gl.utils.setParamInURL('page', '156')).toBe('?page=156');
});
it('should update the existing parameter when its a number', () => {
window.history.pushState({}, null, '?page=15');
expect(gl.utils.setParamInURL('page', 16)).toBe('?page=16');
expect(gl.utils.setParamInURL('page', '16')).toBe('?page=16');
expect(gl.utils.setParamInURL('page', true)).toBe('?page=true');
});
it('should update the existing parameter when its a string', () => {
window.history.pushState({}, null, '?scope=all');
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished');
});
it('should update the existing parameter when more than one parameter exists', () => {
window.history.pushState({}, null, '?scope=all&page=15');
expect(gl.utils.setParamInURL('scope', 'finished')).toBe('?scope=finished&page=15');
});
it('should add a new parameter to the end of the existing ones', () => {
window.history.pushState({}, null, '?scope=all');
expect(gl.utils.setParamInURL('page', 16)).toBe('?scope=all&page=16');
expect(gl.utils.setParamInURL('page', '16')).toBe('?scope=all&page=16');
expect(gl.utils.setParamInURL('page', true)).toBe('?scope=all&page=true');
});
});
describe('gl.utils.getParameterByName', () => {
beforeEach(() => {
window.history.pushState({}, null, '?scope=all&p=2');
});
afterEach(() => {
window.history.replaceState({}, null, null);
});
it('should return valid parameter', () => {
const value = gl.utils.getParameterByName('scope');
expect(value).toBe('all');
......
......@@ -124,6 +124,10 @@ describe('Pagination component', () => {
});
describe('paramHelper', () => {
afterEach(() => {
window.history.pushState({}, null, '');
});
it('can parse url parameters correctly', () => {
window.history.pushState({}, null, '?scope=all&p=2');
......
......@@ -19,6 +19,7 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(dir.commit_id).to eq(SeedRepo::Commit::ID) }
it { expect(dir.name).to eq('encoding') }
it { expect(dir.path).to eq('encoding') }
it { expect(dir.mode).to eq('40000') }
context :subdir do
let(:subdir) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files').first }
......
......@@ -215,9 +215,9 @@ describe Gitlab::GithubImport::Importer, lib: true do
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:repository) { double(id: 1, fork: false) }
let(:source_sha) { create(:commit, project: project).id }
let(:source_branch) { double(ref: 'branch-merged', repo: repository, sha: source_sha) }
let(:source_branch) { double(ref: 'branch-merged', repo: repository, sha: source_sha, user: octocat) }
let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha, user: octocat) }
let(:pull_request) do
double(
number: 1347,
......
......@@ -4,15 +4,18 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:client) { double }
let(:project) { create(:project, :repository) }
let(:source_sha) { create(:commit, project: project).id }
let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
let(:target_commit) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit) }
let(:target_sha) { target_commit.id }
let(:target_short_sha) { target_commit.id.to_s[0..7] }
let(:repository) { double(id: 1, fork: false) }
let(:source_repo) { repository }
let(:source_branch) { double(ref: 'branch-merged', repo: source_repo, sha: source_sha) }
let(:forked_source_repo) { double(id: 2, fork: true, name: 'otherproject', full_name: 'company/otherproject') }
let(:target_repo) { repository }
let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha) }
let(:removed_branch) { double(ref: 'removed-branch', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
let(:forked_branch) { double(ref: 'master', repo: forked_source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha, user: octocat) }
let(:removed_branch) { double(ref: 'removed-branch', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', user: octocat) }
let(:forked_branch) { double(ref: 'master', repo: forked_source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', user: octocat) }
let(:branch_deleted_repo) { double(ref: 'master', repo: nil, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b', user: octocat) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
......@@ -203,16 +206,24 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
context 'when source branch does not exist' do
let(:raw_data) { double(base_data.merge(head: removed_branch)) }
it 'prefixes branch name with pull request number' do
expect(pull_request.source_branch_name).to eq 'pull/1347/removed-branch'
it 'prefixes branch name with gh-:short_sha/:number/:user pattern to avoid collision' do
expect(pull_request.source_branch_name).to eq "gh-#{target_short_sha}/1347/octocat/removed-branch"
end
end
context 'when source branch is from a fork' do
let(:raw_data) { double(base_data.merge(head: forked_branch)) }
it 'prefixes branch name with pull request number and project with namespace to avoid collision' do
expect(pull_request.source_branch_name).to eq 'pull/1347/company/otherproject/master'
it 'prefixes branch name with gh-:short_sha/:number/:user pattern to avoid collision' do
expect(pull_request.source_branch_name).to eq "gh-#{target_short_sha}/1347/octocat/master"
end
end
context 'when source branch is from a deleted fork' do
let(:raw_data) { double(base_data.merge(head: branch_deleted_repo)) }
it 'prefixes branch name with gh-:short_sha/:number/:user pattern to avoid collision' do
expect(pull_request.source_branch_name).to eq "gh-#{target_short_sha}/1347/octocat/master"
end
end
end
......@@ -229,8 +240,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
context 'when target branch does not exist' do
let(:raw_data) { double(base_data.merge(base: removed_branch)) }
it 'prefixes branch name with pull request number' do
expect(pull_request.target_branch_name).to eq 'pull/1347/removed-branch'
it 'prefixes branch name with gh-:short_sha/:number/:user pattern to avoid collision' do
expect(pull_request.target_branch_name).to eq 'gl-2e5d3239/1347/octocat/removed-branch'
end
end
end
......@@ -290,6 +301,14 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
context 'when source repository does not exist anymore' do
let(:raw_data) { double(base_data.merge(head: branch_deleted_repo)) }
it 'returns true' do
expect(pull_request.cross_project?).to eq true
end
end
context 'when source and target repositories are the same' do
let(:raw_data) { double(base_data.merge(head: source_branch)) }
......@@ -299,6 +318,14 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
describe '#source_branch_exists?' do
let(:raw_data) { double(base_data.merge(head: forked_branch)) }
it 'returns false when is a cross_project' do
expect(pull_request.source_branch_exists?).to eq false
end
end
describe '#url' do
let(:raw_data) { double(base_data) }
......
......@@ -179,10 +179,11 @@ describe Gitlab::Workhorse, lib: true do
describe '.git_http_ok' do
let(:user) { create(:user) }
let(:repo_path) { repository.path_to_repo }
subject { described_class.git_http_ok(repository, user) }
it { expect(subject).to eq({ GL_ID: "user-#{user.id}", RepoPath: repository.path_to_repo }) }
it { expect(subject).to eq({ GL_ID: "user-#{user.id}", RepoPath: repo_path }) }
context 'when Gitaly is enabled' do
before do
......@@ -192,6 +193,11 @@ describe Gitlab::Workhorse, lib: true do
it 'includes Gitaly params in the returned value' do
gitaly_socket_path = URI(Gitlab::GitalyClient.get_address('default')).path
expect(subject).to include({ GitalySocketPath: gitaly_socket_path })
expect(subject[:Repository]).to include({
path: repo_path,
storage_name: 'default',
relative_path: project.full_path + '.git',
})
end
end
end
......
......@@ -1995,6 +1995,7 @@ describe Repository, models: true do
end
end
<<<<<<< HEAD
describe '#after_sync' do
it 'expires repository cache' do
expect(repository).to receive(:expire_all_method_caches)
......@@ -2009,4 +2010,18 @@ describe Repository, models: true do
rugged = repository.rugged
rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id)
end
=======
describe '#is_ancestor?' do
context 'Gitaly is_ancestor feature enabled' do
it 'asks Gitaly server if it\'s an ancestor' do
commit = repository.commit
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:is_ancestor).and_return(true)
expect(Gitlab::GitalyClient::Commit).to receive(:is_ancestor).
with(repository.raw_repository, commit.id, commit.id).and_return(true)
expect(repository.is_ancestor?(commit.id, commit.id)).to be true
end
end
end
>>>>>>> ce-com/master
end
......@@ -688,7 +688,7 @@ describe API::Users, api: true do
before { admin }
it "deletes user" do
delete api("/users/#{user.id}", admin)
Sidekiq::Testing.inline! { delete api("/users/#{user.id}", admin) }
expect(response).to have_http_status(204)
expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound
......@@ -696,23 +696,23 @@ describe API::Users, api: true do
end
it "does not delete for unauthenticated user" do
delete api("/users/#{user.id}")
Sidekiq::Testing.inline! { delete api("/users/#{user.id}") }
expect(response).to have_http_status(401)
end
it "is not available for non admin users" do
delete api("/users/#{user.id}", user)
Sidekiq::Testing.inline! { delete api("/users/#{user.id}", user) }
expect(response).to have_http_status(403)
end
it "returns 404 for non-existing user" do
delete api("/users/999999", admin)
Sidekiq::Testing.inline! { delete api("/users/999999", admin) }
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
it "returns a 404 for invalid ID" do
delete api("/users/ASDF", admin)
Sidekiq::Testing.inline! { delete api("/users/ASDF", admin) }
expect(response).to have_http_status(404)
end
......
require 'spec_helper'
describe Search::GlobalService, services: true do
let(:user) { create(:user) }
let(:internal_user) { create(:user) }
let!(:found_project) { create(:empty_project, :private, name: 'searchable_project') }
let!(:unfound_project) { create(:empty_project, :private, name: 'unfound_project') }
let!(:internal_project) { create(:empty_project, :internal, name: 'searchable_internal_project') }
let!(:public_project) { create(:empty_project, :public, name: 'searchable_public_project') }
before do
found_project.add_master(user)
end
describe '#execute' do
context 'unauthenticated' do
it 'returns public projects only' do
results = Search::GlobalService.new(nil, search: "searchable").execute
expect(results.objects('projects')).to match_array [public_project]
end
end
context 'authenticated' do
it 'returns public, internal and private projects' do
results = Search::GlobalService.new(user, search: "searchable").execute
expect(results.objects('projects')).to match_array [public_project, found_project, internal_project]
end
it 'returns only public & internal projects' do
results = Search::GlobalService.new(internal_user, search: "searchable").execute
expect(results.objects('projects')).to match_array [internal_project, public_project]
end
it 'namespace name is searchable' do
results = Search::GlobalService.new(user, search: found_project.namespace.path).execute
expect(results.objects('projects')).to match_array [found_project]
end
context 'nested group' do
let!(:nested_group) { create(:group, :nested) }
let!(:project) { create(:empty_project, namespace: nested_group) }
before do
project.add_master(user)
end
it 'returns result from nested group' do
results = Search::GlobalService.new(user, search: project.path).execute
expect(results.objects('projects')).to match_array [project]
end
it 'returns result from descendants when search inside group' do
results = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent).execute
expect(results.objects('projects')).to match_array [project]
end
end
end
end
end
This diff is collapsed.
......@@ -17,13 +17,28 @@ describe Users::DestroyService, services: true do
expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'will delete the project in the near future' do
expect_any_instance_of(Projects::DestroyService).to receive(:async_execute).once
it 'will delete the project' do
expect_any_instance_of(Projects::DestroyService).to receive(:execute).once
service.execute(user)
end
end
context 'projects in pending_delete' do
before do
project.pending_delete = true
project.save
end
it 'destroys a project in pending_delete' do
expect_any_instance_of(Projects::DestroyService).to receive(:execute).once
service.execute(user)
expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context "a deleted user's issues" do
let(:project) { create(:project) }
......
......@@ -133,9 +133,11 @@ module TestEnv
set_repo_refs(repo_path, branch_sha)
unless File.directory?(repo_path_bare)
# We must copy bare repositories because we will push to them.
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone -q --bare #{repo_path} #{repo_path_bare}))
end
end
def copy_repo(project)
base_repo_path = File.expand_path(factory_repo_path_bare)
......
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