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 @@ ...@@ -2,6 +2,7 @@
*.swp *.swp
*.mo *.mo
*.edit.po *.edit.po
*.rej
.DS_Store .DS_Store
.bundle .bundle
.chef .chef
......
...@@ -65,6 +65,9 @@ stages: ...@@ -65,6 +65,9 @@ stages:
.use-pg: &use-pg .use-pg: &use-pg
services: 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 - postgres:9.2
- redis:alpine - redis:alpine
- docker.elastic.co/elasticsearch/elasticsearch:5.5.2 - docker.elastic.co/elasticsearch/elasticsearch:5.5.2
......
...@@ -46,14 +46,15 @@ ...@@ -46,14 +46,15 @@
)); ));
const extraCostParagraph = sprintf( const extraCostParagraph = sprintf(
_.escape(s__(`ClusterIntegration|%{boldNotice} This will add some _.escape(s__(
extra resources like a load balancer, `ClusterIntegration|%{boldNotice} This will add some extra resources
which incur additional costs. See %{pricingLink}`)), 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>`, boldNotice: `<strong>${_.escape(s__('ClusterIntegration|Note:'))}</strong>`,
pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer"> pricingLink: `<a href="https://cloud.google.com/compute/pricing#lb" target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GKE pricing'))} ${_.escape(s__('ClusterIntegration|check the pricing here'))}</a>`,
</a>`,
}, },
false, false,
); );
...@@ -80,8 +81,7 @@ which incur additional costs. See %{pricingLink}`)), ...@@ -80,8 +81,7 @@ which incur additional costs. See %{pricingLink}`)),
{ {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html" gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer"> target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|Gitlab Integration'))} ${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
</a>`,
}, },
false, false,
); );
......
...@@ -593,6 +593,11 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -593,6 +593,11 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
case 'import:fogbugz:new_user_map': case 'import:fogbugz:new_user_map':
import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail); import('./pages/import/fogbugz/new_user_map').then(m => m.default()).catch(fail);
break; break;
case 'profiles:personal_access_tokens:index':
import('./pages/profiles/personal_access_tokens')
.then(callDefault)
.catch(fail);
break;
case 'admin:impersonation_tokens:index': case 'admin:impersonation_tokens:index':
import('./pages/admin/impersonation_tokens') import('./pages/admin/impersonation_tokens')
.then(callDefault) .then(callDefault)
......
...@@ -30,8 +30,12 @@ ...@@ -30,8 +30,12 @@
shouldRenderContent() { shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length; 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() { jobStarted() {
return this.job.started; return !this.job.started === false;
}, },
}, },
watch: { watch: {
......
...@@ -271,7 +271,7 @@ Please check your network connection and try again.`; ...@@ -271,7 +271,7 @@ Please check your network connection and try again.`;
<div class="timeline-content timeline-content-form"> <div class="timeline-content timeline-content-form">
<form <form
ref="commentForm" 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> <div class="error-alert"></div>
...@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea" ...@@ -301,7 +301,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea"
:disabled="isSubmitting" :disabled="isSubmitting"
placeholder="Write a comment or drag your files here..." placeholder="Write a comment or drag your files here..."
@keydown.up="editCurrentUserLastNote()" @keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"> @keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()">
</textarea> </textarea>
</markdown-field> </markdown-field>
<div class="note-form-actions"> <div class="note-form-actions">
......
...@@ -156,6 +156,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" ...@@ -156,6 +156,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
slot="textarea" slot="textarea"
placeholder="Write a comment or drag your files here..." placeholder="Write a comment or drag your files here..."
@keydown.meta.enter="handleUpdate()" @keydown.meta.enter="handleUpdate()"
@keydown.ctrl.enter="handleUpdate()"
@keydown.up="editMyLastNote()" @keydown.up="editMyLastNote()"
@keydown.esc="cancelHandler(true)"> @keydown.esc="cancelHandler(true)">
</textarea> </textarea>
......
...@@ -19,12 +19,9 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -19,12 +19,9 @@ document.addEventListener('DOMContentLoaded', () => {
return; return;
} }
$(navEl).on('show.bs.dropdown', (e) => { $(navEl).on('shown.bs.dropdown', () => {
const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu');
dropdownEl.one('transitionend', () => {
eventHub.$emit('dropdownOpen'); eventHub.$emit('dropdownOpen');
}); });
});
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
......
<script> <script>
import tooltip from '../directives/tooltip';
/** /**
* Falls back to the code used in `copy_to_clipboard.js` * Falls back to the code used in `copy_to_clipboard.js`
*/ */
export default { export default {
name: 'ClipboardButton', name: 'ClipboardButton',
directives: {
tooltip,
},
props: { props: {
text: { text: {
type: String, type: String,
...@@ -14,6 +18,16 @@ ...@@ -14,6 +18,16 @@
type: String, type: String,
required: true, required: true,
}, },
tooltipPlacement: {
type: String,
required: false,
default: 'top',
},
tooltipContainer: {
type: [String, Boolean],
required: false,
default: false,
},
}, },
}; };
</script> </script>
...@@ -22,8 +36,11 @@ ...@@ -22,8 +36,11 @@
<button <button
type="button" type="button"
class="btn btn-transparent btn-clipboard" class="btn btn-transparent btn-clipboard"
:data-title="title" :title="title"
:data-clipboard-text="text" :data-clipboard-text="text"
v-tooltip
:data-container="tooltipContainer"
:data-placement="tooltipPlacement"
> >
<i <i
aria-hidden="true" aria-hidden="true"
......
...@@ -825,7 +825,6 @@ a.linked-pipeline-mini-item { ...@@ -825,7 +825,6 @@ a.linked-pipeline-mini-item {
// link to the build // link to the build
.mini-pipeline-graph-dropdown-item { .mini-pipeline-graph-dropdown-item {
padding: 3px 7px 4px;
align-items: center; align-items: center;
clear: both; clear: both;
display: flex; display: flex;
......
...@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController ...@@ -29,7 +29,7 @@ class Projects::JobsController < Projects::ApplicationController
:project, :project,
:tags :tags
]) ])
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30).without_count
end end
def cancel_all def cancel_all
......
...@@ -8,7 +8,7 @@ class Route < ActiveRecord::Base ...@@ -8,7 +8,7 @@ class Route < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
validate :ensure_permanent_paths validate :ensure_permanent_paths, if: :path_changed?
after_create :delete_conflicting_redirects after_create :delete_conflicting_redirects
after_update :delete_conflicting_redirects, if: :path_changed? after_update :delete_conflicting_redirects, if: :path_changed?
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls .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') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%li CI variables %li CI variables
%li Any encrypted tokens %li Any encrypted tokens
%p %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 - if project.export_project_path
= link_to 'Download export', download_export_project_path(project), = link_to 'Download export', download_export_project_path(project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default" rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.limit-container-width{ class: container_class } .limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar .avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile') = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile')
%h1.project-title %h1.project-title.qa-project-name
= @project.name = @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) } %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false) = visibility_level_icon(@project.visibility_level, fw: false)
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
.input-group-addon .input-group-addon
= root_url = 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 - else
.input-group-addon.static-namespace .input-group-addon.static-namespace
......
...@@ -22,4 +22,4 @@ ...@@ -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 } = 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 @@ ...@@ -27,7 +27,7 @@
Edit Edit
- if @project.group - 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 Promote
- if @milestone.active? - if @milestone.active?
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
%span %span
= enabled_project_button(project, enabled_protocol) = enabled_project_button(project, enabled_protocol)
- else - 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 %span
= default_clone_protocol.upcase = default_clone_protocol.upcase
= icon('caret-down') = icon('caret-down')
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
= icon('spinner spin', class: 'label-subscribe-button-loading') = icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) - 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 %span.sr-only Promote to Group
= icon('level-up') = icon('level-up')
- if can?(current_user, :admin_label, label) - if can?(current_user, :admin_label, label)
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
\ \
- if @project.group - 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 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" = 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 module ProjectImportOptions
extend ActiveSupport::Concern 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 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 # 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 ...@@ -78,6 +78,7 @@ module Gitlab
# - Any parameter containing `secret` # - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt) # - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url) # - Repo/Project Import URLs (:import_url)
# - Build traces (:trace)
# - Build variables (:variables) # - Build variables (:variables)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key) # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
...@@ -92,6 +93,7 @@ module Gitlab ...@@ -92,6 +93,7 @@ module Gitlab
key key
otp_attempt otp_attempt
sentry_dsn sentry_dsn
trace
variables variables
) )
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
> [Introduced][ce-6373] in GitLab 8.15. > [Introduced][ce-6373] in GitLab 8.15.
### Snippet visibility level ## Snippet visibility level
Snippets in GitLab can be either private, internal, or public. Snippets in GitLab can be either private, internal, or public.
You can set it with the `visibility` field in the snippet. You can set it with the `visibility` field in the snippet.
...@@ -273,4 +273,5 @@ Example response: ...@@ -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 ...@@ -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 ## List SSH keys
Get a list of currently authenticated user's 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. ...@@ -83,6 +83,15 @@ Leverage the power of Docker to run your CI pipelines.
- [CI services (linked Docker containers)](services/README.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/) - 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
- [Review Apps documentation](review_apps/index.md) - [Review Apps documentation](review_apps/index.md)
......
...@@ -64,7 +64,7 @@ up-to-date and install it. ...@@ -64,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems): 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 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). you can [install re2 manually](https://github.com/google/re2/wiki/Install).
......
...@@ -14,6 +14,19 @@ from one server to another is through backup restore. ...@@ -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, GitLab provides a simple command line interface to backup your whole installation,
and is flexible enough to fit your needs. 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 ### Backup timestamp
>**Note:** >**Note:**
......
...@@ -21,6 +21,8 @@ sudo service gitlab stop ...@@ -21,6 +21,8 @@ sudo service gitlab stop
### 2. Backup ### 2. Backup
NOTE: If you installed GitLab from source, make sure `rsync` is installed.
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
......
...@@ -50,15 +50,22 @@ module Banzai ...@@ -50,15 +50,22 @@ module Banzai
end end
def process_link_to_upload_attr(html_attr) def process_link_to_upload_attr(html_attr)
uri_parts = [html_attr.value] path_parts = [html_attr.value]
if group if group
uri_parts.unshift(relative_url_root, 'groups', group.full_path, '-') path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
elsif project elsif project
uri_parts.unshift(relative_url_root, project.full_path) path_parts.unshift(relative_url_root, project.full_path)
end 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 end
def process_link_to_repository_attr(html_attr) def process_link_to_repository_attr(html_attr)
......
...@@ -571,7 +571,21 @@ module Gitlab ...@@ -571,7 +571,21 @@ module Gitlab
end end
def merged_branch_names(branch_names = []) 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 end
# Return an array of Diff objects that represent the diff # Return an array of Diff objects that represent the diff
...@@ -1222,34 +1236,32 @@ module Gitlab ...@@ -1222,34 +1236,32 @@ module Gitlab
end end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:) def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id) gitaly_migrate(:rebase) do |is_enabled|
env = git_env_for_user(user) if is_enabled
gitaly_rebase(user, rebase_id,
if remote_repository.is_a?(RemoteRepository) branch: branch,
env.merge!(remote_repository.fetch_env) branch_sha: branch_sha,
remote_repo_path = GITALY_INTERNAL_URL remote_repository: remote_repository,
remote_branch: remote_branch)
else else
remote_repo_path = remote_repository.path git_rebase(user, rebase_id,
branch: branch,
branch_sha: branch_sha,
remote_repository: remote_repository,
remote_branch: remote_branch)
end 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
end end
def rebase_in_progress?(rebase_id) def rebase_in_progress?(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)) fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
end end
end
end
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:) def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id) squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)
...@@ -1483,14 +1495,7 @@ module Gitlab ...@@ -1483,14 +1495,7 @@ module Gitlab
sort_branches(branches, sort_by) sort_branches(branches, sort_by)
end end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695 def git_merged_branch_names(branch_names, root_sha)
def git_merged_branch_names(branch_names = [])
return [] unless root_ref
root_sha = find_branch(root_ref)&.target
return [] unless root_sha
git_arguments = git_arguments =
%W[branch --merged #{root_sha} %W[branch --merged #{root_sha}
--format=%(refname:short)\ %(objectname)] + branch_names --format=%(refname:short)\ %(objectname)] + branch_names
...@@ -1504,6 +1509,14 @@ module Gitlab ...@@ -1504,6 +1509,14 @@ module Gitlab
end end
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) def process_count_commits_options(options)
if options[:from] || options[:to] if options[:from] || options[:to]
ref = ref =
...@@ -2018,6 +2031,40 @@ module Gitlab ...@@ -2018,6 +2031,40 @@ module Gitlab
tree_id tree_id
end 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:) def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args) run_git(args)
......
...@@ -147,6 +147,34 @@ module Gitlab ...@@ -147,6 +147,34 @@ module Gitlab
start_repository: start_repository) start_repository: start_repository)
end 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 private
def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
......
...@@ -14,12 +14,18 @@ module Gitlab ...@@ -14,12 +14,18 @@ module Gitlab
request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindAllBranchesRequest.new(repository: @gitaly_repo)
response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request) response = GitalyClient.call(@storage, :ref_service, :find_all_branches, request)
response.flat_map do |message| consume_find_all_branches_response(response)
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 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 end
def default_branch_name def default_branch_name
...@@ -62,7 +68,7 @@ module Gitlab ...@@ -62,7 +68,7 @@ module Gitlab
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo) request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by request.sort_by = sort_by_param(sort_by) if sort_by
response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request) response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request)
consume_branches_response(response) consume_find_local_branches_response(response)
end end
def tags def tags
...@@ -151,7 +157,7 @@ module Gitlab ...@@ -151,7 +157,7 @@ module Gitlab
enum_value enum_value
end end
def consume_branches_response(response) def consume_find_local_branches_response(response)
response.flat_map do |message| response.flat_map do |message|
message.branches.map do |gitaly_branch| message.branches.map do |gitaly_branch|
Gitlab::Git::Branch.new( Gitlab::Git::Branch.new(
...@@ -164,6 +170,15 @@ module Gitlab ...@@ -164,6 +170,15 @@ module Gitlab
end end
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) def consume_tags_response(response)
response.flat_map do |message| response.flat_map do |message|
message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) } message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) }
......
...@@ -100,6 +100,23 @@ module Gitlab ...@@ -100,6 +100,23 @@ module Gitlab
) )
end 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) def fetch_source_branch(source_repository, source_branch, local_ref)
request = Gitaly::FetchSourceBranchRequest.new( request = Gitaly::FetchSourceBranchRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
......
...@@ -98,6 +98,9 @@ module Gitlab ...@@ -98,6 +98,9 @@ module Gitlab
) )
end end
# If present DisableCache must be a Boolean. Otherwise workhorse ignores it.
params['DisableCache'] = true if git_archive_cache_disabled?
[ [
SEND_DATA_HEADER, SEND_DATA_HEADER,
"git-archive:#{encode(params)}" "git-archive:#{encode(params)}"
...@@ -248,6 +251,10 @@ module Gitlab ...@@ -248,6 +251,10 @@ module Gitlab
right_commit_id: diff_refs.head_sha right_commit_id: diff_refs.head_sha
} }
end end
def git_archive_cache_disabled?
ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled)
end
end end
end end
end end
...@@ -17,6 +17,17 @@ against any existing instance. ...@@ -17,6 +17,17 @@ against any existing instance.
1. Along with GitLab Docker Images we also build and publish GitLab QA images. 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. 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? ## How can I use it?
You can use GitLab QA to exercise tests on any live instance! For example, the You can use GitLab QA to exercise tests on any live instance! For example, the
......
...@@ -2,16 +2,18 @@ module QA ...@@ -2,16 +2,18 @@ module QA
module Page module Page
module Project module Project
class New < Page::Base class New < Page::Base
## view 'app/views/projects/_new_project_fields.html.haml' do
# TODO, define all selectors required by this page object element :project_namespace_select
# element :project_namespace_field, 'select :namespace_id'
# See gitlab-org/gitlab-qa#154 element :project_path, 'text_field :path'
# element :project_description, 'text_area :description'
view 'app/views/projects/new.html.haml' element :project_create_button, "submit 'Create project'"
end
def choose_test_namespace def choose_test_namespace
find('#s2id_project_namespace_id').click click_element :project_namespace_select
find('.select2-result-label', text: Runtime::Namespace.name).click
first('li', text: Runtime::Namespace.path).click
end end
def choose_name(name) def choose_name(name)
......
...@@ -2,15 +2,21 @@ module QA ...@@ -2,15 +2,21 @@ module QA
module Page module Page
module Project module Project
class Show < Page::Base class Show < Page::Base
## view 'app/views/shared/_clone_panel.html.haml' do
# TODO, define all selectors required by this page object element :clone_dropdown
# element :clone_options_dropdown, '.clone-options-dropdown'
# See gitlab-org/gitlab-qa#154 end
#
view 'app/views/projects/show.html.haml' 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 def choose_repository_clone_http
find('#clone-dropdown').click click_element :clone_dropdown
page.within('.clone-options-dropdown') do page.within('.clone-options-dropdown') do
click_link('HTTP') click_link('HTTP')
...@@ -22,7 +28,7 @@ module QA ...@@ -22,7 +28,7 @@ module QA
end end
def project_name def project_name
find('.project-title').text find('.qa-project-name').text
end end
def wait_for_push def wait_for_push
......
...@@ -11,6 +11,10 @@ module QA ...@@ -11,6 +11,10 @@ module QA
'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S') 'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S')
end end
def path
"#{sandbox_name}/#{name}"
end
def sandbox_name def sandbox_name
'gitlab-qa-sandbox' 'gitlab-qa-sandbox'
end 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', () => { ...@@ -31,6 +31,7 @@ describe('Job details header', () => {
email: 'foo@bar.com', email: 'foo@bar.com',
avatar_url: 'link', avatar_url: 'link',
}, },
started: '2018-01-08T09:48:27.319Z',
new_issue_path: 'path', new_issue_path: 'path',
}, },
isLoading: false, isLoading: false,
...@@ -43,6 +44,11 @@ describe('Job details header', () => { ...@@ -43,6 +44,11 @@ describe('Job details header', () => {
vm.$destroy(); vm.$destroy();
}); });
describe('triggered job', () => {
beforeEach(() => {
vm = mountComponent(HeaderComponent, props);
});
it('should render provided job information', () => { it('should render provided job information', () => {
expect( expect(
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(), vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(),
...@@ -54,4 +60,16 @@ describe('Job details header', () => { ...@@ -54,4 +60,16 @@ describe('Job details header', () => {
vm.$el.querySelector('.js-new-issue').getAttribute('href'), vm.$el.querySelector('.js-new-issue').getAttribute('href'),
).toEqual(props.job.new_issue_path); ).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', () => { ...@@ -139,13 +139,21 @@ describe('issue_comment_form component', () => {
}); });
describe('event enter', () => { 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(); spyOn(vm, 'handleSave').and.callThrough();
vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo'; vm.$el.querySelector('.js-main-target-form textarea').value = 'Foo';
vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true)); vm.$el.querySelector('.js-main-target-form textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleSave).toHaveBeenCalled(); 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', () => { ...@@ -69,11 +69,18 @@ describe('issue_note_form component', () => {
}); });
describe('enter', () => { describe('enter', () => {
it('should submit note', () => { it('should save note when cmd+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough(); spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo'; vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true)); 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(); 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 ...@@ -8,7 +8,8 @@ describe Banzai::Filter::RelativeLinkFilter do
group: group, group: group,
project_wiki: project_wiki, project_wiki: project_wiki,
ref: ref, ref: ref,
requested_path: requested_path requested_path: requested_path,
only_path: only_path
}) })
described_class.call(doc, contexts) described_class.call(doc, contexts)
...@@ -37,6 +38,7 @@ describe Banzai::Filter::RelativeLinkFilter do ...@@ -37,6 +38,7 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { project.commit(ref) } let(:commit) { project.commit(ref) }
let(:project_wiki) { nil } let(:project_wiki) { nil }
let(:requested_path) { '/' } let(:requested_path) { '/' }
let(:only_path) { true }
shared_examples :preserve_unchanged do shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do it 'does not modify any relative URL in anchor' do
...@@ -240,26 +242,35 @@ describe Banzai::Filter::RelativeLinkFilter do ...@@ -240,26 +242,35 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:commit) { nil } let(:commit) { nil }
let(:ref) { nil } let(:ref) { nil }
let(:requested_path) { 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 '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 it 'rebuilds relative URL for a link' do
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) doc = filter(link(upload_path))
expect(doc.at_css('a')['href']) expect(doc.at_css('a')['href']).to eq(relative_path)
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(nested(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))) doc = filter(nested(link(upload_path)))
expect(doc.at_css('a')['href']) expect(doc.at_css('a')['href']).to eq(relative_path)
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
end end
it 'rebuilds relative URL for an image' do it 'rebuilds relative URL for an image' do
doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')) doc = filter(image(upload_path))
expect(doc.at_css('img')['src']) expect(doc.at_css('img')['src']).to eq(relative_path)
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
doc = filter(nested(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))) doc = filter(nested(image(upload_path)))
expect(doc.at_css('img')['src']) expect(doc.at_css('img')['src']).to eq(relative_path)
.to eq "/#{project.full_path}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
end end
it 'does not modify absolute URL' do it 'does not modify absolute URL' do
...@@ -288,6 +299,17 @@ describe Banzai::Filter::RelativeLinkFilter do ...@@ -288,6 +299,17 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:project) { nil } let(:project) { nil }
let(:relative_path) { "/groups/#{group.full_path}/-/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg" } 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 it 'rewrites the link correctly' do
doc = filter(upload_link) doc = filter(upload_link)
......
...@@ -1283,6 +1283,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1283,6 +1283,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
describe '#merged_branch_names' do describe '#merged_branch_names' do
shared_examples 'finding merged branch names' do
context 'when branch names are passed' do context 'when branch names are passed' do
it 'only returns the names we are asking' do it 'only returns the names we are asking' do
names = repository.merged_branch_names(%w[merge-test]) names = repository.merged_branch_names(%w[merge-test])
...@@ -1327,6 +1328,15 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1327,6 +1328,15 @@ describe Gitlab::Git::Repository, seed_helper: true do
end 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 describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") } let(:master_file_paths) { repository.ls_files("master") }
let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") } let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") }
......
...@@ -26,11 +26,16 @@ describe Gitlab::Workhorse do ...@@ -26,11 +26,16 @@ describe Gitlab::Workhorse do
'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
) )
end end
let(:cache_disabled) { false }
subject do subject do
described_class.send_git_archive(repository, ref: ref, format: format) described_class.send_git_archive(repository, ref: ref, format: format)
end 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 context 'when Gitaly workhorse_archive feature is enabled' do
it 'sets the header correctly' do it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject) key, command, params = decode_workhorse_header(subject)
...@@ -39,6 +44,15 @@ describe Gitlab::Workhorse do ...@@ -39,6 +44,15 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-archive') expect(command).to eq('git-archive')
expect(params).to include(gitaly_params) expect(params).to include(gitaly_params)
end 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 end
context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do context 'when Gitaly workhorse_archive feature is disabled', :skip_gitaly_mock do
......
...@@ -2353,38 +2353,44 @@ describe MergeRequest do ...@@ -2353,38 +2353,44 @@ describe MergeRequest do
end end
describe '#rebase_in_progress?' do describe '#rebase_in_progress?' do
# Create merge request and project before we stub file calls 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}") }
before do before do
subject system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
end end
it 'returns true when there is a current rebase directory' do 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)
expect(subject.rebase_in_progress?).to be_truthy expect(subject.rebase_in_progress?).to be_truthy
end end
it 'returns false when there is no rebase directory' do it 'returns false when there is no rebase directory' do
allow(File).to receive(:exist?).and_return(false) FileUtils.rm_rf(rebase_path)
expect(subject.rebase_in_progress?).to be_falsey expect(subject.rebase_in_progress?).to be_falsey
end end
it 'returns false when the rebase directory has expired' do it 'returns false when the rebase directory has expired' do
allow(File).to receive(:exist?).and_return(true) time = 20.minutes.ago.to_time
allow(File).to receive(:mtime).and_return(20.minutes.ago) File.utime(time, time, rebase_path)
expect(subject.rebase_in_progress?).to be_falsey expect(subject.rebase_in_progress?).to be_falsey
end end
it 'returns false when the source project has been removed' do it 'returns false when the source project has been removed' do
allow(subject).to receive(:source_project).and_return(nil) 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)
expect(File).not_to have_received(:exist?)
expect(subject.rebase_in_progress?).to be_falsey expect(subject.rebase_in_progress?).to be_falsey
end end
end end
context 'when Gitaly rebase_in_progress is enabled' do
it_behaves_like 'checking whether a rebase is in progress'
end
context 'when Gitaly rebase_in_progress is enabled', :disable_gitaly do
it_behaves_like 'checking whether a rebase is in progress'
end
end
end end
...@@ -16,6 +16,66 @@ describe Route do ...@@ -16,6 +16,66 @@ describe Route do
it { is_expected.to validate_presence_of(:source) } it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).case_insensitive } 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 end
describe 'callbacks' do describe 'callbacks' do
......
...@@ -36,7 +36,7 @@ describe MergeRequests::RebaseService do ...@@ -36,7 +36,7 @@ describe MergeRequests::RebaseService do
end end
end end
context 'when unexpected error occurs' do context 'when unexpected error occurs', :disable_gitaly do
before do before do
allow(repository).to receive(:run_git!).and_raise('Something went wrong') allow(repository).to receive(:run_git!).and_raise('Something went wrong')
end end
...@@ -53,7 +53,7 @@ describe MergeRequests::RebaseService do ...@@ -53,7 +53,7 @@ describe MergeRequests::RebaseService do
end end
end end
context 'with git command failure' do context 'with git command failure', :disable_gitaly do
before do before do
allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong') allow(repository).to receive(:run_git!).and_raise(Gitlab::Git::Repository::GitError, 'Something went wrong')
end end
...@@ -71,6 +71,7 @@ describe MergeRequests::RebaseService do ...@@ -71,6 +71,7 @@ describe MergeRequests::RebaseService do
end end
context 'valid params' do context 'valid params' do
shared_examples 'successful rebase' do
before do before do
service.execute(merge_request) service.execute(merge_request)
end end
...@@ -94,8 +95,17 @@ describe MergeRequests::RebaseService do ...@@ -94,8 +95,17 @@ describe MergeRequests::RebaseService do
expect(head_commit.committer_email).to eq(user.email) expect(head_commit.committer_email).to eq(user.email)
expect(head_commit.committer_name).to eq(user.name) expect(head_commit.committer_name).to eq(user.name)
end end
end
context 'when Gitaly rebase feature is enabled' do
it_behaves_like 'successful rebase'
end
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 it 'sets GL_REPOSITORY env variable when calling git commands' do
expect(repository).to receive(:popen).exactly(3) expect(repository).to receive(:popen).exactly(3)
.with(anything, anything, hash_including('GL_REPOSITORY')) .with(anything, anything, hash_including('GL_REPOSITORY'))
...@@ -106,6 +116,7 @@ describe MergeRequests::RebaseService do ...@@ -106,6 +116,7 @@ describe MergeRequests::RebaseService do
end end
context 'fork' do context 'fork' do
shared_examples 'successful fork rebase' do
let(:forked_project) do let(:forked_project) do
fork_project(project, user, repository: true) fork_project(project, user, repository: true)
end end
...@@ -129,6 +140,15 @@ describe MergeRequests::RebaseService do ...@@ -129,6 +140,15 @@ describe MergeRequests::RebaseService do
expect(parent_sha).to eq(target_branch_sha) expect(parent_sha).to eq(target_branch_sha)
end end
end end
context 'when Gitaly rebase feature is enabled' do
it_behaves_like 'successful fork rebase'
end
context 'when Gitaly rebase feature is disabled', :disable_gitaly do
it_behaves_like 'successful fork rebase'
end
end
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment