Commit 06215a1d authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'ce-to-ee-2018-01-12' into 'master'

CE upstream - Friday

Closes gitlab-com/support-forum#2883, gitlab-ce#41956, gitaly#866, gitaly#863, gitlab-ce#41163, gitaly#851, and gitlab-ce#37447

See merge request gitlab-org/gitlab-ee!4053
parents 206fab80 f11710e2
......@@ -2,6 +2,7 @@
*.swp
*.mo
*.edit.po
*.rej
.DS_Store
.bundle
.chef
......
......@@ -65,6 +65,9 @@ stages:
.use-pg: &use-pg
services:
# As of Jan 2018, we don't have a strong reason to upgrade to 9.6 for CI yet,
# so using the least common denominator ensures backwards compatibility
# (as many users are still using 9.2).
- postgres:9.2
- redis:alpine
- docker.elastic.co/elasticsearch/elasticsearch:5.5.2
......
......@@ -46,14 +46,15 @@
));
const extraCostParagraph = sprintf(
_.escape(s__(`ClusterIntegration|%{boldNotice} This will add some
extra resources like a load balancer,
which incur additional costs. See %{pricingLink}`)),
{
_.escape(s__(
`ClusterIntegration|%{boldNotice} This will add some extra resources
like a load balancer, which may incur additional costs depending on
the hosting provider Kubernetes is installed on. If you are using GKE,
you can %{pricingLink}.`,
)), {
boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GKE pricing'))}
</a>`,
${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
},
false,
);
......@@ -80,8 +81,7 @@ which incur additional costs. See %{pricingLink}`)),
{
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))}
</a>`,
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
);
......
......@@ -593,6 +593,11 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
break;
case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens')
.then(callDefault)
.catch(fail);
break;
case 'admin:impersonation_tokens:index':
import('./pages/admin/impersonation_tokens')
.then(callDefault)
......
......@@ -30,8 +30,12 @@
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length;
},
/**
* When job has not started the key will be `false`
* When job started the key will be a string with a date.
*/
jobStarted() {
return this.job.started;
return !this.job.started === false;
},
},
watch: {
......
......@@ -271,7 +271,7 @@ Please check your network connection and try again.`;
<div class="timeline-content timeline-content-form">
<form
ref="commentForm"
class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"
class="new-note common-note-form gfm-form js-main-target-form"
>
<div class="error-alert"></div>
......@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
:disabled="isSubmitting"
placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()">
@keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()">
</textarea>
</markdown-field>
<div class="note-form-actions">
......
......@@ -156,6 +156,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
slot="textarea"
placeholder="Write a comment or drag your files here..."
@keydown.meta.enter="handleUpdate()"
@keydown.ctrl.enter="handleUpdate()"
@keydown.up="editMyLastNote()"
@keydown.esc="cancelHandler(true)">
</textarea>
......
......@@ -19,11 +19,8 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
$(navEl).on('show.bs.dropdown', (e) => {
const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu');
dropdownEl.one('transitionend', () => {
eventHub.$emit('dropdownOpen');
});
$(navEl).on('shown.bs.dropdown', () => {
eventHub.$emit('dropdownOpen');
});
// eslint-disable-next-line no-new
......
<script>
import tooltip from '../directives/tooltip';
/**
* Falls back to the code used in `copy_to_clipboard.js`
*/
export default {
name: 'ClipboardButton',
directives: {
tooltip,
},
props: {
text: {
type: String,
......@@ -14,6 +18,16 @@
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
tooltipContainer: {
type: [String, Boolean],
required: false,
default: false,
},
},
};
</script>
......@@ -22,8 +36,11 @@
<button
type="button"
class="btn btn-transparent btn-clipboard"
:data-title="title"
:title="title"
:data-clipboard-text="text"
v-tooltip
:data-container="tooltipContainer"
:data-placement="tooltipPlacement"
>
<i
aria-hidden="true"
......
......@@ -825,7 +825,6 @@ a.linked-pipeline-mini-item {
// link to the build
.mini-pipeline-graph-dropdown-item {
padding: 3px 7px 4px;
align-items: center;
clear: both;
display: flex;
......
......@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController
:project,
:tags
])
@builds = @builds.page(params[:page]).per(30)
@builds = @builds.page(params[:page]).per(30).without_count
end
def cancel_all
......
......@@ -8,7 +8,7 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
validate :ensure_permanent_paths
validate :ensure_permanent_paths, if: :path_changed?
after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed?
......
......@@ -7,7 +7,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
......@@ -30,7 +30,7 @@
%li CI variables
%li Any encrypted tokens
%p
Once the exported file is ready, you will receive a notification email with a download link.
Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- if project.export_project_path
= link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
......
......@@ -4,7 +4,7 @@
.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
%h1.project-title.qa-project-name
= @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false)
......
......@@ -9,7 +9,7 @@
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1}
- else
.input-group-addon.static-namespace
......
......@@ -22,4 +22,4 @@
= render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, pipeline_link: true, stage: true, allow_retry: true, admin: admin }
= paginate builds, theme: 'gitlab'
= paginate_collection(builds)
......@@ -27,7 +27,7 @@
Edit
- if @project.group
= link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "Promoting this milestone will make it available for all projects inside the group. Existing project milestones with the same name will be merged. Are you sure?", toggle: "tooltip" }, method: :post do
= link_to promote_project_milestone_path(@milestone.project, @milestone), title: "Promote to Group Milestone", class: 'btn btn-grouped', data: { confirm: "You are about to promote #{@milestone.title} to a group level. This will make this milestone available to all projects inside #{@project.group.name}. The existing project milestone will be merged into the group level. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
Promote
- if @milestone.active?
......
......@@ -7,7 +7,7 @@
%span
= enabled_project_button(project, enabled_protocol)
- else
%a#clone-dropdown.btn.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown' } }
%a#clone-dropdown.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span
= default_clone_protocol.upcase
= icon('caret-down')
......
......@@ -77,7 +77,7 @@
= icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
= link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
= link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "You are about to promote #{label.title} to a group level. This will make this milestone available to all projects inside #{label.project.group.name}. The existing project label will be merged into the group level. This action cannot be reversed.", toggle: "tooltip"}, method: :post do
%span.sr-only Promote to Group
= icon('level-up')
- if can?(current_user, :admin_label, label)
......
......@@ -51,7 +51,7 @@
\
- if @project.group
= link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "Promoting this milestone will make it available for all projects inside the group. Existing project milestones with the same name will be merged. Are you sure?", toggle: "tooltip" }, method: :post do
= link_to promote_project_milestone_path(milestone.project, milestone), title: "Promote to Group Milestone", class: 'btn btn-xs btn-grouped', data: { confirm: "You are about to promote #{milestone.title} to a group level. This will make this milestone available to all projects inside #{@project.group.name}. The existing project milestone will be merged into the group level. This action cannot be reversed.", toggle: "tooltip" }, method: :post do
Promote
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
......
module ProjectImportOptions
extend ActiveSupport::Concern
included do
IMPORT_RETRY_COUNT = 5
IMPORT_RETRY_COUNT = 5
included do
sidekiq_options retry: IMPORT_RETRY_COUNT, status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
# We only want to mark the project as failed once we exhausted all retries
......
---
title: Improve wording about additional costs for Ingress on custom clusters
merge_request:
author:
type: changed
---
title: Add reason to keep postgresql 9.2 for CI
merge_request: 16277
author: Takuya Noguchi
type: other
---
title: Ensure that emails contain absolute, rather than relative, links to user uploads
merge_request: 16364
author:
type: fixed
---
title: Fix Ctrl+Enter keyboard shortcut saving comment/note edit
merge_request: 16415
author:
type: fixed
---
title: Use simple Next/Prev paging for jobs to avoid large count queries on arbitrarily
large sets of historical jobs
merge_request:
author:
type: performance
---
title: Prevent invalid Route path if path is unchanged
merge_request: 16397
author:
type: fixed
......@@ -78,6 +78,7 @@ module Gitlab
# - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url)
# - Build traces (:trace)
# - Build variables (:variables)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook)
......@@ -92,6 +93,7 @@ module Gitlab
key
otp_attempt
sentry_dsn
trace
variables
)
......
......@@ -2,7 +2,7 @@
> [Introduced][ce-6373] in GitLab 8.15.
### Snippet visibility level
## Snippet visibility level
Snippets in GitLab can be either private, internal, or public.
You can set it with the `visibility` field in the snippet.
......@@ -273,4 +273,5 @@ Example response:
}
```
[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
[ce-6373]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6373
[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12655
......@@ -420,6 +420,10 @@ GET /user
}
```
## List user projects
Please refer to the [List of user projects ](projects.md#list-user-projects).
## List SSH keys
Get a list of currently authenticated user's SSH keys.
......
......@@ -83,6 +83,15 @@ Leverage the power of Docker to run your CI pipelines.
- [CI services (linked Docker containers)](services/README.md)
- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
## GitLab CI/CD for Docker
Leverage the power of Docker to run your CI pipelines.
- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
- [Use CI to build Docker images](docker/using_docker_build.md)
- [CI services (linked Docker containers)](services/README.md)
- Article (2016-03-01): [Setting up GitLab Runner For Continuous Integration](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
## Review Apps
- [Review Apps documentation](review_apps/index.md)
......
......@@ -64,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
......
......@@ -5,8 +5,8 @@
An application data backup creates an archive file that contains the database,
all repositories and all attachments.
You can only restore a backup to **exactly the same version and type (CE/EE)**
of GitLab on which it was created. The best way to migrate your repositories
You can only restore a backup to **exactly the same version and type (CE/EE)**
of GitLab on which it was created. The best way to migrate your repositories
from one server to another is through backup restore.
## Backup
......@@ -14,6 +14,19 @@ from one server to another is through backup restore.
GitLab provides a simple command line interface to backup your whole installation,
and is flexible enough to fit your needs.
### Requirements
If you're using GitLab with the Omnibus package, you're all set. If you
installed GitLab from source, make sure the following packages are installed:
* rsync
If you're using Ubuntu, you could run:
```
sudo apt-get install -y rsync
```
### Backup timestamp
>**Note:**
......@@ -431,7 +444,7 @@ The [restore prerequisites section](#restore-prerequisites) includes crucial
information. Make sure to read and test the whole restore process at least once
before attempting to perform it in a production environment.
You can only restore a backup to **exactly the same version and type (CE/EE)** of
You can only restore a backup to **exactly the same version and type (CE/EE)** of
GitLab that you created it on, for example CE 9.1.0.
### Restore prerequisites
......@@ -511,7 +524,7 @@ sudo service gitlab restart
This procedure assumes that:
- You have installed the **exact same version and type (CE/EE)** of GitLab
- You have installed the **exact same version and type (CE/EE)** of GitLab
Omnibus with which the backup was created.
- You have run `sudo gitlab-ctl reconfigure` at least once.
- GitLab is running. If not, start it using `sudo gitlab-ctl start`.
......
......@@ -21,6 +21,8 @@ sudo service gitlab stop
### 2. Backup
NOTE: If you installed GitLab from source, make sure `rsync` is installed.
```bash
cd /home/git/gitlab
......
......@@ -50,15 +50,22 @@ module Banzai
end
def process_link_to_upload_attr(html_attr)
uri_parts = [html_attr.value]
path_parts = [html_attr.value]
if group
uri_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
elsif project
uri_parts.unshift(relative_url_root, project.full_path)
path_parts.unshift(relative_url_root, project.full_path)
end
html_attr.value = File.join(*uri_parts)
path = File.join(*path_parts)
html_attr.value =
if context[:only_path]
path
else
URI.join(Gitlab.config.gitlab.base_url, path).to_s
end
end
def process_link_to_repository_attr(html_attr)
......
......@@ -571,7 +571,21 @@ module Gitlab
end
def merged_branch_names(branch_names = [])
Set.new(git_merged_branch_names(branch_names))
return [] unless root_ref
root_sha = find_branch(root_ref)&.target
return [] unless root_sha
branches = gitaly_migrate(:merged_branch_names) do |is_enabled|
if is_enabled
gitaly_merged_branch_names(branch_names, root_sha)
else
git_merged_branch_names(branch_names, root_sha)
end
end
Set.new(branches)
end
# Return an array of Diff objects that represent the diff
......@@ -1222,33 +1236,31 @@ module Gitlab
end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
env = git_env_for_user(user)
if remote_repository.is_a?(RemoteRepository)
env.merge!(remote_repository.fetch_env)
remote_repo_path = GITALY_INTERNAL_URL
else
remote_repo_path = remote_repository.path
end
with_worktree(rebase_path, branch, env: env) do
run_git!(
%W(pull --rebase #{remote_repo_path} #{remote_branch}),
chdir: rebase_path, env: env
)
rebase_sha = run_git!(%w(rev-parse HEAD), chdir: rebase_path, env: env).strip
Gitlab::Git::OperationService.new(user, self)
.update_branch(branch, rebase_sha, branch_sha)
rebase_sha
gitaly_migrate(:rebase) do |is_enabled|
if is_enabled
gitaly_rebase(user, rebase_id,
branch: branch,
branch_sha: branch_sha,
remote_repository: remote_repository,
remote_branch: remote_branch)
else
git_rebase(user, rebase_id,
branch: branch,
branch_sha: branch_sha,
remote_repository: remote_repository,
remote_branch: remote_branch)
end
end
end
def rebase_in_progress?(rebase_id)
fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
gitaly_migrate(:rebase_in_progress) do |is_enabled|
if is_enabled
gitaly_repository_client.rebase_in_progress?(rebase_id)
else
fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
end
end
end
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
......@@ -1483,14 +1495,7 @@ module Gitlab
sort_branches(branches, sort_by)
end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695
def git_merged_branch_names(branch_names = [])
return [] unless root_ref
root_sha = find_branch(root_ref)&.target
return [] unless root_sha
def git_merged_branch_names(branch_names, root_sha)
git_arguments =
%W[branch --merged #{root_sha}
--format=%(refname:short)\ %(objectname)] + branch_names
......@@ -1504,6 +1509,14 @@ module Gitlab
end
end
def gitaly_merged_branch_names(branch_names, root_sha)
qualified_branch_names = branch_names.map { |b| "refs/heads/#{b}" }
gitaly_ref_client.merged_branches(qualified_branch_names)
.reject { |b| b.target == root_sha }
.map(&:name)
end
def process_count_commits_options(options)
if options[:from] || options[:to]
ref =
......@@ -2018,6 +2031,40 @@ module Gitlab
tree_id
end
def gitaly_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
gitaly_operation_client.user_rebase(user, rebase_id,
branch: branch,
branch_sha: branch_sha,
remote_repository: remote_repository,
remote_branch: remote_branch)
end
def git_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
env = git_env_for_user(user)
if remote_repository.is_a?(RemoteRepository)
env.merge!(remote_repository.fetch_env)
remote_repo_path = GITALY_INTERNAL_URL
else
remote_repo_path = remote_repository.path
end
with_worktree(rebase_path, branch, env: env) do
run_git!(
%W(pull --rebase #{remote_repo_path} #{remote_branch}),
chdir: rebase_path, env: env
)
rebase_sha = run_git!(%w(rev-parse HEAD), chdir: rebase_path, env: env).strip
Gitlab::Git::OperationService.new(user, self)
.update_branch(branch, rebase_sha, branch_sha)
rebase_sha
end
end
def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args)
......
......@@ -147,6 +147,34 @@ module Gitlab
start_repository: start_repository)
end
def user_rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
request = Gitaly::UserRebaseRequest.new(
repository: @gitaly_repo,
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
rebase_id: rebase_id.to_s,
branch: encode_binary(branch),
branch_sha: branch_sha,
remote_repository: remote_repository.gitaly_repository,
remote_branch: encode_binary(remote_branch)
)
response = GitalyClient.call(
@repository.storage,
:operation_service,
:user_rebase,
request,
remote_storage: remote_repository.storage
)
if response.pre_receive_error.presence
raise Gitlab::Git::HooksService::PreReceiveError, response.pre_receive_error
elsif response.git_error.presence
raise Gitlab::Git::Repository::GitError, response.git_error
else
response.rebase_sha
end
end
private
def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
......
......@@ -14,12 +14,18 @@ module Gitlab
request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request)
response.flat_map do |message|
message.branches.map do |branch|
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target)
Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit)
end
end
consume_find_all_branches_response(response)
end
def merged_branches(branch_names = [])
request = Gitaly::FindAllBranchesRequest.new(
repository: @gitaly_repo,
merged_only: true,
merged_branches: branch_names.map { |s| encode_binary(s) }
)
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request)
consume_find_all_branches_response(response)
end
def default_branch_name
......@@ -62,7 +68,7 @@ module Gitlab
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by
response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request)
consume_branches_response(response)
consume_find_local_branches_response(response)
end
def tags
......@@ -151,7 +157,7 @@ module Gitlab
enum_value
end
def consume_branches_response(response)
def consume_find_local_branches_response(response)
response.flat_map do |message|
message.branches.map do |gitaly_branch|
Gitlab::Git::Branch.new(
......@@ -164,6 +170,15 @@ module Gitlab
end
end
def consume_find_all_branches_response(response)
response.flat_map do |message|
message.branches.map do |branch|
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target)
Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit)
end
end
end
def consume_tags_response(response)
response.flat_map do |message|
message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) }
......
......@@ -100,6 +100,23 @@ module Gitlab
)
end
def rebase_in_progress?(rebase_id)
request = Gitaly::IsRebaseInProgressRequest.new(
repository: @gitaly_repo,
rebase_id: rebase_id.to_s
)
response = GitalyClient.call(
@storage,
:repository_service,
:is_rebase_in_progress,
request,
timeout: GitalyClient.default_timeout
)
response.in_progress
end
def fetch_source_branch(source_repository, source_branch, local_ref)
request = Gitaly::FetchSourceBranchRequest.new(
repository: @gitaly_repo,
......
......@@ -98,6 +98,9 @@ module Gitlab
)
end
# If present DisableCache must be a Boolean. Otherwise workhorse ignores it.
params['DisableCache'] = true if git_archive_cache_disabled?
[
SEND_DATA_HEADER,
"git-archive:#{encode(params)}"
......@@ -248,6 +251,10 @@ module Gitlab
right_commit_id: diff_refs.head_sha
}
end
def git_archive_cache_disabled?
ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled)
end
end
end
end
......@@ -17,6 +17,17 @@ against any existing instance.
1. Along with GitLab Docker Images we also build and publish GitLab QA images.
1. GitLab QA project uses these images to execute integration tests.
## Validating GitLab views / partials / selectors in merge requests
We recently added a new CI job that is going to be triggered for every push
event in CE and EE projects. The job is called `qa:selectors` and it will
verify coupling between page objects implemented as a part of GitLab QA
and corresponding views / partials / selectors in CE / EE.
Whenever `qa:selectors` job fails in your merge request, you are supposed to
fix [page objects](qa/page/README.md). You should also trigger end-to-end tests
using `package-qa` manual action, to test if everything works fine.
## How can I use it?
You can use GitLab QA to exercise tests on any live instance! For example, the
......
......@@ -2,16 +2,18 @@ module QA
module Page
module Project
class New < Page::Base
##
# TODO, define all selectors required by this page object
#
# See gitlab-org/gitlab-qa#154
#
view 'app/views/projects/new.html.haml'
view 'app/views/projects/_new_project_fields.html.haml' do
element :project_namespace_select
element :project_namespace_field, 'select :namespace_id'
element :project_path, 'text_field :path'
element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'"
end
def choose_test_namespace
find('#s2id_project_namespace_id').click
find('.select2-result-label', text: Runtime::Namespace.name).click
click_element :project_namespace_select
first('li', text: Runtime::Namespace.path).click
end
def choose_name(name)
......
......@@ -2,15 +2,21 @@ module QA
module Page
module Project
class Show < Page::Base
##
# TODO, define all selectors required by this page object
#
# See gitlab-org/gitlab-qa#154
#
view 'app/views/projects/show.html.haml'
view 'app/views/shared/_clone_panel.html.haml' do
element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown'
end
view 'app/views/shared/_clone_panel.html.haml' do
element :project_repository_location, 'text_field_tag :project_clone'
end
view 'app/views/projects/_home_panel.html.haml' do
element :project_name
end
def choose_repository_clone_http
find('#clone-dropdown').click
click_element :clone_dropdown
page.within('.clone-options-dropdown') do
click_link('HTTP')
......@@ -22,7 +28,7 @@ module QA
end
def project_name
find('.project-title').text
find('.qa-project-name').text
end
def wait_for_push
......
......@@ -11,6 +11,10 @@ module QA
'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S')
end
def path
"#{sandbox_name}/#{name}"
end
def sandbox_name
'gitlab-qa-sandbox'
end
......
FactoryBot.define do
factory :redirect_route do
sequence(:path) { |n| "redirect#{n}" }
source factory: :group
permanent false
trait :permanent do
permanent true
end
trait :temporary do
permanent false
end
end
end
......@@ -31,6 +31,7 @@ describe('Job details header', () => {
email: 'foo@bar.com',
avatar_url: 'link',
},
started: '2018-01-08T09:48:27.319Z',
new_issue_path: 'path',
},
isLoading: false,
......@@ -43,15 +44,32 @@ describe('Job details header', () => {
vm.$destroy();
});
it('should render provided job information', () => {
expect(
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
describe('triggered job', () => {
beforeEach(() => {
vm = mountComponent(HeaderComponent, props);
});
it('should render provided job information', () => {
expect(
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
).toEqual('failed Job #123 triggered 3 weeks ago by Foo');
});
it('should render new issue link', () => {
expect(
vm.$el.querySelector('.js-new-issue').getAttribute('href'),
).toEqual(props.job.new_issue_path);
});
});
it('should render new issue link', () => {
expect(
vm.$el.querySelector('.js-new-issue').getAttribute('href'),
).toEqual(props.job.new_issue_path);
describe('created job', () => {
it('should render created key', () => {
props.job.started = false;
vm = mountComponent(HeaderComponent, props);
expect(
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
).toEqual('failed Job #123 created 3 weeks ago by Foo');
});
});
});
......@@ -139,13 +139,21 @@ describe('issue_comment_form component', () => {
});
describe('event enter', () => {
it('should save note when cmd/ctrl+enter is pressed', () => {
it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleSave).toHaveBeenCalled();
});
it('should save note when ctrl+enter is pressed', () => {
spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleSave).toHaveBeenCalled();
});
});
});
......
......@@ -69,11 +69,18 @@ describe('issue_note_form component', () => {
});
describe('enter', () => {
it('should submit note', () => {
it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
it('should save note when ctrl+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
});
......
import Vue from 'vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('clipboard button', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(clipboardButton);
vm = mountComponent(Component, {
text: 'copy me',
title: 'Copy this value into Clipboard!',
});
});
afterEach(() => {
vm.$destroy();
});
it('renders a button for clipboard', () => {
expect(vm.$el.tagName).toEqual('BUTTON');
expect(vm.$el.getAttribute('data-clipboard-text')).toEqual('copy me');
expect(vm.$el.querySelector('i').className).toEqual('fa fa-clipboard');
});
it('should have a tooltip with default values', () => {
expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!');
expect(vm.$el.getAttribute('data-placement')).toEqual('top');
expect(vm.$el.getAttribute('data-container')).toEqual(null);
});
});
......@@ -8,7 +8,8 @@ describe Banzai::Filter::RelativeLinkFilter do
group: group,
project_wiki: project_wiki,
ref: ref,
requested_path: requested_path
requested_path: requested_path,
only_path: only_path
})
described_class.call(doc, contexts)
......@@ -37,6 +38,7 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { project.commit(ref) }
let(:project_wiki) { nil }
let(:requested_path) { '/' }
let(:only_path) { true }
shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do
......@@ -240,26 +242,35 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { nil }
let(:ref) { nil }
let(:requested_path) { nil }
let(:upload_path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
let(:relative_path) { "/#{project.full_path}#{upload_path}" }
context 'to a project upload' do
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
let(:only_path) { false }
it 'rewrites the link correctly' do
doc = filter(link(upload_path))
expect(doc.at_css('a')['href']).to eq(absolute_path)
end
end
it 'rebuilds relative URL for a link' do
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('a')['href'])
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(link(upload_path))
expect(doc.at_css('a')['href']).to eq(relative_path)
doc = filter(nested(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
expect(doc.at_css('a')['href'])
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(nested(link(upload_path)))
expect(doc.at_css('a')['href']).to eq(relative_path)
end
it 'rebuilds relative URL for an image' do
doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('img')['src'])
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(image(upload_path))
expect(doc.at_css('img')['src']).to eq(relative_path)
doc = filter(nested(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')))
expect(doc.at_css('img')['src'])
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(nested(image(upload_path)))
expect(doc.at_css('img')['src']).to eq(relative_path)
end
it 'does not modify absolute URL' do
......@@ -288,6 +299,17 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" }
context 'with an absolute URL' do
let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
let(:only_path) { false }
it 'rewrites the link correctly' do
doc = filter(upload_link)
expect(doc.at_css('a')['href']).to eq(absolute_path)
end
end
it 'rewrites the link correctly' do
doc = filter(upload_link)
......
......@@ -1283,48 +1283,58 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#merged_branch_names' do
context 'when branch names are passed' do
it 'only returns the names we are asking' do
names = repository.merged_branch_names(%w[merge-test])
shared_examples 'finding merged branch names' do
context 'when branch names are passed' do
it 'only returns the names we are asking' do
names = repository.merged_branch_names(%w[merge-test])
expect(names).to contain_exactly('merge-test')
end
expect(names).to contain_exactly('merge-test')
end
it 'does not return unmerged branch names' do
names = repository.merged_branch_names(%w[feature])
it 'does not return unmerged branch names' do
names = repository.merged_branch_names(%w[feature])
expect(names).to be_empty
expect(names).to be_empty
end
end
end
context 'when no root ref is available' do
it 'returns empty list' do
project = create(:project, :empty_repo)
context 'when no root ref is available' do
it 'returns empty list' do
project = create(:project, :empty_repo)
names = project.repository.merged_branch_names(%w[feature])
names = project.repository.merged_branch_names(%w[feature])
expect(names).to be_empty
expect(names).to be_empty
end
end
end
context 'when no branch names are specified' do
before do
repository.create_branch('identical', 'master')
end
context 'when no branch names are specified' do
before do
repository.create_branch('identical', 'master')
end
after do
ensure_seeds
end
after do
ensure_seeds
end
it 'returns all merged branch names except for identical one' do
names = repository.merged_branch_names
it 'returns all merged branch names except for identical one' do
names = repository.merged_branch_names
expect(names).to include('merge-test')
expect(names).to include('fix-mode')
expect(names).not_to include('feature')
expect(names).not_to include('identical')
expect(names).to include('merge-test')
expect(names).to include('fix-mode')
expect(names).not_to include('feature')
expect(names).not_to include('identical')
end
end
end
context 'when Gitaly merged_branch_names feature is enabled' do
it_behaves_like 'finding merged branch names'
end
context 'when Gitaly merged_branch_names feature is disabled', :disable_gitaly do
it_behaves_like 'finding merged branch names'
end
end
describe "#ls_files" do
......
......@@ -26,11 +26,16 @@ describe Gitlab::Workhorse do
'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
)
end
let(:cache_disabled) { false }
subject do
described_class.send_git_archive(repository, ref: ref, format: format)
end
before do
allow(described_class).to receive(:git_archive_cache_disabled?).and_return(cache_disabled)
end
context 'when Gitaly workhorse_archive feature is enabled' do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
......@@ -39,6 +44,15 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-archive')
expect(params).to include(gitaly_params)
end
context 'when archive caching is disabled' do
let(:cache_disabled) { true }
it 'tells workhorse not to use the cache' do
_, _, params = decode_workhorse_header(subject)
expect(params).to include({ 'DisableCache' => true })
end
end
end
context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do
......
......@@ -2353,38 +2353,44 @@ describe MergeRequest do
end
describe '#rebase_in_progress?' do
# Create merge request and project before we stub file calls
before do
subject
end
shared_examples 'checking whether a rebase is in progress' do
let(:repo_path) { subject.source_project.repository.path }
let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
it 'returns true when there is a current rebase directory' do
allow(File).to receive(:exist?).and_return(true)
allow(File).to receive(:mtime).and_return(Time.now)
before do
system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
end
expect(subject.rebase_in_progress?).to be_truthy
end
it 'returns true when there is a current rebase directory' do
expect(subject.rebase_in_progress?).to be_truthy
end
it 'returns false when there is no rebase directory' do
allow(File).to receive(:exist?).and_return(false)
it 'returns false when there is no rebase directory' do
FileUtils.rm_rf(rebase_path)
expect(subject.rebase_in_progress?).to be_falsey
end
expect(subject.rebase_in_progress?).to be_falsey
end
it 'returns false when the rebase directory has expired' do
time = 20.minutes.ago.to_time
File.utime(time, time, rebase_path)
it 'returns false when the rebase directory has expired' do
allow(File).to receive(:exist?).and_return(true)
allow(File).to receive(:mtime).and_return(20.minutes.ago)
expect(subject.rebase_in_progress?).to be_falsey
end
expect(subject.rebase_in_progress?).to be_falsey
it 'returns false when the source project has been removed' do
allow(subject).to receive(:source_project).and_return(nil)
expect(subject.rebase_in_progress?).to be_falsey
end
end
it 'returns false when the source project has been removed' do
allow(subject).to receive(:source_project).and_return(nil)
allow(File).to receive(:exist?).and_return(true)
allow(File).to receive(:mtime).and_return(Time.now)
context 'when Gitaly rebase_in_progress is enabled' do
it_behaves_like 'checking whether a rebase is in progress'
end
expect(File).not_to have_received(:exist?)
expect(subject.rebase_in_progress?).to be_falsey
context 'when Gitaly rebase_in_progress is enabled', :disable_gitaly do
it_behaves_like 'checking whether a rebase is in progress'
end
end
end
......@@ -16,6 +16,66 @@ describe Route do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
describe '#ensure_permanent_paths' do
context 'when the route is not yet persisted' do
let(:new_route) { described_class.new(path: 'foo', source: build(:group)) }
context 'when permanent conflicting redirects exist' do
it 'is invalid' do
redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
redirect.save!(validate: false)
expect(new_route.valid?).to be_falsey
expect(new_route.errors.first[1]).to eq('foo has been taken before. Please use another one')
end
end
context 'when no permanent conflicting redirects exist' do
it 'is valid' do
expect(new_route.valid?).to be_truthy
end
end
end
context 'when path has changed' do
before do
route.path = 'foo'
end
context 'when permanent conflicting redirects exist' do
it 'is invalid' do
redirect = build(:redirect_route, :permanent, path: 'foo/bar/baz')
redirect.save!(validate: false)
expect(route.valid?).to be_falsey
expect(route.errors.first[1]).to eq('foo has been taken before. Please use another one')
end
end
context 'when no permanent conflicting redirects exist' do
it 'is valid' do
expect(route.valid?).to be_truthy
end
end
end
context 'when path has not changed' do
context 'when permanent conflicting redirects exist' do
it 'is valid' do
redirect = build(:redirect_route, :permanent, path: 'git_lab/foo/bar')
redirect.save!(validate: false)
expect(route.valid?).to be_truthy
end
end
context 'when no permanent conflicting redirects exist' do
it 'is valid' do
expect(route.valid?).to be_truthy
end
end
end
end
end
describe 'callbacks' do
......
......@@ -36,7 +36,7 @@ describe MergeRequests::RebaseService do
end
end
context 'when unexpected error occurs' do
context 'when unexpected error occurs', :disable_gitaly do
before do
allow(repository).to receive(:run_git!).and_raise('Something went wrong')
end
......@@ -53,7 +53,7 @@ describe MergeRequests::RebaseService do
end
end
context 'with git command failure' do
context 'with git command failure', :disable_gitaly do
before do
allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong')
end
......@@ -71,31 +71,41 @@ describe MergeRequests::RebaseService do
end
context 'valid params' do
before do
service.execute(merge_request)
end
shared_examples 'successful rebase' do
before do
service.execute(merge_request)
end
it 'rebases source branch' do
parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha
target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
expect(parent_sha).to eq(target_branch_sha)
end
it 'rebases source branch' do
parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha
target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
expect(parent_sha).to eq(target_branch_sha)
end
it 'records the new SHA on the merge request' do
head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
it 'records the new SHA on the merge request' do
head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
expect(merge_request.reload.rebase_commit_sha).to eq(head_sha)
end
it 'logs correct author and commiter' do
head_commit = merge_request.source_project.repository.commit(merge_request.source_branch)
expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com')
expect(head_commit.author_name).to eq('Dmitriy Zaporozhets')
expect(head_commit.committer_email).to eq(user.email)
expect(head_commit.committer_name).to eq(user.name)
end
end
it 'logs correct author and commiter' do
head_commit = merge_request.source_project.repository.commit(merge_request.source_branch)
context 'when Gitaly rebase feature is enabled' do
it_behaves_like 'successful rebase'
end
expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com')
expect(head_commit.author_name).to eq('Dmitriy Zaporozhets')
expect(head_commit.committer_email).to eq(user.email)
expect(head_commit.committer_name).to eq(user.name)
context 'when Gitaly rebase feature is disabled', :disable_gitaly do
it_behaves_like 'successful rebase'
end
context 'git commands' do
context 'git commands', :disable_gitaly do
it 'sets GL_REPOSITORY env variable when calling git commands' do
expect(repository).to receive(:popen).exactly(3)
.with(anything, anything, hash_including('GL_REPOSITORY'))
......@@ -106,27 +116,37 @@ describe MergeRequests::RebaseService do
end
context 'fork' do
let(:forked_project) do
fork_project(project, user, repository: true)
shared_examples 'successful fork rebase' do
let(:forked_project) do
fork_project(project, user, repository: true)
end
let(:merge_request_from_fork) do
forked_project.repository.create_file(
user,
'new-file-to-target',
'',
message: 'Add new file to target',
branch_name: 'master')
create(:merge_request,
source_branch: 'master', source_project: forked_project,
target_branch: 'master', target_project: project)
end
it 'rebases source branch' do
parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha
target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
expect(parent_sha).to eq(target_branch_sha)
end
end
let(:merge_request_from_fork) do
forked_project.repository.create_file(
user,
'new-file-to-target',
'',
message: 'Add new file to target',
branch_name: 'master')
create(:merge_request,
source_branch: 'master', source_project: forked_project,
target_branch: 'master', target_project: project)
context 'when Gitaly rebase feature is enabled' do
it_behaves_like 'successful fork rebase'
end
it 'rebases source branch' do
parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha
target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
expect(parent_sha).to eq(target_branch_sha)
context 'when Gitaly rebase feature is disabled', :disable_gitaly do
it_behaves_like 'successful fork rebase'
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment