diff --git a/.flayignore b/.flayignore
index b63ce4c4df0..acac0ce14c9 100644
--- a/.flayignore
+++ b/.flayignore
@@ -5,3 +5,4 @@ app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb
+lib/gitlab/gitaly_client/operation_service.rb
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a31817e2b8d..15d9117976a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -195,6 +195,10 @@ entry.
- Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi)
- [BUGIFX] Improves subgroup creation permissions. !13418
+## 9.5.7 (2017-10-03)
+
+- Fix gitlab rake:import:repos task.
+
## 9.5.6 (2017-09-29)
- [FIXED] Fix MR ready to merge buttons/controls at mobile breakpoint. !14242
diff --git a/Gemfile.lock b/Gemfile.lock
index a0ad2716c01..4622fd141ba 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -910,7 +910,7 @@ GEM
json (>= 1.8.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.2)
+ unf_ext (0.0.7.4)
unicode-display_width (1.3.0)
unicorn (5.1.0)
kgio (~> 2.6)
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
index af718e894cf..ce05b3eabec 100644
--- a/app/assets/javascripts/locale/index.js
+++ b/app/assets/javascripts/locale/index.js
@@ -1,30 +1,12 @@
import Jed from 'jed';
-
import sprintf from './sprintf';
-/**
- This is required to require all the translation folders in the current directory
- this saves us having to do this manually & keep up to date with new languages
-**/
-function requireAll(requireContext) { return requireContext.keys().map(requireContext); }
-
-const allLocales = requireAll(require.context('./', true, /^(?!.*(?:index.js$)).*\.js$/));
-const locales = allLocales.reduce((d, obj) => {
- const data = d;
- const localeKey = Object.keys(obj)[0];
-
- data[localeKey] = obj[localeKey];
-
- return data;
-}, {});
-
const langAttribute = document.querySelector('html').getAttribute('lang');
const lang = (langAttribute || 'en').replace(/-/g, '_');
-const locale = new Jed(locales[lang]);
+const locale = new Jed(window.translations || {});
/**
Translates `text`
-
@param text The text to be translated
@returns {String} The translated text
**/
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 0db2abe507d..af0658eb668 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -127,6 +127,21 @@ import IssuablesHelper from './helpers/issuables_helper';
$el.text(gl.text.addDelimiter(count));
};
+ MergeRequest.prototype.hideCloseButton = function() {
+ const el = document.querySelector('.merge-request .issuable-actions');
+ const closeDropdownItem = el.querySelector('li.close-item');
+ if (closeDropdownItem) {
+ closeDropdownItem.classList.add('hidden');
+ // Selects the next dropdown item
+ el.querySelector('li.report-item').click();
+ } else {
+ // No dropdown just hide the Close button
+ el.querySelector('.btn-close').classList.add('hidden');
+ }
+ // Dropdown for mobile screen
+ el.querySelector('li.js-close-item').classList.add('hidden');
+ };
+
return MergeRequest;
})();
}).call(window);
diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue
index b8a16356576..b4067d229aa 100644
--- a/app/assets/javascripts/notebook/cells/code.vue
+++ b/app/assets/javascripts/notebook/cells/code.vue
@@ -1,18 +1,3 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ ```
+
+1. Properties in a Vue Component:
1. `name`
1. `props`
1. `mixins`
@@ -490,6 +508,7 @@ On those a default key should not be provided.
1. `beforeDestroy`
1. `destroyed`
+
#### Vue and Bootstrap
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 2607353782a..277e0cd5f00 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -428,7 +428,7 @@ is a good example of this pattern.
## Style guide
-Please refer to the Vue section of our [style guide](style_guide_js.md#vuejs)
+Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js)
for best practices while writing your Vue components and templates.
## Testing Vue Components
diff --git a/doc/development/testing.md b/doc/development/testing.md
index d856b003353..4d5b90de6fc 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -302,7 +302,7 @@ range of inputs, might look like this:
```ruby
describe "#==" do
- using Rspec::Parameterized::TableSyntax
+ using RSpec::Parameterized::TableSyntax
let(:project1) { create(:project) }
let(:project2) { create(:project) }
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index fac91935a45..597c98fbf6b 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -11,6 +11,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Security Webcast with Yubico](https://about.gitlab.com/2016/08/31/gitlab-and-yubico-security-webcast/)
- **Integrations:**
- [GitLab as OAuth2 authentication service provider](../../integration/oauth_provider.md#introduction-to-oauth)
+ - [GitLab as OpenID Connect identity provider](../../integration/openid_connect_provider.md)
## GitLab administrators
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 5c615daf464..2c4dfcff4a6 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -17,25 +17,25 @@ have its own space to store its Docker images.
You can read more about Docker Registry at https://docs.docker.com/registry/introduction/.
----
-
## Enable the Container Registry for your project
+NOTE: **Note:**
+If you cannot find the Container Registry entry under your project's settings,
+that means that it is not enabled in your GitLab instance. Ask your administrator
+to enable it.
+
1. First, ask your system administrator to enable GitLab Container Registry
following the [administration documentation](../../administration/container_registry.md).
If you are using GitLab.com, this is enabled by default so you can start using
the Registry immediately.
-
-1. Go to your project's settings and enable the **Container Registry** feature
- on your project. For new projects this might be enabled by default. For
- existing projects (prior GitLab 8.8), you will have to explicitly enable it.
-
- ![Enable Container Registry](img/container_registry_enable.png)
-
+1. Go to your [project's General settings](settings/index.md#sharing-and-permissions)
+ and enable the **Container Registry** feature on your project. For new
+ projects this might be enabled by default. For existing projects
+ (prior GitLab 8.8), you will have to explicitly enable it.
1. Hit **Save changes** for the changes to take effect. You should now be able
- to see the **Registry** link in the project menu.
+ to see the **Registry** link in the sidebar.
- ![Container Registry tab](img/container_registry_tab.png)
+![Container Registry](img/container_registry.png)
## Build and push images
diff --git a/doc/user/project/img/container_registry.png b/doc/user/project/img/container_registry.png
new file mode 100644
index 00000000000..abbaf838538
Binary files /dev/null and b/doc/user/project/img/container_registry.png differ
diff --git a/doc/user/project/img/container_registry_enable.png b/doc/user/project/img/container_registry_enable.png
deleted file mode 100644
index d067a8be1ca..00000000000
Binary files a/doc/user/project/img/container_registry_enable.png and /dev/null differ
diff --git a/doc/user/project/img/container_registry_tab.png b/doc/user/project/img/container_registry_tab.png
deleted file mode 100644
index a85237271d9..00000000000
Binary files a/doc/user/project/img/container_registry_tab.png and /dev/null differ
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index cf7f519f783..5f6dc9e4e8b 100644
Binary files a/doc/user/project/img/issue_board.png and b/doc/user/project/img/issue_board.png differ
diff --git a/doc/user/project/img/issue_board_move_issue_card_list.png b/doc/user/project/img/issue_board_move_issue_card_list.png
index c6b17ada40e..3666dbb87ab 100644
Binary files a/doc/user/project/img/issue_board_move_issue_card_list.png and b/doc/user/project/img/issue_board_move_issue_card_list.png differ
diff --git a/doc/user/project/img/labels_assign_label_in_new_issue.png b/doc/user/project/img/labels_assign_label_in_new_issue.png
deleted file mode 100644
index badfbed0bbe..00000000000
Binary files a/doc/user/project/img/labels_assign_label_in_new_issue.png and /dev/null differ
diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png
index 474953d565b..7934e3bfb5e 100644
Binary files a/doc/user/project/img/labels_default.png and b/doc/user/project/img/labels_default.png differ
diff --git a/doc/user/project/img/labels_filter.png b/doc/user/project/img/labels_filter.png
index 3aca77f0070..6a1ebfc2ecb 100644
Binary files a/doc/user/project/img/labels_filter.png and b/doc/user/project/img/labels_filter.png differ
diff --git a/doc/user/project/img/labels_filter_by_priority.png b/doc/user/project/img/labels_filter_by_priority.png
index 5609a1f6d7f..419e555e709 100644
Binary files a/doc/user/project/img/labels_filter_by_priority.png and b/doc/user/project/img/labels_filter_by_priority.png differ
diff --git a/doc/user/project/img/labels_new_label.png b/doc/user/project/img/labels_new_label.png
index b44b4bd296d..e26425d0188 100644
Binary files a/doc/user/project/img/labels_new_label.png and b/doc/user/project/img/labels_new_label.png differ
diff --git a/doc/user/project/img/labels_prioritize.png b/doc/user/project/img/labels_prioritize.png
index 3e888f36364..d602a3c90ec 100644
Binary files a/doc/user/project/img/labels_prioritize.png and b/doc/user/project/img/labels_prioritize.png differ
diff --git a/doc/user/project/img/project_repository_settings.png b/doc/user/project/img/project_repository_settings.png
index 1aa7efc36f1..aa4d4452c87 100644
Binary files a/doc/user/project/img/project_repository_settings.png and b/doc/user/project/img/project_repository_settings.png differ
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index e2cc67726e0..96a5a23ee13 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -12,6 +12,8 @@ Other interesting links:
- [GitLab Issue Board landing page on about.gitlab.com][landing]
- [YouTube video introduction to Issue Boards][youtube]
+![GitLab Issue Board](img/issue_board.png)
+
## Overview
The Issue Board builds on GitLab's existing
@@ -89,10 +91,6 @@ two defaults:
- **Backlog** (default): shows all open issues that does not belong to one of lists. Always appears on the very left.
- **Closed** (default): shows all closed issues. Always appears on the very right.
-![GitLab Issue Board](img/issue_board.png)
-
----
-
In short, here's a list of actions you can take in an Issue Board:
- [Create a new list](#creating-a-new-list).
diff --git a/doc/user/project/issues/img/button_close_issue.png b/doc/user/project/issues/img/button_close_issue.png
index 8fb2e23f58a..05d257ce9bf 100644
Binary files a/doc/user/project/issues/img/button_close_issue.png and b/doc/user/project/issues/img/button_close_issue.png differ
diff --git a/doc/user/project/issues/img/group_issues_list_view.png b/doc/user/project/issues/img/group_issues_list_view.png
index 5d20e8cbc89..bba964076d0 100644
Binary files a/doc/user/project/issues/img/group_issues_list_view.png and b/doc/user/project/issues/img/group_issues_list_view.png differ
diff --git a/doc/user/project/issues/img/issue_board.png b/doc/user/project/issues/img/issue_board.png
index 1759b28a9ef..87b1016cc76 100644
Binary files a/doc/user/project/issues/img/issue_board.png and b/doc/user/project/issues/img/issue_board.png differ
diff --git a/doc/user/project/issues/img/issue_template.png b/doc/user/project/issues/img/issue_template.png
index c63229a4af2..0e4c8df897b 100644
Binary files a/doc/user/project/issues/img/issue_template.png and b/doc/user/project/issues/img/issue_template.png differ
diff --git a/doc/user/project/issues/img/issues_main_view.png b/doc/user/project/issues/img/issues_main_view.png
index 4faa42e40ee..a929916c682 100644
Binary files a/doc/user/project/issues/img/issues_main_view.png and b/doc/user/project/issues/img/issues_main_view.png differ
diff --git a/doc/user/project/issues/img/issues_main_view_numbered.jpg b/doc/user/project/issues/img/issues_main_view_numbered.jpg
index 4b5d7fba459..b4b68476d24 100644
Binary files a/doc/user/project/issues/img/issues_main_view_numbered.jpg and b/doc/user/project/issues/img/issues_main_view_numbered.jpg differ
diff --git a/doc/user/project/issues/img/new_issue.png b/doc/user/project/issues/img/new_issue.png
index e72ac49d6b9..07d65a93070 100644
Binary files a/doc/user/project/issues/img/new_issue.png and b/doc/user/project/issues/img/new_issue.png differ
diff --git a/doc/user/project/issues/img/new_issue_from_issue_board.png b/doc/user/project/issues/img/new_issue_from_issue_board.png
index 9c2b3ff50fa..da892eff0a6 100644
Binary files a/doc/user/project/issues/img/new_issue_from_issue_board.png and b/doc/user/project/issues/img/new_issue_from_issue_board.png differ
diff --git a/doc/user/project/issues/img/new_issue_from_open_issue.png b/doc/user/project/issues/img/new_issue_from_open_issue.png
index 2aed5372830..c6f3f0617ab 100644
Binary files a/doc/user/project/issues/img/new_issue_from_open_issue.png and b/doc/user/project/issues/img/new_issue_from_open_issue.png differ
diff --git a/doc/user/project/issues/img/new_issue_from_projects_dashboard.png b/doc/user/project/issues/img/new_issue_from_projects_dashboard.png
index cddf36b7457..4b9535f6b15 100644
Binary files a/doc/user/project/issues/img/new_issue_from_projects_dashboard.png and b/doc/user/project/issues/img/new_issue_from_projects_dashboard.png differ
diff --git a/doc/user/project/issues/img/new_issue_from_tracker_list.png b/doc/user/project/issues/img/new_issue_from_tracker_list.png
index 7e5413f0b7d..66793cb44fa 100644
Binary files a/doc/user/project/issues/img/new_issue_from_tracker_list.png and b/doc/user/project/issues/img/new_issue_from_tracker_list.png differ
diff --git a/doc/user/project/issues/img/project_issues_list_view.png b/doc/user/project/issues/img/project_issues_list_view.png
index 2fcc9e8d9da..584a81aab8a 100644
Binary files a/doc/user/project/issues/img/project_issues_list_view.png and b/doc/user/project/issues/img/project_issues_list_view.png differ
diff --git a/doc/user/project/issues/img/sidebar_move_issue.png b/doc/user/project/issues/img/sidebar_move_issue.png
index 111f7861364..1e688cec894 100644
Binary files a/doc/user/project/issues/img/sidebar_move_issue.png and b/doc/user/project/issues/img/sidebar_move_issue.png differ
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 8ec7adad172..21a2e1213ec 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -20,8 +20,6 @@ Head over a single project and navigate to **Issues > Labels**.
The first time you visit this page, you'll notice that there are no labels
created yet.
-![Generate new labels](img/labels_generate.png)
-
Creating a new label from scratch is as easy as pressing the **New label**
button. From there on you can choose the name, give it an optional description,
a color and you are set.
@@ -32,21 +30,23 @@ When you are ready press the **Create label** button to create the new label.
---
-## Default Labels
+## Default labels
-It's possible to populate the labels for your project from a set of predefined labels.
-
-### Generate GitLab's predefined label set
-
-![Generate new labels](img/labels_generate.png)
+The very first time you visit the labels area, it's gonna be empty. In that
+case, it's possible to populate the labels for your project from a set of
+predefined labels.
Click the link to 'Generate a default set of labels' and GitLab will
-generate a set of predefined labels for you. There are 8 default generated labels
-in total and you can see them in the screenshot below.
+generate them for you. There are 8 default generated labels in total:
-![Default generated labels](img/labels_default.png)
-
----
+- bug
+- confirmed
+- critical
+- discussion
+- documentation
+- enhancement
+- suggestion
+- support
## Labels Overview
@@ -102,30 +102,25 @@ If you work on a large or popular project, try subscribing only to the labels
that are relevant to you. You’ll notice it’ll be much easier to focus on what’s
important.
-## Create a new label right from the issue tracker
+## Create a new label when inside an issue
-> Introduced in GitLab 8.6.
-
-There are times when you are already in the issue tracker searching for a
+There are times when you are already inside an issue searching to assign a
label, only to realize it doesn't exist. Instead of going to the **Labels**
page and being distracted from your original purpose, you can create new
labels on the fly.
-Select **Create new** from the labels dropdown list, provide a name, pick a
-color and hit **Create**.
+Expand the issue sidebar and select **Create new label** from the labels dropdown
+list. Provide a name, pick a color and hit **Create**. The new label will be
+ready to used right away!
-![Create new label on the fly](img/labels_new_label_on_the_fly_create.png)
![New label on the fly](img/labels_new_label_on_the_fly.png)
## Assigning labels to issues and merge requests
There are generally two ways to assign a label to an issue or merge request.
-You can assign a label when you first create or edit an issue or merge request.
-
-![Assign label in new issue](img/labels_assign_label_in_new_issue.png)
-
----
+The first one is to assign a label when you first create or edit an issue or
+merge request.
The second way is by using the right sidebar when inside an issue or merge
request. Expand it and hit **Edit** in the labels area. Start typing the name
diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
index 64b94d81024..22ef11e4049 100644
--- a/doc/user/project/merge_requests/cherry_pick_changes.md
+++ b/doc/user/project/merge_requests/cherry_pick_changes.md
@@ -2,24 +2,19 @@
> [Introduced][ce-3514] in GitLab 8.7.
----
-
GitLab implements Git's powerful feature to [cherry-pick any commit][git-cherry-pick]
-with introducing a **Cherry-pick** button in Merge Requests and commit details.
+with introducing a **Cherry-pick** button in merge requests and commit details.
-## Cherry-picking a Merge Request
+## Cherry-picking a merge request
-After the Merge Request has been merged, a **Cherry-pick** button will be available
-to cherry-pick the changes introduced by that Merge Request:
+After the merge request has been merged, a **Cherry-pick** button will be available
+to cherry-pick the changes introduced by that merge request.
![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png)
----
-
-You can cherry-pick the changes directly into the selected branch or you can opt to
-create a new Merge Request with the cherry-pick changes:
-
-![Cherry-pick Merge Request modal](img/cherry_pick_changes_mr_modal.png)
+After you click that button, a modal will appear where you can choose to
+cherry-pick the changes directly into the selected branch or you can opt to
+create a new merge request with the cherry-pick changes
## Cherry-picking a Commit
@@ -27,15 +22,9 @@ You can cherry-pick a Commit from the Commit details page:
![Cherry-pick commit](img/cherry_pick_changes_commit.png)
----
-
-Similar to cherry-picking a Merge Request, you can opt to cherry-pick the changes
-directly into the target branch or create a new Merge Request to cherry-pick the
-changes:
-
-![Cherry-pick commit modal](img/cherry_pick_changes_commit_modal.png)
-
----
+Similar to cherry-picking a merge request, you can opt to cherry-pick the changes
+directly into the target branch or create a new merge request to cherry-pick the
+changes.
Please note that when cherry-picking merge commits, the mainline will always be the
first parent. If you want to use a different mainline then you need to do that
diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png
index 5ab094ab367..7dc344f8cf6 100644
Binary files a/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png and b/doc/user/project/merge_requests/img/cherry_pick_changes_commit.png differ
diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png b/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png
deleted file mode 100644
index 42dcb9203ec..00000000000
Binary files a/doc/user/project/merge_requests/img/cherry_pick_changes_commit_modal.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png
index 71227747182..811b0998f85 100644
Binary files a/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png and b/doc/user/project/merge_requests/img/cherry_pick_changes_mr.png differ
diff --git a/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png b/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png
deleted file mode 100644
index 604eb22f51c..00000000000
Binary files a/doc/user/project/merge_requests/img/cherry_pick_changes_mr_modal.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/commit_compare.png b/doc/user/project/merge_requests/img/commit_compare.png
deleted file mode 100644
index e612a39716e..00000000000
Binary files a/doc/user/project/merge_requests/img/commit_compare.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/merge_request.png b/doc/user/project/merge_requests/img/merge_request.png
new file mode 100644
index 00000000000..f9ca6348953
Binary files /dev/null and b/doc/user/project/merge_requests/img/merge_request.png differ
diff --git a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png
index 33f5a4a7a02..d7f0535d3c5 100644
Binary files a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png and b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_enable.png differ
diff --git a/doc/user/project/merge_requests/img/revert_changes_commit_modal.png b/doc/user/project/merge_requests/img/revert_changes_commit_modal.png
deleted file mode 100644
index ef7b6dae553..00000000000
Binary files a/doc/user/project/merge_requests/img/revert_changes_commit_modal.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/revert_changes_mr_modal.png b/doc/user/project/merge_requests/img/revert_changes_mr_modal.png
deleted file mode 100644
index f6540c9dd33..00000000000
Binary files a/doc/user/project/merge_requests/img/revert_changes_mr_modal.png and /dev/null differ
diff --git a/doc/user/project/merge_requests/img/versions.png b/doc/user/project/merge_requests/img/versions.png
index 33c58d2abff..3883fb4bc1c 100644
Binary files a/doc/user/project/merge_requests/img/versions.png and b/doc/user/project/merge_requests/img/versions.png differ
diff --git a/doc/user/project/merge_requests/img/versions_compare.png b/doc/user/project/merge_requests/img/versions_compare.png
index db978ea7b1d..f5bd85dc7c1 100644
Binary files a/doc/user/project/merge_requests/img/versions_compare.png and b/doc/user/project/merge_requests/img/versions_compare.png differ
diff --git a/doc/user/project/merge_requests/img/versions_dropdown.png b/doc/user/project/merge_requests/img/versions_dropdown.png
index 889a2d93e6c..cc70a5bf14b 100644
Binary files a/doc/user/project/merge_requests/img/versions_dropdown.png and b/doc/user/project/merge_requests/img/versions_dropdown.png differ
diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
index 047b0b4620f..0c492aca363 100644
Binary files a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png and b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png differ
diff --git a/doc/user/project/merge_requests/img/wip_mark_as_wip.png b/doc/user/project/merge_requests/img/wip_mark_as_wip.png
index 8bd206bc24a..e405879b28a 100644
Binary files a/doc/user/project/merge_requests/img/wip_mark_as_wip.png and b/doc/user/project/merge_requests/img/wip_mark_as_wip.png differ
diff --git a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png
index c0bfa6a35a2..d7f8c419945 100644
Binary files a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png and b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 8e081b4f0b8..6289fcf3c2b 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -3,6 +3,8 @@
Merge requests allow you to exchange changes you made to source code and
collaborate with other people on the same project.
+![Merge request view](img/merge_request.png)
+
## Overview
A Merge Request (**MR**) is the basis of GitLab as a code collaboration
diff --git a/doc/user/project/merge_requests/revert_changes.md b/doc/user/project/merge_requests/revert_changes.md
index 5ead9f4177f..8cf8a59dbfe 100644
--- a/doc/user/project/merge_requests/revert_changes.md
+++ b/doc/user/project/merge_requests/revert_changes.md
@@ -2,51 +2,39 @@
> [Introduced][ce-1990] in GitLab 8.5.
----
-
GitLab implements Git's powerful feature to [revert any commit][git-revert]
-with introducing a **Revert** button in Merge Requests and commit details.
+with introducing a **Revert** button in merge requests and commit details.
## Reverting a Merge Request
-_**Note:** The **Revert** button will only be available for Merge Requests
-created since GitLab 8.5. However, you can still revert a Merge Request
-by reverting the merge commit from the list of Commits page._
+NOTE: **Note:**
+The **Revert** button will only be available for merge requests
+created since GitLab 8.5. However, you can still revert a merge request
+by reverting the merge commit from the list of Commits page.
After the Merge Request has been merged, a **Revert** button will be available
-to revert the changes introduced by that Merge Request:
+to revert the changes introduced by that merge request.
-![Revert Merge Request](img/revert_changes_mr.png)
+![Revert Merge Request](img/cherry_pick_changes_mr.png)
----
+After you click that button, a modal will appear where you can choose to
+revert the changes directly into the selected branch or you can opt to
+create a new merge request with the revert changes.
-You can revert the changes directly into the selected branch or you can opt to
-create a new Merge Request with the revert changes:
-
-![Revert Merge Request modal](img/revert_changes_mr_modal.png)
-
----
-
-After the Merge Request has been reverted, the **Revert** button will not be
+After the merge request has been reverted, the **Revert** button will not be
available anymore.
## Reverting a Commit
You can revert a Commit from the Commit details page:
-![Revert commit](img/revert_changes_commit.png)
+![Revert commit](img/cherry_pick_changes_commit.png)
----
+Similar to reverting a merge request, you can opt to revert the changes
+directly into the target branch or create a new merge request to revert the
+changes.
-Similar to reverting a Merge Request, you can opt to revert the changes
-directly into the target branch or create a new Merge Request to revert the
-changes:
-
-![Revert commit modal](img/revert_changes_commit_modal.png)
-
----
-
-After the Commit has been reverted, the **Revert** button will not be available
+After the commit has been reverted, the **Revert** button will not be available
anymore.
Please note that when reverting merge commits, the mainline will always be the
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index f88738b4c61..3490bbd968c 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -26,7 +26,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
end
step 'I fork to my namespace' do
- page.within '.fork-namespaces' do
+ page.within '.fork-thumbnail-container' do
click_link current_user.name
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 22b735c6f7b..89b654253cb 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -53,14 +53,15 @@ module Gitlab
# Rugged repo object
attr_reader :rugged
- attr_reader :storage, :gl_repository, :relative_path
+ attr_reader :storage, :gl_repository, :relative_path, :gitaly_resolver
- # 'path' must be the path to a _bare_ git repository, e.g.
- # /path/to/my-repo.git
+ # This initializer method is only used on the client side (gitlab-ce).
+ # Gitaly-ruby uses a different initializer.
def initialize(storage, relative_path, gl_repository)
@storage = storage
@relative_path = relative_path
@gl_repository = gl_repository
+ @gitaly_resolver = Gitlab::GitalyClient
storage_path = Gitlab.config.repositories.storages[@storage]['path']
@path = File.join(storage_path, @relative_path)
@@ -676,7 +677,13 @@ module Gitlab
end
def rm_branch(branch_name, user:)
- OperationService.new(user, self).rm_branch(find_branch(branch_name))
+ gitaly_migrate(:operation_user_delete_branch) do |is_enabled|
+ if is_enabled
+ gitaly_operations_client.user_delete_branch(branch_name, user)
+ else
+ OperationService.new(user, self).rm_branch(find_branch(branch_name))
+ end
+ end
end
def rm_tag(tag_name, user:)
@@ -981,9 +988,9 @@ module Gitlab
def with_repo_tmp_commit(start_repository, start_branch_name, sha)
tmp_ref = fetch_ref(
- start_repository.path,
- "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
- "refs/tmp/#{SecureRandom.hex}/head"
+ start_repository,
+ source_ref: "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
+ target_ref: "refs/tmp/#{SecureRandom.hex}/head"
)
yield commit(sha)
@@ -1015,13 +1022,27 @@ module Gitlab
end
end
- def write_ref(ref_path, sha)
- rugged.references.create(ref_path, sha, force: true)
+ def write_ref(ref_path, ref)
+ raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
+ raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
+
+ command = [Gitlab.config.git.bin_path] + %w[update-ref --stdin -z]
+ input = "update #{ref_path}\x00#{ref}\x00\x00"
+ output, status = circuit_breaker.perform do
+ popen(command, path) { |stdin| stdin.write(input) }
+ end
+
+ raise GitError, output unless status.zero?
end
- def fetch_ref(source_path, source_ref, target_ref)
- args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
- message, status = run_git(args)
+ def fetch_ref(source_repository, source_ref:, target_ref:)
+ message, status = GitalyClient.migrate(:fetch_ref) do |is_enabled|
+ if is_enabled
+ gitaly_fetch_ref(source_repository, source_ref: source_ref, target_ref: target_ref)
+ else
+ local_fetch_ref(source_repository.path, source_ref: source_ref, target_ref: target_ref)
+ end
+ end
# Make sure ref was created, and raise Rugged::ReferenceError when not
raise Rugged::ReferenceError, message if status != 0
@@ -1030,9 +1051,9 @@ module Gitlab
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
- def run_git(args)
+ def run_git(args, env: {})
circuit_breaker.perform do
- popen([Gitlab.config.git.bin_path, *args], path)
+ popen([Gitlab.config.git.bin_path, *args], path, env)
end
end
@@ -1489,9 +1510,33 @@ module Gitlab
OperationService.new(user, self).add_branch(branch_name, target_object.oid)
find_branch(branch_name)
- rescue Rugged::ReferenceError
+ rescue Rugged::ReferenceError => ex
raise InvalidRef, ex
end
+
+ def local_fetch_ref(source_path, source_ref:, target_ref:)
+ args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
+ run_git(args)
+ end
+
+ def gitaly_fetch_ref(source_repository, source_ref:, target_ref:)
+ gitaly_ssh = File.absolute_path(File.join(Gitlab.config.gitaly.client_path, 'gitaly-ssh'))
+ gitaly_address = gitaly_resolver.address(source_repository.storage)
+ gitaly_token = gitaly_resolver.token(source_repository.storage)
+
+ request = Gitaly::SSHUploadPackRequest.new(repository: source_repository.gitaly_repository)
+ env = {
+ 'GITALY_ADDRESS' => gitaly_address,
+ 'GITALY_PAYLOAD' => request.to_json,
+ 'GITALY_WD' => Dir.pwd,
+ 'GIT_SSH_COMMAND' => "#{gitaly_ssh} upload-pack"
+ }
+ env['GITALY_TOKEN'] = gitaly_token if gitaly_token.present?
+
+ args = %W(fetch --no-tags -f ssh://gitaly/internal.git #{source_ref}:#{target_ref})
+
+ run_git(args, env: env)
+ end
end
end
end
diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb
index e0943d3a3eb..92a6a672534 100644
--- a/lib/gitlab/git/rev_list.rb
+++ b/lib/gitlab/git/rev_list.rb
@@ -31,7 +31,7 @@ module Gitlab
output, status = popen(args, nil, Gitlab::Git::Env.all.stringify_keys)
unless status.zero?
- raise "Got a non-zero exit code while calling out `#{args.join(' ')}`."
+ raise "Got a non-zero exit code while calling out `#{args.join(' ')}`: #{output}"
end
output.split("\n")
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 46bd5c18603..81ddaf13e10 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -60,6 +60,20 @@ module Gitlab
target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit)
Gitlab::Git::Branch.new(@repository, branch.name, target_commit.id, target_commit)
end
+
+ def user_delete_branch(branch_name, user)
+ request = Gitaly::UserDeleteBranchRequest.new(
+ repository: @gitaly_repo,
+ branch_name: GitalyClient.encode(branch_name),
+ user: Util.gitaly_user(user)
+ )
+
+ response = GitalyClient.call(@repository.storage, :operation_service, :user_delete_branch, request)
+
+ if pre_receive_error = response.pre_receive_error.presence
+ raise Gitlab::Git::HooksService::PreReceiveError, pre_receive_error
+ end
+ end
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 104280f520a..d7d24eeb37b 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -25,7 +25,7 @@ module Gitlab
Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
"#{MAX_RSS}"
- Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"\
+ Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']} "\
"in #{GRACE_TIME} seconds"
sleep(GRACE_TIME)
diff --git a/lib/gitlab/sql/union.rb b/lib/gitlab/sql/union.rb
index 222021e8802..f30c771837a 100644
--- a/lib/gitlab/sql/union.rb
+++ b/lib/gitlab/sql/union.rb
@@ -12,8 +12,9 @@ module Gitlab
#
# Project.where("id IN (#{sql})")
class Union
- def initialize(relations)
+ def initialize(relations, remove_duplicates: true)
@relations = relations
+ @remove_duplicates = remove_duplicates
end
def to_sql
@@ -25,7 +26,11 @@ module Gitlab
@relations.map { |rel| rel.reorder(nil).to_sql }.reject(&:blank?)
end
- fragments.join("\nUNION\n")
+ fragments.join("\n#{union_keyword}\n")
+ end
+
+ def union_keyword
+ @remove_duplicates ? 'UNION' : 'UNION ALL'
end
end
end
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
index 259a755d724..a42f02a84fd 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -3,8 +3,8 @@ namespace :gitlab do
desc 'GitLab | Assets | Compile all frontend assets'
task compile: [
'yarn:check',
- 'rake:assets:precompile',
'gettext:po_to_json',
+ 'rake:assets:precompile',
'webpack:compile',
'fix_urls'
]
diff --git a/qa/Gemfile b/qa/Gemfile
index 5d089a45934..ff29824529f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,5 +1,6 @@
source 'https://rubygems.org'
+gem 'pry-byebug', '~> 3.4.1', platform: :mri
gem 'capybara', '~> 2.12.1'
gem 'capybara-screenshot', '~> 1.0.14'
gem 'rake', '~> 12.0.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 4dd71aa5010..95aeef10752 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -3,6 +3,7 @@ GEM
specs:
addressable (2.5.0)
public_suffix (~> 2.0, >= 2.0.2)
+ byebug (9.0.6)
capybara (2.12.1)
addressable
mime-types (>= 1.16)
@@ -13,22 +14,27 @@ GEM
capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3)
launchy
- capybara-webkit (1.12.0)
- capybara (>= 2.3.0, < 2.13.0)
- json
childprocess (0.7.0)
ffi (~> 1.0, >= 1.0.11)
+ coderay (1.1.1)
diff-lcs (1.3)
ffi (1.9.18)
- json (2.0.3)
launchy (2.4.3)
addressable (~> 2.3)
+ method_source (0.8.2)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_portile2 (2.1.0)
nokogiri (1.7.0.1)
mini_portile2 (~> 2.1.0)
+ pry (0.10.4)
+ coderay (~> 1.1.0)
+ method_source (~> 0.8.1)
+ slop (~> 3.4)
+ pry-byebug (3.4.2)
+ byebug (~> 9.0)
+ pry (~> 0.10)
public_suffix (2.0.5)
rack (2.0.1)
rack-test (0.6.3)
@@ -52,6 +58,7 @@ GEM
childprocess (~> 0.5)
rubyzip (~> 1.0)
websocket (~> 1.0)
+ slop (3.6.0)
websocket (1.2.4)
xpath (2.0.0)
nokogiri (~> 1.3)
@@ -62,7 +69,7 @@ PLATFORMS
DEPENDENCIES
capybara (~> 2.12.1)
capybara-screenshot (~> 1.0.14)
- capybara-webkit (~> 1.12.0)
+ pry-byebug (~> 3.4.1)
rake (~> 12.0.0)
rspec (~> 3.5)
selenium-webdriver (~> 2.53)
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index f4619042e34..baa06b1c75e 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -4,8 +4,6 @@ module QA
class Menu < Page::Base
def go_to_license
link = find_link 'License'
- # Click space to scroll this link into the view
- link.send_keys(:space)
link.click
end
end
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
index 4dfdd6cd93c..79c681168cc 100644
--- a/qa/qa/specs/config.rb
+++ b/qa/qa/specs/config.rb
@@ -43,8 +43,7 @@ module QA
Capybara.register_driver :chrome do |app|
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
'chromeOptions' => {
- 'binary' => '/usr/bin/google-chrome-stable',
- 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1024]
+ 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1680]
}
)
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 629c131aee6..e46d1995498 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -96,18 +96,6 @@ describe Projects::MergeRequestsController do
expect(response).to match_response_schema('entities/merge_request')
end
end
-
- context 'number of queries', :request_store do
- it 'verifies number of queries' do
- # pre-create objects
- merge_request
-
- recorded = ActiveRecord::QueryRecorder.new { go(format: :json) }
-
- expect(recorded.count).to be_within(5).of(30)
- expect(recorded.cached_count).to eq(0)
- end
- end
end
describe "as diff" do
diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb
index 2805968dcd9..5d9d5351687 100644
--- a/spec/controllers/projects/registry/repositories_controller_spec.rb
+++ b/spec/controllers/projects/registry/repositories_controller_spec.rb
@@ -42,6 +42,13 @@ describe Projects::Registry::RepositoriesController do
expect { go_to_index }.to change { ContainerRepository.all.count }.by(1)
expect(ContainerRepository.first).to be_root_repository
end
+
+ it 'json has a list of projects' do
+ go_to_index(format: :json)
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('registry/repositories')
+ end
end
context 'when there are no tags for this repository' do
@@ -58,6 +65,31 @@ describe Projects::Registry::RepositoriesController do
it 'does not ensure root container repository' do
expect { go_to_index }.not_to change { ContainerRepository.all.count }
end
+
+ it 'responds with json if asked' do
+ go_to_index(format: :json)
+
+ expect(response).to have_http_status(:ok)
+ expect(json_response).to be_kind_of(Array)
+ end
+ end
+ end
+ end
+
+ describe 'DELETE destroy' do
+ context 'when root container repository exists' do
+ let!(:repository) do
+ create(:container_repository, :root, project: project)
+ end
+
+ before do
+ stub_container_registry_tags(repository: :any, tags: [])
+ end
+
+ it 'deletes a repository' do
+ expect { delete_repository(repository) }.to change { ContainerRepository.all.count }.by(-1)
+
+ expect(response).to have_http_status(:no_content)
end
end
end
@@ -77,8 +109,16 @@ describe Projects::Registry::RepositoriesController do
end
end
- def go_to_index
+ def go_to_index(format: :html)
get :index, namespace_id: project.namespace,
- project_id: project
+ project_id: project,
+ format: format
+ end
+
+ def delete_repository(repository)
+ delete :destroy, namespace_id: project.namespace,
+ project_id: project,
+ id: repository,
+ format: :json
end
end
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index f4af3587d23..bb702ebeb23 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -4,24 +4,83 @@ describe Projects::Registry::TagsController do
let(:user) { create(:user) }
let(:project) { create(:project, :private) }
+ let(:repository) do
+ create(:container_repository, name: 'image', project: project)
+ end
+
before do
sign_in(user)
stub_container_registry_config(enabled: true)
end
- context 'when user has access to registry' do
- before do
- project.add_developer(user)
+ describe 'GET index' do
+ let(:tags) do
+ Array.new(40) { |i| "tag#{i}" }
end
- describe 'POST destroy' do
+ before do
+ stub_container_registry_tags(repository: /image/, tags: tags)
+ end
+
+ context 'when user can control the registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'receive a list of tags' do
+ get_tags
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('registry/tags')
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'when user can read the registry' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'receive a list of tags' do
+ get_tags
+
+ expect(response).to have_http_status(:ok)
+ expect(response).to match_response_schema('registry/tags')
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'when user does not have access to registry' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not receive a list of tags' do
+ get_tags
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ private
+
+ def get_tags
+ get :index, namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ format: :json
+ end
+ end
+
+ describe 'POST destroy' do
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
context 'when there is matching tag present' do
before do
- stub_container_registry_tags(repository: /image/, tags: %w[rc1 test.])
- end
-
- let(:repository) do
- create(:container_repository, name: 'image', project: project)
+ stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
end
it 'makes it possible to delete regular tag' do
@@ -37,12 +96,15 @@ describe Projects::Registry::TagsController do
end
end
end
- end
- def destroy_tag(name)
- post :destroy, namespace_id: project.namespace,
- project_id: project,
- repository_id: repository,
- id: name
+ private
+
+ def destroy_tag(name)
+ post :destroy, namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ id: name,
+ format: :json
+ end
end
end
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index e5abfd67d60..0dd1238d6e2 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -12,7 +12,7 @@ FactoryGirl.define do
deployment.project ||= deployment.environment.project
unless deployment.project.repository_exists?
- allow(deployment.project.repository).to receive(:fetch_ref)
+ allow(deployment.project.repository).to receive(:create_ref)
end
end
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index ae39ba4da6b..45213dc6995 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Container Registry" do
+describe "Container Registry", js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -41,16 +41,19 @@ describe "Container Registry" do
expect_any_instance_of(ContainerRepository)
.to receive(:delete_tags!).and_return(true)
- click_on 'Remove repository'
+ click_on(class: 'js-remove-repo')
end
scenario 'user removes a specific tag from container repository' do
visit_container_registry
+ find('.js-toggle-repo').trigger('click')
+ wait_for_requests
+
expect_any_instance_of(ContainerRegistry::Tag)
.to receive(:delete).and_return(true)
- click_on 'Remove tag'
+ click_on(class: 'js-delete-registry')
end
end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index ad06cee4e81..2f407b13c2f 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do
describe 'when checking branches' do
context 'with artifacts' do
before do
- visit project_branches_path(project)
+ visit project_branches_path(project, search: 'binary-encoding')
end
scenario 'shows download artifacts button' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index ad4527a0b74..d1f5623554d 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -5,12 +5,6 @@ describe 'Branches' do
let(:project) { create(:project, :public, :repository) }
let(:repository) { project.repository }
- def set_protected_branch_name(branch_name)
- find(".js-protected-branch-select").click
- find(".dropdown-input-field").set(branch_name)
- click_on("Create wildcard #{branch_name}")
- end
-
context 'logged in as developer' do
before do
sign_in(user)
@@ -18,12 +12,10 @@ describe 'Branches' do
end
describe 'Initial branches page' do
- it 'shows all the branches' do
+ it 'shows all the branches sorted by last updated by default' do
visit project_branches_path(project)
- repository.branches_sorted_by(:name).first(20).each do |branch|
- expect(page).to have_content("#{branch.name}")
- end
+ expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc))
end
it 'sorts the branches by name' do
@@ -32,22 +24,7 @@ describe 'Branches' do
click_button "Last updated" # Open sorting dropdown
click_link "Name"
- sorted = repository.branches_sorted_by(:name).first(20).map do |branch|
- Regexp.escape(branch.name)
- end
- expect(page).to have_content(/#{sorted.join(".*")}/)
- end
-
- it 'sorts the branches by last updated' do
- visit project_branches_path(project)
-
- click_button "Last updated" # Open sorting dropdown
- click_link "Last updated"
-
- sorted = repository.branches_sorted_by(:updated_desc).first(20).map do |branch|
- Regexp.escape(branch.name)
- end
- expect(page).to have_content(/#{sorted.join(".*")}/)
+ expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :name))
end
it 'sorts the branches by oldest updated' do
@@ -56,10 +33,7 @@ describe 'Branches' do
click_button "Last updated" # Open sorting dropdown
click_link "Oldest updated"
- sorted = repository.branches_sorted_by(:updated_asc).first(20).map do |branch|
- Regexp.escape(branch.name)
- end
- expect(page).to have_content(/#{sorted.join(".*")}/)
+ expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc))
end
it 'avoids a N+1 query in branches index' do
@@ -99,28 +73,6 @@ describe 'Branches' do
expect(find('.all-branches')).to have_selector('li', count: 0)
end
end
-
- describe 'Delete protected branch' do
- before do
- project.add_user(user, :master)
- visit project_protected_branches_path(project)
- set_protected_branch_name('fix')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content('fix') }
- expect(ProtectedBranch.count).to eq(1)
- project.add_user(user, :developer)
- end
-
- it 'does not allow devleoper to removes protected branch', js: true do
- visit project_branches_path(project)
-
- fill_in 'branch-search', with: 'fix'
- find('#branch-search').native.send_keys(:enter)
-
- expect(page).to have_css('.btn-remove.disabled')
- end
- end
end
context 'logged in as master' do
@@ -136,37 +88,6 @@ describe 'Branches' do
expect(page).to have_content("Protected branches can be managed in project settings")
end
end
-
- describe 'Delete protected branch' do
- before do
- visit project_protected_branches_path(project)
- set_protected_branch_name('fix')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content('fix') }
- expect(ProtectedBranch.count).to eq(1)
- end
-
- it 'removes branch after modal confirmation', js: true do
- visit project_branches_path(project)
-
- fill_in 'branch-search', with: 'fix'
- find('#branch-search').native.send_keys(:enter)
-
- expect(page).to have_content('fix')
- expect(find('.all-branches')).to have_selector('li', count: 1)
- page.find('[data-target="#modal-delete-branch"]').trigger(:click)
-
- expect(page).to have_css('.js-delete-branch[disabled]')
- fill_in 'delete_branch_input', with: 'fix'
- click_link 'Delete protected branch'
-
- fill_in 'branch-search', with: 'fix'
- find('#branch-search').native.send_keys(:enter)
-
- expect(page).to have_content('No branches to show')
- end
- end
end
context 'logged out' do
@@ -180,4 +101,13 @@ describe 'Branches' do
end
end
end
+
+ def sorted_branches(repository, count:, sort_by:)
+ sorted_branches =
+ repository.branches_sorted_by(sort_by).first(count).map do |branch|
+ Regexp.escape(branch.name)
+ end
+
+ Regexp.new(sorted_branches.join('.*'))
+ end
end
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 3677bf38724..bf9885f73bd 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,11 +1,148 @@
require 'spec_helper'
-feature 'Protected Branches', js: true do
- let(:user) { create(:user, :admin) }
+feature 'Protected Branches', :js do
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
let(:project) { create(:project, :repository) }
- before do
- sign_in(user)
+ context 'logged in as developer' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ describe 'Delete protected branch' do
+ before do
+ create(:protected_branch, project: project, name: 'fix')
+ expect(ProtectedBranch.count).to eq(1)
+ end
+
+ it 'does not allow developer to removes protected branch' do
+ visit project_branches_path(project)
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_css('.btn-remove.disabled')
+ end
+ end
+ end
+
+ context 'logged in as master' do
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ describe 'Delete protected branch' do
+ before do
+ create(:protected_branch, project: project, name: 'fix')
+ expect(ProtectedBranch.count).to eq(1)
+ end
+
+ it 'removes branch after modal confirmation' do
+ visit project_branches_path(project)
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 1)
+ page.find('[data-target="#modal-delete-branch"]').trigger(:click)
+
+ expect(page).to have_css('.js-delete-branch[disabled]')
+ fill_in 'delete_branch_input', with: 'fix'
+ click_link 'Delete protected branch'
+
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('No branches to show')
+ end
+ end
+ end
+
+ context 'logged in as admin' do
+ before do
+ sign_in(admin)
+ end
+
+ describe "explicit protected branches" do
+ it "allows creating explicit protected branches" do
+ visit project_protected_branches_path(project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('some-branch') }
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.name).to eq('some-branch')
+ end
+
+ it "displays the last commit on the matching branch if it exists" do
+ commit = create(:commit, project: project)
+ project.repository.add_branch(admin, 'some-branch', commit.id)
+
+ visit project_protected_branches_path(project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) }
+ end
+
+ it "displays an error message if the named branch does not exist" do
+ visit project_protected_branches_path(project)
+ set_protected_branch_name('some-branch')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('branch was removed') }
+ end
+ end
+
+ describe "wildcard protected branches" do
+ it "allows creating protected branches with a wildcard" do
+ visit project_protected_branches_path(project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content('*-stable') }
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.name).to eq('*-stable')
+ end
+
+ it "displays the number of matching branches" do
+ project.repository.add_branch(admin, 'production-stable', 'master')
+ project.repository.add_branch(admin, 'staging-stable', 'master')
+
+ visit project_protected_branches_path(project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ within(".protected-branches-list") { expect(page).to have_content("2 matching branches") }
+ end
+
+ it "displays all the branches matching the wildcard" do
+ project.repository.add_branch(admin, 'production-stable', 'master')
+ project.repository.add_branch(admin, 'staging-stable', 'master')
+ project.repository.add_branch(admin, 'development', 'master')
+
+ visit project_protected_branches_path(project)
+ set_protected_branch_name('*-stable')
+ click_on "Protect"
+
+ visit project_protected_branches_path(project)
+ click_on "2 matching branches"
+
+ within(".protected-branches-list") do
+ expect(page).to have_content("production-stable")
+ expect(page).to have_content("staging-stable")
+ expect(page).not_to have_content("development")
+ end
+ end
+ end
+
+ describe "access control" do
+ include_examples "protected branches > access control > CE"
+ end
end
def set_protected_branch_name(branch_name)
@@ -13,81 +150,4 @@ feature 'Protected Branches', js: true do
find(".dropdown-input-field").set(branch_name)
click_on("Create wildcard #{branch_name}")
end
-
- describe "explicit protected branches" do
- it "allows creating explicit protected branches" do
- visit project_protected_branches_path(project)
- set_protected_branch_name('some-branch')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content('some-branch') }
- expect(ProtectedBranch.count).to eq(1)
- expect(ProtectedBranch.last.name).to eq('some-branch')
- end
-
- it "displays the last commit on the matching branch if it exists" do
- commit = create(:commit, project: project)
- project.repository.add_branch(user, 'some-branch', commit.id)
-
- visit project_protected_branches_path(project)
- set_protected_branch_name('some-branch')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content(commit.id[0..7]) }
- end
-
- it "displays an error message if the named branch does not exist" do
- visit project_protected_branches_path(project)
- set_protected_branch_name('some-branch')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content('branch was removed') }
- end
- end
-
- describe "wildcard protected branches" do
- it "allows creating protected branches with a wildcard" do
- visit project_protected_branches_path(project)
- set_protected_branch_name('*-stable')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content('*-stable') }
- expect(ProtectedBranch.count).to eq(1)
- expect(ProtectedBranch.last.name).to eq('*-stable')
- end
-
- it "displays the number of matching branches" do
- project.repository.add_branch(user, 'production-stable', 'master')
- project.repository.add_branch(user, 'staging-stable', 'master')
-
- visit project_protected_branches_path(project)
- set_protected_branch_name('*-stable')
- click_on "Protect"
-
- within(".protected-branches-list") { expect(page).to have_content("2 matching branches") }
- end
-
- it "displays all the branches matching the wildcard" do
- project.repository.add_branch(user, 'production-stable', 'master')
- project.repository.add_branch(user, 'staging-stable', 'master')
- project.repository.add_branch(user, 'development', 'master')
-
- visit project_protected_branches_path(project)
- set_protected_branch_name('*-stable')
- click_on "Protect"
-
- visit project_protected_branches_path(project)
- click_on "2 matching branches"
-
- within(".protected-branches-list") do
- expect(page).to have_content("production-stable")
- expect(page).to have_content("staging-stable")
- expect(page).not_to have_content("development")
- end
- end
- end
-
- describe "access control" do
- include_examples "protected branches > access control > CE"
- end
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index 0796d9b8af9..30b4e56bc98 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -93,7 +93,7 @@
"merge_commit_message_with_description": { "type": "string" },
"diverged_commits_count": { "type": "integer" },
"commit_change_content_path": { "type": "string" },
- "remove_wip_path": { "type": "string" },
+ "remove_wip_path": { "type": ["string", "null"] },
"commits_count": { "type": "integer" },
"remove_source_branch": { "type": ["boolean", "null"] },
"merge_ongoing": { "type": "boolean" },
diff --git a/spec/fixtures/api/schemas/registry/repositories.json b/spec/fixtures/api/schemas/registry/repositories.json
new file mode 100644
index 00000000000..4978bd89cda
--- /dev/null
+++ b/spec/fixtures/api/schemas/registry/repositories.json
@@ -0,0 +1,6 @@
+{
+ "type": "array",
+ "items": {
+ "$ref": "repository.json"
+ }
+}
diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json
new file mode 100644
index 00000000000..4175642eb00
--- /dev/null
+++ b/spec/fixtures/api/schemas/registry/repository.json
@@ -0,0 +1,27 @@
+{
+ "type": "object",
+ "required" : [
+ "id",
+ "path",
+ "location",
+ "tags_path"
+ ],
+ "properties" : {
+ "id": {
+ "type": "integer"
+ },
+ "path": {
+ "type": "string"
+ },
+ "location": {
+ "type": "string"
+ },
+ "tags_path": {
+ "type": "string"
+ },
+ "destroy_path": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/registry/tag.json b/spec/fixtures/api/schemas/registry/tag.json
new file mode 100644
index 00000000000..5bc307e0e64
--- /dev/null
+++ b/spec/fixtures/api/schemas/registry/tag.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "required" : [
+ "name",
+ "location"
+ ],
+ "properties" : {
+ "name": {
+ "type": "string"
+ },
+ "location": {
+ "type": "string"
+ },
+ "revision": {
+ "type": "string"
+ },
+ "total_size": {
+ "type": "integer"
+ },
+ "created_at": {
+ "type": "date"
+ },
+ "destroy_path": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/registry/tags.json b/spec/fixtures/api/schemas/registry/tags.json
new file mode 100644
index 00000000000..c72f957459a
--- /dev/null
+++ b/spec/fixtures/api/schemas/registry/tags.json
@@ -0,0 +1,6 @@
+{
+ "type": "array",
+ "items": {
+ "$ref": "tag.json"
+ }
+}
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 4bc2205e642..3fd16d76f51 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -41,6 +41,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
remove_repository(project)
end
+ it 'merge_requests/merge_request_of_current_user.html.raw' do |example|
+ merge_request.update(author: admin)
+
+ render_merge_request(example.description, merge_request)
+ end
+
it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
create(:ci_build, :pending, pipeline: pipeline)
diff --git a/spec/javascripts/notes/stores/helpers.js b/spec/javascripts/helpers/vuex_action_helper.js
similarity index 100%
rename from spec/javascripts/notes/stores/helpers.js
rename to spec/javascripts/helpers/vuex_action_helper.js
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 6ff42e2378d..3ab901da6b6 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -58,5 +58,44 @@ import IssuablesHelper from '~/helpers/issuables_helper';
expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
});
});
+
+ describe('hideCloseButton', () => {
+ describe('merge request of another user', () => {
+ beforeEach(() => {
+ loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ this.el = document.querySelector('.merge-request .issuable-actions');
+ const merge = new MergeRequest();
+ merge.hideCloseButton();
+ });
+
+ it('hides the dropdown close item and selects the next item', () => {
+ const closeItem = this.el.querySelector('li.close-item');
+ const smallCloseItem = this.el.querySelector('.js-close-item');
+ const reportItem = this.el.querySelector('li.report-item');
+
+ expect(closeItem).toHaveClass('hidden');
+ expect(smallCloseItem).toHaveClass('hidden');
+ expect(reportItem).toHaveClass('droplab-item-selected');
+ expect(reportItem).not.toHaveClass('hidden');
+ });
+ });
+
+ describe('merge request of current_user', () => {
+ beforeEach(() => {
+ loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ this.el = document.querySelector('.merge-request .issuable-actions');
+ const merge = new MergeRequest();
+ merge.hideCloseButton();
+ });
+
+ it('hides the close button', () => {
+ const closeButton = this.el.querySelector('.btn-close');
+ const smallCloseItem = this.el.querySelector('.js-close-item');
+
+ expect(closeButton).toHaveClass('hidden');
+ expect(smallCloseItem).toHaveClass('hidden');
+ });
+ });
+ });
});
}).call(window);
diff --git a/spec/javascripts/notes/components/issue_comment_form_spec.js b/spec/javascripts/notes/components/issue_comment_form_spec.js
index 1c8b1b98242..3f659af5c3b 100644
--- a/spec/javascripts/notes/components/issue_comment_form_spec.js
+++ b/spec/javascripts/notes/components/issue_comment_form_spec.js
@@ -33,6 +33,30 @@ describe('issue_comment_form component', () => {
expect(vm.$el.querySelector('.timeline-icon .user-avatar-link').getAttribute('href')).toEqual(userDataMock.path);
});
+ describe('handleSave', () => {
+ it('should request to save note when note is entered', () => {
+ vm.note = 'hello world';
+ spyOn(vm, 'saveNote').and.returnValue(new Promise(() => {}));
+ spyOn(vm, 'resizeTextarea');
+ spyOn(vm, 'stopPolling');
+
+ vm.handleSave();
+ expect(vm.isSubmitting).toEqual(true);
+ expect(vm.note).toEqual('');
+ expect(vm.saveNote).toHaveBeenCalled();
+ expect(vm.stopPolling).toHaveBeenCalled();
+ expect(vm.resizeTextarea).toHaveBeenCalled();
+ });
+
+ it('should toggle issue state when no note', () => {
+ spyOn(vm, 'toggleIssueState');
+
+ vm.handleSave();
+
+ expect(vm.toggleIssueState).toHaveBeenCalled();
+ });
+ });
+
describe('textarea', () => {
it('should render textarea with placeholder', () => {
expect(
@@ -40,6 +64,22 @@ describe('issue_comment_form component', () => {
).toEqual('Write a comment or drag your files here...');
});
+ it('should make textarea disabled while requesting', (done) => {
+ const $submitButton = $(vm.$el.querySelector('.js-comment-submit-button'));
+ vm.note = 'hello world';
+ spyOn(vm, 'stopPolling');
+ spyOn(vm, 'saveNote').and.returnValue(new Promise(() => {}));
+
+ vm.$nextTick(() => { // Wait for vm.note change triggered. It should enable $submitButton.
+ $submitButton.trigger('click');
+
+ vm.$nextTick(() => { // Wait for vm.isSubmitting triggered. It should disable textarea.
+ expect(vm.$el.querySelector('.js-main-target-form textarea').disabled).toBeTruthy();
+ done();
+ });
+ });
+ });
+
it('should support quick actions', () => {
expect(
vm.$el.querySelector('.js-main-target-form textarea').getAttribute('data-supports-quick-actions'),
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 2b2219dcf0c..3d1ca870ca4 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -1,5 +1,5 @@
import * as actions from '~/notes/stores/actions';
-import testAction from './helpers';
+import testAction from '../../helpers/vuex_action_helper';
import { discussionMock, notesDataMock, userDataMock, issueDataMock, individualNote } from '../mock_data';
describe('Actions Notes Store', () => {
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
new file mode 100644
index 00000000000..43e7d9e1224
--- /dev/null
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -0,0 +1,122 @@
+import Vue from 'vue';
+import registry from '~/registry/components/app.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+import { reposServerResponse } from '../mock_data';
+
+describe('Registry List', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(registry);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('with data', () => {
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify(reposServerResponse), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ vm = mountComponent(Component, { endpoint: 'foo' });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ });
+
+ it('should render a list of repos', (done) => {
+ setTimeout(() => {
+ expect(vm.$store.state.repos.length).toEqual(reposServerResponse.length);
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelectorAll('.container-image').length,
+ ).toEqual(reposServerResponse.length);
+ done();
+ });
+ }, 0);
+ });
+
+ describe('delete repository', () => {
+ it('should be possible to delete a repo', (done) => {
+ setTimeout(() => {
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.container-image-head .js-remove-repo')).toBeDefined();
+ done();
+ });
+ }, 0);
+ });
+ });
+
+ describe('toggle repository', () => {
+ it('should open the container', (done) => {
+ setTimeout(() => {
+ Vue.nextTick(() => {
+ vm.$el.querySelector('.js-toggle-repo').click();
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.js-toggle-repo i').className).toEqual('fa fa-chevron-up');
+ done();
+ });
+ });
+ }, 0);
+ });
+ });
+ });
+
+ describe('without data', () => {
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([]), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ vm = mountComponent(Component, { endpoint: 'foo' });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ });
+
+ it('should render empty message', (done) => {
+ setTimeout(() => {
+ expect(
+ vm.$el.querySelector('p').textContent.trim(),
+ ).toEqual('No container images stored for this project. Add one by following the instructions above.');
+ done();
+ }, 0);
+ });
+ });
+
+ describe('while loading data', () => {
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify(reposServerResponse), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ vm = mountComponent(Component, { endpoint: 'foo' });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ });
+
+ it('should render a loading spinner', (done) => {
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.fa-spinner')).not.toBe(null);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
new file mode 100644
index 00000000000..5891921318a
--- /dev/null
+++ b/spec/javascripts/registry/components/collapsible_container_spec.js
@@ -0,0 +1,58 @@
+import Vue from 'vue';
+import collapsibleComponent from '~/registry/components/collapsible_container.vue';
+import store from '~/registry/stores';
+import { repoPropsData } from '../mock_data';
+
+describe('collapsible registry container', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(collapsibleComponent);
+ vm = new Component({
+ store,
+ propsData: {
+ repo: repoPropsData,
+ },
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('toggle', () => {
+ it('should be closed by default', () => {
+ expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
+ expect(vm.$el.querySelector('.container-image-head i').className).toEqual('fa fa-chevron-right');
+ });
+
+ it('should be open when user clicks on closed repo', (done) => {
+ vm.$el.querySelector('.js-toggle-repo').click();
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.container-image-tags')).toBeDefined();
+ expect(vm.$el.querySelector('.container-image-head i').className).toEqual('fa fa-chevron-up');
+ done();
+ });
+ });
+
+ it('should be closed when the user clicks on an opened repo', (done) => {
+ vm.$el.querySelector('.js-toggle-repo').click();
+
+ Vue.nextTick(() => {
+ vm.$el.querySelector('.js-toggle-repo').click();
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
+ expect(vm.$el.querySelector('.container-image-head i').className).toEqual('fa fa-chevron-right');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('delete repo', () => {
+ it('should be possible to delete a repo', () => {
+ expect(vm.$el.querySelector('.js-remove-repo')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
new file mode 100644
index 00000000000..6aa61afc445
--- /dev/null
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -0,0 +1,49 @@
+import Vue from 'vue';
+import tableRegistry from '~/registry/components/table_registry.vue';
+import store from '~/registry/stores';
+import { repoPropsData } from '../mock_data';
+
+describe('table registry', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(tableRegistry);
+ vm = new Component({
+ store,
+ propsData: {
+ repo: repoPropsData,
+ },
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render a table with the registry list', () => {
+ expect(
+ vm.$el.querySelectorAll('table tbody tr').length,
+ ).toEqual(repoPropsData.list.length);
+ });
+
+ it('should render registry tag', () => {
+ const textRendered = vm.$el.querySelector('.table tbody tr').textContent.trim().replace(/\s\s+/g, ' ');
+ expect(textRendered).toContain(repoPropsData.list[0].tag);
+ expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
+ expect(textRendered).toContain(repoPropsData.list[0].layers);
+ expect(textRendered).toContain(repoPropsData.list[0].size);
+ });
+
+ it('should be possible to delete a registry', () => {
+ expect(
+ vm.$el.querySelector('.table tbody tr .js-delete-registry'),
+ ).toBeDefined();
+ });
+
+ describe('pagination', () => {
+ it('should be possible to change the page', () => {
+ expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/javascripts/registry/getters_spec.js b/spec/javascripts/registry/getters_spec.js
new file mode 100644
index 00000000000..3d989541881
--- /dev/null
+++ b/spec/javascripts/registry/getters_spec.js
@@ -0,0 +1,43 @@
+import * as getters from '~/registry/stores/getters';
+
+describe('Getters Registry Store', () => {
+ let state;
+
+ beforeEach(() => {
+ state = {
+ isLoading: false,
+ endpoint: '/root/empty-project/container_registry.json',
+ repos: [{
+ canDelete: true,
+ destroyPath: 'bar',
+ id: '134',
+ isLoading: false,
+ list: [],
+ location: 'foo',
+ name: 'gitlab-org/omnibus-gitlab/foo',
+ tagsPath: 'foo',
+ }, {
+ canDelete: true,
+ destroyPath: 'bar',
+ id: '123',
+ isLoading: false,
+ list: [],
+ location: 'foo',
+ name: 'gitlab-org/omnibus-gitlab',
+ tagsPath: 'foo',
+ }],
+ };
+ });
+
+ describe('isLoading', () => {
+ it('should return the isLoading property', () => {
+ expect(getters.isLoading(state)).toEqual(state.isLoading);
+ });
+ });
+
+ describe('repos', () => {
+ it('should return the repos', () => {
+ expect(getters.repos(state)).toEqual(state.repos);
+ });
+ });
+});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js
new file mode 100644
index 00000000000..18600d00bff
--- /dev/null
+++ b/spec/javascripts/registry/mock_data.js
@@ -0,0 +1,122 @@
+export const defaultState = {
+ isLoading: false,
+ endpoint: '',
+ repos: [],
+};
+
+export const reposServerResponse = [
+ {
+ destroy_path: 'path',
+ id: '123',
+ location: 'location',
+ path: 'foo',
+ tags_path: 'tags_path',
+ },
+ {
+ destroy_path: 'path_',
+ id: '456',
+ location: 'location_',
+ path: 'bar',
+ tags_path: 'tags_path_',
+ },
+];
+
+export const registryServerResponse = [
+ {
+ name: 'centos7',
+ short_revision: 'b118ab5b0',
+ revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
+ size: 679,
+ layers: 19,
+ location: 'location',
+ created_at: 1505828744434,
+ destroy_path: 'path_',
+ },
+ {
+ name: 'centos6',
+ short_revision: 'b118ab5b0',
+ revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
+ size: 679,
+ layers: 19,
+ location: 'location',
+ created_at: 1505828744434,
+ }];
+
+export const parsedReposServerResponse = [
+ {
+ canDelete: true,
+ destroyPath: reposServerResponse[0].destroy_path,
+ id: reposServerResponse[0].id,
+ isLoading: false,
+ list: [],
+ location: reposServerResponse[0].location,
+ name: reposServerResponse[0].path,
+ tagsPath: reposServerResponse[0].tags_path,
+ },
+ {
+ canDelete: true,
+ destroyPath: reposServerResponse[1].destroy_path,
+ id: reposServerResponse[1].id,
+ isLoading: false,
+ list: [],
+ location: reposServerResponse[1].location,
+ name: reposServerResponse[1].path,
+ tagsPath: reposServerResponse[1].tags_path,
+ },
+];
+
+export const parsedRegistryServerResponse = [
+ {
+ tag: registryServerResponse[0].name,
+ revision: registryServerResponse[0].revision,
+ shortRevision: registryServerResponse[0].short_revision,
+ size: registryServerResponse[0].size,
+ layers: registryServerResponse[0].layers,
+ location: registryServerResponse[0].location,
+ createdAt: registryServerResponse[0].created_at,
+ destroyPath: registryServerResponse[0].destroy_path,
+ canDelete: true,
+ },
+ {
+ tag: registryServerResponse[1].name,
+ revision: registryServerResponse[1].revision,
+ shortRevision: registryServerResponse[1].short_revision,
+ size: registryServerResponse[1].size,
+ layers: registryServerResponse[1].layers,
+ location: registryServerResponse[1].location,
+ createdAt: registryServerResponse[1].created_at,
+ destroyPath: registryServerResponse[1].destroy_path,
+ canDelete: false,
+ },
+];
+
+export const repoPropsData = {
+ canDelete: true,
+ destroyPath: 'path',
+ id: '123',
+ isLoading: false,
+ list: [
+ {
+ tag: 'centos6',
+ revision: 'b118ab5b0e90b7cb5127db31d5321ac14961d097516a8e0e72084b6cdc783b43',
+ shortRevision: 'b118ab5b0',
+ size: 19,
+ layers: 10,
+ location: 'location',
+ createdAt: 1505828744434,
+ destroyPath: 'path',
+ canDelete: true,
+ },
+ ],
+ location: 'location',
+ name: 'foo',
+ tagsPath: 'path',
+ pagination: {
+ perPage: 5,
+ page: 1,
+ total: 13,
+ totalPages: 1,
+ nextPage: null,
+ previousPage: null,
+ },
+};
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
new file mode 100644
index 00000000000..3c9da4f107b
--- /dev/null
+++ b/spec/javascripts/registry/stores/actions_spec.js
@@ -0,0 +1,85 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+import _ from 'underscore';
+import * as actions from '~/registry/stores/actions';
+import * as types from '~/registry/stores/mutation_types';
+import testAction from '../../helpers/vuex_action_helper';
+import {
+ defaultState,
+ reposServerResponse,
+ registryServerResponse,
+ parsedReposServerResponse,
+} from '../mock_data';
+
+Vue.use(VueResource);
+
+describe('Actions Registry Store', () => {
+ let interceptor;
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = defaultState;
+ });
+
+ describe('server requests', () => {
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ });
+
+ describe('fetchRepos', () => {
+ beforeEach(() => {
+ interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify(reposServerResponse), {
+ status: 200,
+ }));
+ };
+
+ Vue.http.interceptors.push(interceptor);
+ });
+
+ it('should set receveived repos', (done) => {
+ testAction(actions.fetchRepos, null, mockedState, [
+ { type: types.TOGGLE_MAIN_LOADING },
+ { type: types.SET_REPOS_LIST, payload: reposServerResponse },
+ ], done);
+ });
+ });
+
+ describe('fetchList', () => {
+ beforeEach(() => {
+ interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify(registryServerResponse), {
+ status: 200,
+ }));
+ };
+
+ Vue.http.interceptors.push(interceptor);
+ });
+
+ it('should set received list', (done) => {
+ mockedState.repos = parsedReposServerResponse;
+
+ testAction(actions.fetchList, { repo: mockedState.repos[1] }, mockedState, [
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING },
+ { type: types.SET_REGISTRY_LIST, payload: registryServerResponse },
+ ], done);
+ });
+ });
+ });
+
+ describe('setMainEndpoint', () => {
+ it('should commit set main endpoint', (done) => {
+ testAction(actions.setMainEndpoint, 'endpoint', mockedState, [
+ { type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' },
+ ], done);
+ });
+ });
+
+ describe('toggleLoading', () => {
+ it('should commit toggle main loading', (done) => {
+ testAction(actions.toggleLoading, null, mockedState, [
+ { type: types.TOGGLE_MAIN_LOADING },
+ ], done);
+ });
+ });
+});
diff --git a/spec/javascripts/registry/stores/mutations_spec.js b/spec/javascripts/registry/stores/mutations_spec.js
new file mode 100644
index 00000000000..2e4c0659daa
--- /dev/null
+++ b/spec/javascripts/registry/stores/mutations_spec.js
@@ -0,0 +1,81 @@
+import mutations from '~/registry/stores/mutations';
+import * as types from '~/registry/stores/mutation_types';
+import {
+ defaultState,
+ reposServerResponse,
+ registryServerResponse,
+ parsedReposServerResponse,
+ parsedRegistryServerResponse,
+} from '../mock_data';
+
+describe('Mutations Registry Store', () => {
+ let mockState;
+ beforeEach(() => {
+ mockState = defaultState;
+ });
+
+ describe('SET_MAIN_ENDPOINT', () => {
+ it('should set the main endpoint', () => {
+ const expectedState = Object.assign({}, mockState, { endpoint: 'foo' });
+ mutations[types.SET_MAIN_ENDPOINT](mockState, 'foo');
+ expect(mockState).toEqual(expectedState);
+ });
+ });
+
+ describe('SET_REPOS_LIST', () => {
+ it('should set a parsed repository list', () => {
+ mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
+ expect(mockState.repos).toEqual(parsedReposServerResponse);
+ });
+ });
+
+ describe('TOGGLE_MAIN_LOADING', () => {
+ it('should set a parsed repository list', () => {
+ mutations[types.TOGGLE_MAIN_LOADING](mockState);
+ expect(mockState.isLoading).toEqual(true);
+ });
+ });
+
+ describe('SET_REGISTRY_LIST', () => {
+ it('should set a list of registries in a specific repository', () => {
+ mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
+ mutations[types.SET_REGISTRY_LIST](mockState, {
+ repo: mockState.repos[0],
+ resp: registryServerResponse,
+ headers: {
+ 'x-per-page': 2,
+ 'x-page': 1,
+ 'x-total': 10,
+ },
+ });
+
+ expect(mockState.repos[0].list).toEqual(parsedRegistryServerResponse);
+ expect(mockState.repos[0].pagination).toEqual({
+ perPage: 2,
+ page: 1,
+ total: 10,
+ totalPages: NaN,
+ nextPage: NaN,
+ previousPage: NaN,
+ });
+ });
+ });
+
+ describe('TOGGLE_REGISTRY_LIST_LOADING', () => {
+ it('should toggle isLoading property for a specific repository', () => {
+ mutations[types.SET_REPOS_LIST](mockState, reposServerResponse);
+ mutations[types.SET_REGISTRY_LIST](mockState, {
+ repo: mockState.repos[0],
+ resp: registryServerResponse,
+ headers: {
+ 'x-per-page': 2,
+ 'x-page': 1,
+ 'x-total': 10,
+ },
+ });
+
+ mutations[types.TOGGLE_REGISTRY_LIST_LOADING](mockState, mockState.repos[0]);
+ expect(mockState.repos[0].isLoading).toEqual(true);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 2422e844e97..c83b947579b 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -95,38 +95,87 @@ describe('MRWidgetReadyToMerge', () => {
});
});
+ describe('status', () => {
+ it('defaults to success', () => {
+ vm.mr.pipeline = true;
+ expect(vm.status).toEqual('success');
+ });
+
+ it('returns failed when MR has CI but also has an unknown status', () => {
+ vm.mr.hasCI = true;
+ expect(vm.status).toEqual('failed');
+ });
+
+ it('returns default when MR has no pipeline', () => {
+ expect(vm.status).toEqual('success');
+ });
+
+ it('returns pending when pipeline is active', () => {
+ vm.mr.pipeline = {};
+ vm.mr.isPipelineActive = true;
+ expect(vm.status).toEqual('pending');
+ });
+
+ it('returns failed when pipeline is failed', () => {
+ vm.mr.pipeline = {};
+ vm.mr.isPipelineFailed = true;
+ expect(vm.status).toEqual('failed');
+ });
+ });
+
describe('mergeButtonClass', () => {
const defaultClass = 'btn btn-sm btn-success accept-merge-request';
const failedClass = `${defaultClass} btn-danger`;
const inActionClass = `${defaultClass} btn-info`;
- it('should return default class', () => {
+ it('defaults to success class', () => {
+ expect(vm.mergeButtonClass).toEqual(defaultClass);
+ });
+
+ it('returns success class for success status', () => {
vm.mr.pipeline = true;
expect(vm.mergeButtonClass).toEqual(defaultClass);
});
- it('should return failed class when MR has CI but also has an unknown status', () => {
- vm.mr.hasCI = true;
- expect(vm.mergeButtonClass).toEqual(failedClass);
- });
-
- it('should return default class when MR has no pipeline', () => {
- expect(vm.mergeButtonClass).toEqual(defaultClass);
- });
-
- it('should return in action class when pipeline is active', () => {
+ it('returns info class for pending status', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineActive = true;
expect(vm.mergeButtonClass).toEqual(inActionClass);
});
- it('should return failed class when pipeline is failed', () => {
- vm.mr.pipeline = {};
- vm.mr.isPipelineFailed = true;
+ it('returns failed class for failed status', () => {
+ vm.mr.hasCI = true;
expect(vm.mergeButtonClass).toEqual(failedClass);
});
});
+ describe('status icon', () => {
+ it('defaults to tick icon', () => {
+ expect(vm.iconClass).toEqual('success');
+ });
+
+ it('shows tick for success status', () => {
+ vm.mr.pipeline = true;
+ expect(vm.iconClass).toEqual('success');
+ });
+
+ it('shows tick for pending status', () => {
+ vm.mr.pipeline = {};
+ vm.mr.isPipelineActive = true;
+ expect(vm.iconClass).toEqual('success');
+ });
+
+ it('shows x for failed status', () => {
+ vm.mr.hasCI = true;
+ expect(vm.iconClass).toEqual('failed');
+ });
+
+ it('shows x for merge not allowed', () => {
+ vm.mr.hasCI = true;
+ expect(vm.iconClass).toEqual('failed');
+ });
+ });
+
describe('mergeButtonText', () => {
it('should return Merge', () => {
expect(vm.mergeButtonText).toEqual('Merge');
@@ -177,7 +226,7 @@ describe('MRWidgetReadyToMerge', () => {
expect(vm.isMergeButtonDisabled).toBeTruthy();
});
- it('should return true when there vm instance is making request', () => {
+ it('should return true when the vm instance is making request', () => {
vm.isMakingRequest = true;
expect(vm.isMergeButtonDisabled).toBeTruthy();
});
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index a0482e30a33..5f12125beb2 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1444,6 +1444,51 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#rm_branch' do
+ shared_examples "user deleting a branch" do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
+ let(:user) { create(:user) }
+ let(:branch_name) { "to-be-deleted-soon" }
+
+ before do
+ project.team << [user, :developer]
+ repository.create_branch(branch_name)
+ end
+
+ it "removes the branch from the repo" do
+ repository.rm_branch(branch_name, user: user)
+
+ expect(repository.rugged.branches[branch_name]).to be_nil
+ end
+ end
+
+ context "when Gitaly user_delete_branch is enabled" do
+ it_behaves_like "user deleting a branch"
+ end
+
+ context "when Gitaly user_delete_branch is disabled", skip_gitaly_mock: true do
+ it_behaves_like "user deleting a branch"
+ end
+ end
+
+ describe '#write_ref' do
+ context 'validations' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ref_path, :ref) do
+ 'foo bar' | '123'
+ 'foobar' | "12\x003"
+ end
+
+ with_them do
+ it 'raises ArgumentError' do
+ expect { repository.write_ref(ref_path, ref) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+ end
+
def create_remote_branch(repository, remote_name, branch_name, source_branch_name)
source_branch = repository.branches.find { |branch| branch.name == source_branch_name }
rugged = repository.rugged
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index 769b14687ac..7bd6a7fa842 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -4,10 +4,10 @@ describe Gitlab::GitalyClient::OperationService do
let(:project) { create(:project) }
let(:repository) { project.repository.raw }
let(:client) { described_class.new(repository) }
+ let(:user) { create(:user) }
+ let(:gitaly_user) { Gitlab::GitalyClient::Util.gitaly_user(user) }
describe '#user_create_branch' do
- let(:user) { create(:user) }
- let(:gitaly_user) { Gitlab::GitalyClient::Util.gitaly_user(user) }
let(:branch_name) { 'new' }
let(:start_point) { 'master' }
let(:request) do
@@ -52,4 +52,41 @@ describe Gitlab::GitalyClient::OperationService do
end
end
end
+
+ describe '#user_delete_branch' do
+ let(:branch_name) { 'my-branch' }
+ let(:request) do
+ Gitaly::UserDeleteBranchRequest.new(
+ repository: repository.gitaly_repository,
+ branch_name: branch_name,
+ user: gitaly_user
+ )
+ end
+ let(:response) { Gitaly::UserDeleteBranchResponse.new }
+
+ subject { client.user_delete_branch(branch_name, user) }
+
+ it 'sends a user_delete_branch message' do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_delete_branch).with(request, kind_of(Hash))
+ .and_return(response)
+
+ subject
+ end
+
+ context "when pre_receive_error is present" do
+ let(:response) do
+ Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "something failed")
+ end
+
+ it "throws a PreReceive exception" do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_delete_branch).with(request, kind_of(Hash))
+ .and_return(response)
+
+ expect { subject }.to raise_error(
+ Gitlab::Git::HooksService::PreReceiveError, "something failed")
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index baf8f6644bf..8026fba9f0a 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -22,5 +22,12 @@ describe Gitlab::SQL::Union do
expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error
expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}")
end
+
+ it 'uses UNION ALL when removing duplicates is disabled' do
+ union = described_class
+ .new([relation_1, relation_2], remove_duplicates: false)
+
+ expect(union.to_sql).to include('UNION ALL')
+ end
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 8eabc4ca72f..81c2057e175 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -155,5 +155,15 @@ describe Key, :mailer do
it 'strips white spaces' do
expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key)
end
+
+ it 'invalidates the public_key attribute' do
+ key = build(:key)
+
+ original = key.public_key
+ key.key = valid_key
+
+ expect(original.key_text).not_to be_nil
+ expect(key.public_key.key_text).to eq(valid_key)
+ end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index d80d5657c42..188a0a98ec3 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -791,6 +791,49 @@ describe MergeRequest do
end
end
+ describe '#has_ci?' do
+ let(:merge_request) { build_stubbed(:merge_request) }
+
+ context 'has ci' do
+ it 'returns true if MR has head_pipeline_id and commits' do
+ allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil }
+ allow(merge_request).to receive(:head_pipeline_id) { double }
+ allow(merge_request).to receive(:has_no_commits?) { false }
+
+ expect(merge_request.has_ci?).to be(true)
+ end
+
+ it 'returns true if MR has any pipeline and commits' do
+ allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil }
+ allow(merge_request).to receive(:head_pipeline_id) { nil }
+ allow(merge_request).to receive(:has_no_commits?) { false }
+ allow(merge_request).to receive(:all_pipelines) { [double] }
+
+ expect(merge_request.has_ci?).to be(true)
+ end
+
+ it 'returns true if MR has CI service and commits' do
+ allow(merge_request).to receive_message_chain(:source_project, :ci_service) { double }
+ allow(merge_request).to receive(:head_pipeline_id) { nil }
+ allow(merge_request).to receive(:has_no_commits?) { false }
+ allow(merge_request).to receive(:all_pipelines) { [] }
+
+ expect(merge_request.has_ci?).to be(true)
+ end
+ end
+
+ context 'has no ci' do
+ it 'returns false if MR has no CI service nor pipeline, and no commits' do
+ allow(merge_request).to receive_message_chain(:source_project, :ci_service) { nil }
+ allow(merge_request).to receive(:head_pipeline_id) { nil }
+ allow(merge_request).to receive(:all_pipelines) { [] }
+ allow(merge_request).to receive(:has_no_commits?) { true }
+
+ expect(merge_request.has_ci?).to be(false)
+ end
+ end
+ end
+
describe '#all_pipelines' do
shared_examples 'returning pipelines with proper ordering' do
let!(:all_pipelines) do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 8a4dcdc311e..7156c1b7aa8 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -636,18 +636,18 @@ describe Repository do
describe '#fetch_ref' do
describe 'when storage is broken', broken_storage: true do
it 'should raise a storage error' do
- path = broken_repository.path_to_repo
-
- expect_to_raise_storage_error { broken_repository.fetch_ref(path, '1', '2') }
+ expect_to_raise_storage_error do
+ broken_repository.fetch_ref(broken_repository, source_ref: '1', target_ref: '2')
+ end
end
end
end
describe '#create_ref' do
- it 'redirects the call to fetch_ref' do
+ it 'redirects the call to write_ref' do
ref, ref_path = '1', '2'
- expect(repository).to receive(:fetch_ref).with(repository.path_to_repo, ref, ref_path)
+ expect(repository.raw_repository).to receive(:write_ref).with(ref_path, ref)
repository.create_ref(ref, ref_path)
end
@@ -901,47 +901,6 @@ describe Repository do
end
end
- describe '#rm_branch' do
- let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
- let(:blank_sha) { '0000000000000000000000000000000000000000' }
-
- context 'when pre hooks were successful' do
- it 'runs without errors' do
- expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
- .with(git_user, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')
-
- expect { repository.rm_branch(user, 'feature') }.not_to raise_error
- end
-
- it 'deletes the branch' do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
-
- expect { repository.rm_branch(user, 'feature') }.not_to raise_error
-
- expect(repository.find_branch('feature')).to be_nil
- end
- end
-
- context 'when pre hooks failed' do
- it 'gets an error' do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
-
- expect do
- repository.rm_branch(user, 'feature')
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
- end
-
- it 'does not delete the branch' do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
-
- expect do
- repository.rm_branch(user, 'feature')
- end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
- expect(repository.find_branch('feature')).not_to be_nil
- end
- end
- end
-
describe '#update_branch_with_hooks' do
let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev
@@ -1744,13 +1703,75 @@ describe Repository do
end
describe '#rm_branch' do
- let(:user) { create(:user) }
+ shared_examples "user deleting a branch" do
+ it 'removes a branch' do
+ expect(repository).to receive(:before_remove_branch)
+ expect(repository).to receive(:after_remove_branch)
- it 'removes a branch' do
- expect(repository).to receive(:before_remove_branch)
- expect(repository).to receive(:after_remove_branch)
+ repository.rm_branch(user, 'feature')
+ end
+ end
- repository.rm_branch(user, 'feature')
+ context 'with gitaly enabled' do
+ it_behaves_like "user deleting a branch"
+
+ context 'when pre hooks failed' do
+ before do
+ allow_any_instance_of(Gitlab::GitalyClient::OperationService)
+ .to receive(:user_delete_branch).and_raise(Gitlab::Git::HooksService::PreReceiveError)
+ end
+
+ it 'gets an error and does not delete the branch' do
+ expect do
+ repository.rm_branch(user, 'feature')
+ end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+
+ expect(repository.find_branch('feature')).not_to be_nil
+ end
+ end
+ end
+
+ context 'with gitaly disabled', skip_gitaly_mock: true do
+ it_behaves_like "user deleting a branch"
+
+ let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
+ let(:blank_sha) { '0000000000000000000000000000000000000000' }
+
+ context 'when pre hooks were successful' do
+ it 'runs without errors' do
+ expect_any_instance_of(Gitlab::Git::HooksService).to receive(:execute)
+ .with(git_user, repository.raw_repository, old_rev, blank_sha, 'refs/heads/feature')
+
+ expect { repository.rm_branch(user, 'feature') }.not_to raise_error
+ end
+
+ it 'deletes the branch' do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
+
+ expect { repository.rm_branch(user, 'feature') }.not_to raise_error
+
+ expect(repository.find_branch('feature')).to be_nil
+ end
+ end
+
+ context 'when pre hooks failed' do
+ it 'gets an error' do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
+
+ expect do
+ repository.rm_branch(user, 'feature')
+ end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ end
+
+ it 'does not delete the branch' do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
+
+ expect do
+ repository.rm_branch(user, 'feature')
+ end.to raise_error(Gitlab::Git::HooksService::PreReceiveError)
+ expect(repository.find_branch('feature')).not_to be_nil
+ end
+ end
end
end
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index 2187be0190d..5e114434a67 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -300,6 +300,10 @@ describe MergeRequestPresenter do
described_class.new(resource, current_user: user).remove_wip_path
end
+ before do
+ allow(resource).to receive(:work_in_progress?).and_return(true)
+ end
+
context 'when merge request enabled and has permission' do
it 'has remove_wip_path' do
allow(project).to receive(:merge_requests_enabled?) { true }
diff --git a/spec/serializers/container_repository_entity_spec.rb b/spec/serializers/container_repository_entity_spec.rb
new file mode 100644
index 00000000000..c589cd18f77
--- /dev/null
+++ b/spec/serializers/container_repository_entity_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe ContainerRepositoryEntity do
+ let(:entity) do
+ described_class.new(repository, request: request)
+ end
+
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+ set(:repository) { create(:container_repository, project: project) }
+
+ let(:request) { double('request') }
+
+ subject { entity.as_json }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ allow(request).to receive(:project).and_return(project)
+ allow(request).to receive(:current_user).and_return(user)
+ end
+
+ it 'exposes required informations' do
+ expect(subject).to include(:id, :path, :location, :tags_path)
+ end
+
+ context 'when user can manage repositories' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'exposes destroy_path' do
+ expect(subject).to include(:destroy_path)
+ end
+ end
+
+ context 'when user cannot manage repositories' do
+ it 'does not expose destroy_path' do
+ expect(subject).not_to include(:destroy_path)
+ end
+ end
+end
diff --git a/spec/serializers/container_tag_entity_spec.rb b/spec/serializers/container_tag_entity_spec.rb
new file mode 100644
index 00000000000..6dcc5204516
--- /dev/null
+++ b/spec/serializers/container_tag_entity_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe ContainerTagEntity do
+ let(:entity) do
+ described_class.new(tag, request: request)
+ end
+
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+ set(:repository) { create(:container_repository, name: 'image', project: project) }
+
+ let(:request) { double('request') }
+ let(:tag) { repository.tag('test') }
+
+ subject { entity.as_json }
+
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: /image/, tags: %w[test])
+ allow(request).to receive(:project).and_return(project)
+ allow(request).to receive(:current_user).and_return(user)
+ end
+
+ it 'exposes required informations' do
+ expect(subject).to include(:name, :location, :revision, :total_size, :created_at)
+ end
+
+ context 'when user can manage repositories' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'exposes destroy_path' do
+ expect(subject).to include(:destroy_path)
+ end
+ end
+
+ context 'when user cannot manage repositories' do
+ it 'does not expose destroy_path' do
+ expect(subject).not_to include(:destroy_path)
+ end
+ end
+end
diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb
index 4288955ddbc..4aeb593da44 100644
--- a/spec/serializers/merge_request_entity_spec.rb
+++ b/spec/serializers/merge_request_entity_spec.rb
@@ -11,16 +11,6 @@ describe MergeRequestEntity do
described_class.new(resource, request: request).as_json
end
- it 'includes author' do
- req = double('request')
-
- author_payload = UserEntity
- .represent(resource.author, request: req)
- .as_json
-
- expect(subject[:author]).to eq(author_payload)
- end
-
it 'includes pipeline' do
req = double('request', current_user: user)
pipeline = build_stubbed(:ci_pipeline)
diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb
index 78a2ff73746..5f22d886910 100644
--- a/spec/support/stub_gitlab_calls.rb
+++ b/spec/support/stub_gitlab_calls.rb
@@ -39,11 +39,11 @@ module StubGitlabCalls
.and_return({ 'tags' => tags })
allow_any_instance_of(ContainerRegistry::Client)
- .to receive(:repository_manifest).with(repository)
+ .to receive(:repository_manifest).with(repository, anything)
.and_return(stub_container_registry_tag_manifest)
allow_any_instance_of(ContainerRegistry::Client)
- .to receive(:blob).with(repository)
+ .to receive(:blob).with(repository, anything, 'application/octet-stream')
.and_return(stub_container_registry_blob)
end
diff --git a/spec/views/projects/registry/repositories/index.html.haml_spec.rb b/spec/views/projects/registry/repositories/index.html.haml_spec.rb
deleted file mode 100644
index cf0aa44a4a2..00000000000
--- a/spec/views/projects/registry/repositories/index.html.haml_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'spec_helper'
-
-describe 'projects/registry/repositories/index' do
- let(:group) { create(:group, path: 'group') }
- let(:project) { create(:project, group: group, path: 'test') }
-
- let(:repository) do
- create(:container_repository, project: project, name: 'image')
- end
-
- before do
- stub_container_registry_config(enabled: true,
- host_port: 'registry.gitlab',
- api_url: 'http://registry.gitlab')
-
- stub_container_registry_tags(repository: :any, tags: [:latest])
-
- assign(:project, project)
- assign(:images, [repository])
-
- allow(view).to receive(:can?).and_return(true)
- end
-
- it 'contains container repository path' do
- render
-
- expect(rendered).to have_content 'group/test/image'
- end
-
- it 'contains attribute for copying tag location into clipboard' do
- render
-
- expect(rendered).to have_css 'button[data-clipboard-text="docker pull ' \
- 'registry.gitlab/group/test/image:latest"]'
- end
-end