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,11 +19,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -19,11 +19,8 @@ document.addEventListener('DOMContentLoaded', () => {
return; return;
} }
$(navEl).on('show.bs.dropdown', (e) => { $(navEl).on('shown.bs.dropdown', () => {
const dropdownEl = $(e.currentTarget).find('.projects-dropdown-menu'); eventHub.$emit('dropdownOpen');
dropdownEl.one('transitionend', () => {
eventHub.$emit('dropdownOpen');
});
}); });
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
......
<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).
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
An application data backup creates an archive file that contains the database, An application data backup creates an archive file that contains the database,
all repositories and all attachments. all repositories and all attachments.
You can only restore a backup to **exactly the same version and type (CE/EE)** 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 of GitLab on which it was created. The best way to migrate your repositories
from one server to another is through backup restore. from one server to another is through backup restore.
## Backup ## Backup
...@@ -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:**
...@@ -431,7 +444,7 @@ The [restore prerequisites section](#restore-prerequisites) includes crucial ...@@ -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 information. Make sure to read and test the whole restore process at least once
before attempting to perform it in a production environment. 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. GitLab that you created it on, for example CE 9.1.0.
### Restore prerequisites ### Restore prerequisites
...@@ -511,7 +524,7 @@ sudo service gitlab restart ...@@ -511,7 +524,7 @@ sudo service gitlab restart
This procedure assumes that: 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. Omnibus with which the backup was created.
- You have run `sudo gitlab-ctl reconfigure` at least once. - You have run `sudo gitlab-ctl reconfigure` at least once.
- GitLab is running. If not, start it using `sudo gitlab-ctl start`. - GitLab is running. If not, start it using `sudo gitlab-ctl start`.
......
...@@ -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,33 +1236,31 @@ module Gitlab ...@@ -1222,33 +1236,31 @@ 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,
else remote_branch: remote_branch)
remote_repo_path = remote_repository.path else
end git_rebase(user, rebase_id,
branch: branch,
with_worktree(rebase_path, branch, env: env) do branch_sha: branch_sha,
run_git!( remote_repository: remote_repository,
%W(pull --rebase #{remote_repo_path} #{remote_branch}), remote_branch: remote_branch)
chdir: rebase_path, env: env end
)
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)
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 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:)
...@@ -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| end
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target)
Gitlab::Git::Branch.new(@repository, branch.name, branch.target.id, target_commit) def merged_branches(branch_names = [])
end request = Gitaly::FindAllBranchesRequest.new(
end 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,15 +44,32 @@ describe('Job details header', () => { ...@@ -43,15 +44,32 @@ describe('Job details header', () => {
vm.$destroy(); vm.$destroy();
}); });
it('should render provided job information', () => { describe('triggered job', () => {
expect( beforeEach(() => {
vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(), vm = mountComponent(HeaderComponent, props);
).toEqual('failed Job #123 triggered 3 weeks ago by Foo'); });
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', () => { describe('created job', () => {
expect( it('should render created key', () => {
vm.$el.querySelector('.js-new-issue').getAttribute('href'), props.job.started = false;
).toEqual(props.job.new_issue_path); 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,48 +1283,58 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1283,48 +1283,58 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
describe '#merged_branch_names' do describe '#merged_branch_names' do
context 'when branch names are passed' do shared_examples 'finding merged branch names' do
it 'only returns the names we are asking' do context 'when branch names are passed' do
names = repository.merged_branch_names(%w[merge-test]) it 'only returns the names we are asking' do
names = repository.merged_branch_names(%w[merge-test])
expect(names).to contain_exactly('merge-test') expect(names).to contain_exactly('merge-test')
end end
it 'does not return unmerged branch names' do it 'does not return unmerged branch names' do
names = repository.merged_branch_names(%w[feature]) names = repository.merged_branch_names(%w[feature])
expect(names).to be_empty expect(names).to be_empty
end
end end
end
context 'when no root ref is available' do context 'when no root ref is available' do
it 'returns empty list' do it 'returns empty list' do
project = create(:project, :empty_repo) 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
end
context 'when no branch names are specified' do context 'when no branch names are specified' do
before do before do
repository.create_branch('identical', 'master') repository.create_branch('identical', 'master')
end end
after do after do
ensure_seeds ensure_seeds
end end
it 'returns all merged branch names except for identical one' do it 'returns all merged branch names except for identical one' do
names = repository.merged_branch_names names = repository.merged_branch_names
expect(names).to include('merge-test') expect(names).to include('merge-test')
expect(names).to include('fix-mode') expect(names).to include('fix-mode')
expect(names).not_to include('feature') expect(names).not_to include('feature')
expect(names).not_to include('identical') expect(names).not_to include('identical')
end
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 end
describe "#ls_files" do describe "#ls_files" do
......
...@@ -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
before do let(:repo_path) { subject.source_project.repository.path }
subject let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
end
it 'returns true when there is a current rebase directory' do before do
allow(File).to receive(:exist?).and_return(true) system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
allow(File).to receive(:mtime).and_return(Time.now) end
expect(subject.rebase_in_progress?).to be_truthy it 'returns true when there is a current rebase directory' do
end expect(subject.rebase_in_progress?).to be_truthy
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
time = 20.minutes.ago.to_time
File.utime(time, time, rebase_path)
it 'returns false when the rebase directory has expired' do expect(subject.rebase_in_progress?).to be_falsey
allow(File).to receive(:exist?).and_return(true) end
allow(File).to receive(:mtime).and_return(20.minutes.ago)
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 end
it 'returns false when the source project has been removed' do context 'when Gitaly rebase_in_progress is enabled' do
allow(subject).to receive(:source_project).and_return(nil) it_behaves_like 'checking whether a rebase is in progress'
allow(File).to receive(:exist?).and_return(true) end
allow(File).to receive(:mtime).and_return(Time.now)
expect(File).not_to have_received(:exist?) context 'when Gitaly rebase_in_progress is enabled', :disable_gitaly do
expect(subject.rebase_in_progress?).to be_falsey it_behaves_like 'checking whether a rebase is in progress'
end end
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,31 +71,41 @@ describe MergeRequests::RebaseService do ...@@ -71,31 +71,41 @@ describe MergeRequests::RebaseService do
end end
context 'valid params' do context 'valid params' do
before do shared_examples 'successful rebase' do
service.execute(merge_request) before do
end service.execute(merge_request)
end
it 'rebases source branch' do it 'rebases source branch' do
parent_sha = merge_request.source_project.repository.commit(merge_request.source_branch).parents.first.sha 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 target_branch_sha = merge_request.target_project.repository.commit(merge_request.target_branch).sha
expect(parent_sha).to eq(target_branch_sha) expect(parent_sha).to eq(target_branch_sha)
end end
it 'records the new SHA on the merge request' do it 'records the new SHA on the merge request' do
head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha head_sha = merge_request.source_project.repository.commit(merge_request.source_branch).sha
expect(merge_request.reload.rebase_commit_sha).to eq(head_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 end
it 'logs correct author and commiter' do context 'when Gitaly rebase feature is enabled' do
head_commit = merge_request.source_project.repository.commit(merge_request.source_branch) it_behaves_like 'successful rebase'
end
expect(head_commit.author_email).to eq('dmitriy.zaporozhets@gmail.com') context 'when Gitaly rebase feature is disabled', :disable_gitaly do
expect(head_commit.author_name).to eq('Dmitriy Zaporozhets') it_behaves_like 'successful rebase'
expect(head_commit.committer_email).to eq(user.email)
expect(head_commit.committer_name).to eq(user.name)
end 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,27 +116,37 @@ describe MergeRequests::RebaseService do ...@@ -106,27 +116,37 @@ describe MergeRequests::RebaseService do
end end
context 'fork' do context 'fork' do
let(:forked_project) do shared_examples 'successful fork rebase' do
fork_project(project, user, repository: true) 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 end
let(:merge_request_from_fork) do context 'when Gitaly rebase feature is enabled' do
forked_project.repository.create_file( it_behaves_like 'successful fork rebase'
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 end
it 'rebases source branch' do context 'when Gitaly rebase feature is disabled', :disable_gitaly do
parent_sha = forked_project.repository.commit(merge_request_from_fork.source_branch).parents.first.sha it_behaves_like 'successful fork rebase'
target_branch_sha = project.repository.commit(merge_request_from_fork.target_branch).sha
expect(parent_sha).to eq(target_branch_sha)
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