Commit 8529f1f5 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

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

CE upstream - 2018-07-12 12:22 UTC

See merge request gitlab-org/gitlab-ee!6481
parents 12c6bfc0 697bcf1c
...@@ -23,7 +23,6 @@ class ProjectWiki ...@@ -23,7 +23,6 @@ class ProjectWiki
@user = user @user = user
end end
delegate :empty?, to: :pages
delegate :repository_storage, :hashed_storage?, to: :project delegate :repository_storage, :hashed_storage?, to: :project
def path def path
...@@ -82,6 +81,10 @@ class ProjectWiki ...@@ -82,6 +81,10 @@ class ProjectWiki
!!find_page('home') !!find_page('home')
end end
def empty?
pages(limit: 1).empty?
end
# Returns an Array of Gitlab WikiPage instances or an # Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages. # empty Array if this Wiki has no pages.
def pages(limit: nil) def pages(limit: nil)
......
.explore-title.text-center .explore-title.text-center
%h2 %h2
Explore GitLab = _("Explore GitLab")
%p.lead %p.lead
Discover projects, groups and snippets. Share your projects with others = _("Discover projects, groups and snippets. Share your projects with others")
%br %br
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%ul.nav-links.nav.nav-tabs %ul.nav-links.nav.nav-tabs
= nav_link(page: explore_groups_path) do = nav_link(page: explore_groups_path) do
= link_to explore_groups_path do = link_to explore_groups_path do
Explore Groups = _("Explore Groups")
.nav-controls .nav-controls
= render 'shared/groups/search_form' = render 'shared/groups/search_form'
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
- @hide_top_links = true - @hide_top_links = true
- page_title "Groups" - page_title _("Groups")
- header_title "Groups", dashboard_groups_path - header_title _("Groups"), dashboard_groups_path
- if current_user - if current_user
= render 'dashboard/groups_head' = render 'dashboard/groups_head'
...@@ -10,14 +10,14 @@ ...@@ -10,14 +10,14 @@
- if cookies[:explore_groups_landing_dismissed] != 'true' - if cookies[:explore_groups_landing_dismissed] != 'true'
.explore-groups.landing.content-block.js-explore-groups-landing.hide .explore-groups.landing.content-block.js-explore-groups-landing.hide
%button.dismiss-button{ type: 'button', 'aria-label' => 'Dismiss' }= icon('times') %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= icon('times')
.svg-container .svg-container
= custom_icon('icon_explore_groups_splash') = custom_icon('icon_explore_groups_splash')
.inner-content .inner-content
%p Below you will find all the groups that are public. %p= _("Below you will find all the groups that are public.")
%p You can easily contribute to them by requesting to join these groups. %p= _("You can easily contribute to them by requesting to join these groups.")
- if params[:filter].blank? && @groups.empty? - if params[:filter].blank? && @groups.empty?
.nothing-here-block No public groups .nothing-here-block= _("No public groups")
- else - else
= render 'groups' = render 'groups'
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
.dropdown .dropdown
%button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' } %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' }
= icon('globe') = icon('globe')
%span.light Visibility: %span.light= _("Visibility:")
- if params[:visibility_level].present? - if params[:visibility_level].present?
= visibility_level_label(params[:visibility_level].to_i) = visibility_level_label(params[:visibility_level].to_i)
- else - else
Any = _('Any')
= icon('chevron-down') = icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right %ul.dropdown-menu.dropdown-menu-right
%li %li
= link_to filter_projects_path(visibility_level: nil) do = link_to filter_projects_path(visibility_level: nil) do
Any = _('Any')
- Gitlab::VisibilityLevel.values.each do |level| - Gitlab::VisibilityLevel.values.each do |level|
%li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' } %li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' }
= link_to filter_projects_path(visibility_level: level) do = link_to filter_projects_path(visibility_level: level) do
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
%ul.nav-links.nav.nav-tabs %ul.nav-links.nav.nav-tabs
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do = nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do = link_to trending_explore_projects_path do
Trending = _('Trending')
= nav_link(page: starred_explore_projects_path) do = nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do = link_to starred_explore_projects_path do
Most stars = _('Most stars')
= nav_link(page: explore_projects_path) do = nav_link(page: explore_projects_path) do
= link_to explore_projects_path do = link_to explore_projects_path do
All = _('All')
.nav-controls .nav-controls
- unless current_user - unless current_user
......
- @hide_top_links = true - @hide_top_links = true
- page_title "Projects" - page_title _("Projects")
- header_title "Projects", dashboard_projects_path - header_title _("Projects"), dashboard_projects_path
- if current_user - if current_user
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
......
- @hide_top_links = true - @hide_top_links = true
- page_title "Projects" - page_title _("Projects")
- header_title "Projects", dashboard_projects_path - header_title _("Projects"), dashboard_projects_path
- if current_user - if current_user
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
......
- @hide_top_links = true - @hide_top_links = true
- page_title "Projects" - page_title _("Projects")
- header_title "Projects", dashboard_projects_path - header_title _("Projects"), dashboard_projects_path
- if current_user - if current_user
= render 'dashboard/projects_head' = render 'dashboard/projects_head'
......
---
title: Fix updated_at if created_at is set for Note API
merge_request:
author:
type: fixed
---
title: Use appropriate timeout on Gitaly server info checks, avoid error on timeout
merge_request: 20552
author:
type: fixed
---
title: Optimize ProjectWiki#empty? check
merge_request: 20573
author:
type: performance
...@@ -30,7 +30,7 @@ class Gitlab::Seeder::Environments ...@@ -30,7 +30,7 @@ class Gitlab::Seeder::Environments
def create_merge_request_review_deployments! def create_merge_request_review_deployments!
@project @project
.merge_requests .merge_requests
.select { |mr| mr.source_branch.match(/[^a-zA-Z0-9]+/) } .select { |mr| mr.source_branch.match?(/[a-zA-Z0-9]+/) }
.sample(4) .sample(4)
.each do |merge_request| .each do |merge_request|
next unless merge_request.diff_head_sha next unless merge_request.diff_head_sha
......
...@@ -394,7 +394,7 @@ For example, `/` is represented by `%2F`: ...@@ -394,7 +394,7 @@ For example, `/` is represented by `%2F`:
GET /api/v4/projects/diaspora%2Fdiaspora GET /api/v4/projects/diaspora%2Fdiaspora
``` ```
## Branches & tags name encoding ## Branches and tags name encoding
If your branch or tag contains a `/`, make sure the branch/tag name is If your branch or tag contains a `/`, make sure the branch/tag name is
URL-encoded. URL-encoded.
...@@ -405,6 +405,36 @@ For example, `/` is represented by `%2F`: ...@@ -405,6 +405,36 @@ For example, `/` is represented by `%2F`:
GET /api/v4/projects/1/branches/my%2Fbranch/commits GET /api/v4/projects/1/branches/my%2Fbranch/commits
``` ```
## Encoding API parameters of `array` and `hash` types
When making an API call with parameters of type `array` and/or `hash`, the parameters may be
specified as shown below.
### `array`
`import_sources` is a parameter of type `array`:
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
-d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \
"https://gitlab.example.com/api/v4/some_endpoint
```
### `hash`
`override_params` is a parameter of type `hash`:
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \
--form "namespace=email" \
--form "path=impapi" \
--form "file=@/path/to/somefile.txt"
--form "override_params[visibility]=private" \
--form "override_params[some_other_param]=some_value" \
https://gitlab.example.com/api/v4/projects/import
```
## `id` vs `iid` ## `id` vs `iid`
When you work with the API, you may notice two similar fields in API entities: When you work with the API, you may notice two similar fields in API entities:
......
...@@ -218,6 +218,7 @@ Parameters: ...@@ -218,6 +218,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `snippet_id` (required) - The ID of a snippet - `snippet_id` (required) - The ID of a snippet
- `body` (required) - The content of a note - `body` (required) - The content of a note
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
```bash ```bash
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
...@@ -340,6 +341,7 @@ Parameters: ...@@ -340,6 +341,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `merge_request_iid` (required) - The IID of a merge request - `merge_request_iid` (required) - The IID of a merge request
- `body` (required) - The content of a note - `body` (required) - The content of a note
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
### Modify existing merge request note ### Modify existing merge request note
......
...@@ -28,8 +28,11 @@ POST /projects/:id/export ...@@ -28,8 +28,11 @@ POST /projects/:id/export
| `upload[url]` | string | yes | The URL to upload the project | | `upload[url]` | string | yes | The URL to upload the project |
| `upload[http_method]` | string | no | The HTTP method to upload the exported project. Only `PUT` and `POST` methods allowed. Default is `PUT` | | `upload[http_method]` | string | no | The HTTP method to upload the exported project. Only `PUT` and `POST` methods allowed. Default is `PUT` |
```console ```console
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export --data "description=FooBar&upload[http_method]=PUT&upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd" curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export \
--data "upload[http_method]=PUT" \
--data-urlencode "upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd"
``` ```
```json ```json
...@@ -125,6 +128,29 @@ by `@`. For example: ...@@ -125,6 +128,29 @@ by `@`. For example:
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import
``` ```
cURL doesn't support posting a file from a remote server. Importing a project from a remote server can be accomplished through something like the following:
```python
import requests
import urllib
import json
import sys
s3_file = urllib.urlopen(presigned_url)
url = 'https://gitlab.example.com/api/v4/projects/import'
files = {'file': s3_file}
data = {
"path": "example-project",
"namespace": "example-group"
}
headers = {
'Private-Token': "9koXpg98eAheJpvBs5tK"
}
requests.post(url, headers=headers, data=data, files=files)
```
```json ```json
{ {
"id": 1, "id": 1,
......
...@@ -2,27 +2,24 @@ ...@@ -2,27 +2,24 @@
To get started with Vue, read through [their documentation][vue-docs]. To get started with Vue, read through [their documentation][vue-docs].
## Vue architecture ## Examples
All new features built with Vue.js must follow a [Flux architecture][flux]. What is described in the following sections can be found in these examples:
The main goal we are trying to achieve is to have only one data flow and only one data entry.
In order to achieve this goal, you can either use [vuex](#vuex) or use the [store pattern][state-management], explained below:
Each Vue bundle needs a Store - where we keep all the data -, a Service - that we use to communicate with the server - and a main Vue component. - web ide: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/ide/stores
- security products: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports
- registry: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/registry/stores
Think of the Main Vue Component as the entry point of your application. This is the only smart ## Vue architecture
component that should exist in each Vue feature.
This component is responsible for:
1. Calling the Service to get data from the server
1. Calling the Store to store the data received
1. Mounting all the other components
![Vue Architecture](img/vue_arch.png) All new features built with Vue.js must follow a [Flux architecture][flux].
The main goal we are trying to achieve is to have only one data flow and only one data entry.
In order to achieve this goal we use [vuex](#vuex).
You can also read about this architecture in vue docs about [state management][state-management] You can also read about this architecture in vue docs about [state management][state-management]
and about [one way data flow][one-way-data-flow]. and about [one way data flow][one-way-data-flow].
### Components, Stores and Services ### Components and Store
In some features implemented with Vue.js, like the [issue board][issue-boards] In some features implemented with Vue.js, like the [issue board][issue-boards]
or [environments table][environments-table] or [environments table][environments-table]
...@@ -33,10 +30,8 @@ new_feature ...@@ -33,10 +30,8 @@ new_feature
├── components ├── components
│ └── component.vue │ └── component.vue
│ └── ... │ └── ...
├── stores ├── store
│ └── new_feature_store.js │ └── new_feature_store.js
├── services # only when not using vuex
│ └── new_feature_service.js
├── index.js ├── index.js
``` ```
_For consistency purposes, we recommend you to follow the same structure._ _For consistency purposes, we recommend you to follow the same structure._
...@@ -125,217 +120,6 @@ You can read more about components in Vue.js site, [Component System][component- ...@@ -125,217 +120,6 @@ You can read more about components in Vue.js site, [Component System][component-
#### Vuex #### Vuex
Check this [page](vuex.md) for more details. Check this [page](vuex.md) for more details.
#### Flux like state management
The Store is a class that allows us to manage the state in a single
source of truth. It is not aware of the service or the components.
The concept we are trying to follow is better explained by Vue documentation
itself, please read this guide: [State Management][state-management]
### A folder for the Service
**If you are using Vuex you won't need this step**
The Service is a class used only to communicate with the server.
It does not store or manipulate any data. It is not aware of the store or the components.
We use [axios][axios] to communicate with the server.
Refer to [axios](axios.md) for more details.
Axios instance should only be imported in the service file.
```javascript
import axios from '~/lib/utils/axios_utils';
```
### End Result
The following example shows an application:
```javascript
// store.js
export default class Store {
/**
* This is where we will iniatialize the state of our data.
* Usually in a small SPA you don't need any options when starting the store.
* In that case you do need guarantee it's an Object and it's documented.
*
* @param {Object} options
*/
constructor(options) {
this.options = options;
// Create a state object to handle all our data in the same place
this.todos = [];
}
setTodos(todos = []) {
this.todos = todos;
}
addTodo(todo) {
this.todos.push(todo);
}
removeTodo(todoID) {
const state = this.todos;
const newState = state.filter((element) => {element.id !== todoID});
this.todos = newState;
}
}
// service.js
import axios from '~/lib/utils/axios_utils'
export default class Service {
constructor(options) {
this.todos = axios.create({
baseURL: endpoint.todosEndpoint
});
}
getTodos() {
return this.todos.get();
}
addTodo(todo) {
return this.todos.put(todo);
}
}
// todo_component.vue
<script>
export default {
props: {
data: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div>
<h1>
Title: {{data.title}}
</h1>
<p>
{{data.text}}
</p>
</div>
</template>
// todos_main_component.vue
<script>
import Store from 'store';
import Service from 'service';
import TodoComponent from 'todoComponent';
export default {
components: {
todo: TodoComponent,
},
/**
* Although most data belongs in the store, each component it's own state.
* We want to show a loading spinner while we are fetching the todos, this state belong
* in the component.
*
* We need to access the store methods through all methods of our component.
* We need to access the state of our store.
*/
data() {
const store = new Store();
return {
isLoading: false,
store: store,
todos: store.todos,
};
},
created() {
this.service = new Service('/todos');
this.getTodos();
},
methods: {
getTodos() {
this.isLoading = true;
this.service
.getTodos()
.then(response => {
this.store.setTodos(response);
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
// Show an error
});
},
addTodo(event) {
this.service
.addTodo({
title: 'New entry',
text: `You clicked on ${event.target.tagName}`,
})
.then(response => {
this.store.addTodo(response);
})
.catch(() => {
// Show an error
});
},
},
};
</script>
<template>
<div class="container">
<div v-if="isLoading">
<i
class="fa fa-spin fa-spinner"
aria-hidden="true" />
</div>
<div
v-if="!isLoading"
class="js-todo-list">
<template v-for='todo in todos'>
<todo :data="todo" />
</template>
<button
@click="addTodo"
class="js-add-todo">
Add Todo
</button>
</div>
<div>
</template>
// index.js
import todoComponent from 'todos_main_component.vue';
new Vue({
el: '.js-todo-app',
components: {
todoComponent,
},
render: createElement => createElement('todo-component' {
props: {
someProp: [],
}
}),
});
```
The [issue boards service][issue-boards-service]
is a good example of this pattern.
## Style guide ## Style guide
Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js) Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js)
...@@ -446,6 +230,5 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e ...@@ -446,6 +230,5 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e
[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch [state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow [one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
[vue-test]: https://vuejs.org/v2/guide/unit-testing.html [vue-test]: https://vuejs.org/v2/guide/unit-testing.html
[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
[flux]: https://facebook.github.io/flux [flux]: https://facebook.github.io/flux
[axios]: https://github.com/axios/axios [axios]: https://github.com/axios/axios
...@@ -62,7 +62,7 @@ Click on `Add Kubernetes cluster`, the cluster is now connected to GitLab. At th ...@@ -62,7 +62,7 @@ Click on `Add Kubernetes cluster`, the cluster is now connected to GitLab. At th
If you would like to utilize your own CI/CD scripts to deploy to the cluster, you can stop here. If you would like to utilize your own CI/CD scripts to deploy to the cluster, you can stop here.
## Disable Role Based-Access Control (RBAC) ## Disable Role-Based Access Control (RBAC)
Presently, Auto DevOps and one-click app installs do not support [Kubernetes role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/). Support is [being worked on](https://gitlab.com/groups/gitlab-org/-/epics/136), but in the interim RBAC must be disabled to utilize for these features. Presently, Auto DevOps and one-click app installs do not support [Kubernetes role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/). Support is [being worked on](https://gitlab.com/groups/gitlab-org/-/epics/136), but in the interim RBAC must be disabled to utilize for these features.
......
...@@ -99,6 +99,8 @@ module API ...@@ -99,6 +99,8 @@ module API
current_user.admin? || parent.owned_by?(current_user) current_user.admin? || parent.owned_by?(current_user)
end end
opts[:updated_at] = opts[:created_at] if opts[:created_at]
project = parent if parent.is_a?(Project) project = parent if parent.is_a?(Project)
::Notes::CreateService.new(project, current_user, opts).execute ::Notes::CreateService.new(project, current_user, opts).execute
end end
......
...@@ -50,7 +50,7 @@ module Gitaly ...@@ -50,7 +50,7 @@ module Gitaly
@info ||= @info ||=
begin begin
Gitlab::GitalyClient::ServerService.new(@storage).info Gitlab::GitalyClient::ServerService.new(@storage).info
rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded rescue GRPC::Unavailable, GRPC::DeadlineExceeded
# This will show the server as being out of date # This will show the server as being out of date
Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: []) Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: [])
end end
......
...@@ -86,9 +86,6 @@ module Gitlab ...@@ -86,9 +86,6 @@ module Gitlab
# Relative path of repo # Relative path of repo
attr_reader :relative_path attr_reader :relative_path
# Rugged repo object
attr_reader :rugged
attr_reader :gitlab_projects, :storage, :gl_repository, :relative_path attr_reader :gitlab_projects, :storage, :gl_repository, :relative_path
# This initializer method is only used on the client side (gitlab-ce). # This initializer method is only used on the client side (gitlab-ce).
...@@ -112,8 +109,9 @@ module Gitlab ...@@ -112,8 +109,9 @@ module Gitlab
[storage, relative_path] == [other.storage, other.relative_path] [storage, relative_path] == [other.storage, other.relative_path]
end end
# This method will be removed when Gitaly reaches v1.1.
def path def path
@path ||= File.join( File.join(
Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path
) )
end end
...@@ -127,8 +125,9 @@ module Gitlab ...@@ -127,8 +125,9 @@ module Gitlab
raise Gitlab::Git::CommandError.new(e.message) raise Gitlab::Git::CommandError.new(e.message)
end end
# This method will be removed when Gitaly reaches v1.1.
def rugged def rugged
@rugged ||= circuit_breaker.perform do circuit_breaker.perform do
Rugged::Repository.new(path, alternates: alternate_object_directories) Rugged::Repository.new(path, alternates: alternate_object_directories)
end end
rescue Rugged::RepositoryError, Rugged::OSError rescue Rugged::RepositoryError, Rugged::OSError
...@@ -713,12 +712,6 @@ module Gitlab ...@@ -713,12 +712,6 @@ module Gitlab
Gitlab::Git.committer_hash(email: user.email, name: user.name) Gitlab::Git.committer_hash(email: user.email, name: user.name)
end end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
# Delete the specified branch from the repository # Delete the specified branch from the repository
def delete_branch(branch_name) def delete_branch(branch_name)
gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
...@@ -1758,6 +1751,12 @@ module Gitlab ...@@ -1758,6 +1751,12 @@ module Gitlab
def sha_from_ref(ref) def sha_from_ref(ref)
rev_parse_target(ref).oid rev_parse_target(ref).oid
end end
def create_commit(params = {})
params[:message].delete!("\r")
Rugged::Commit.create(rugged, params)
end
end end
end end
end end
...@@ -12,35 +12,12 @@ module Gitlab ...@@ -12,35 +12,12 @@ module Gitlab
end end
# This method returns an array of new commit references # This method returns an array of new commit references
def new_refs # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233
repository.rev_list(including: newrev, excluding: :all).split("\n")
end
# Finds newly added objects
# Returns an array of shas
# #
# Can skip objects which do not have a path using required_path: true def new_refs
# This skips commit objects and root trees, which might not be needed when Gitlab::GitalyClient::StorageSettings.allow_disk_access do
# looking for blobs repository.rev_list(including: newrev, excluding: :all).split("\n")
# end
# When given a block it will yield objects as a lazy enumerator so
# the caller can limit work done instead of processing megabytes of data
def new_objects(options: [], require_path: nil, not_in: nil, &lazy_block)
opts = {
including: newrev,
options: options,
excluding: not_in.nil? ? :all : not_in,
require_path: require_path
}
get_objects(opts, &lazy_block)
end
def all_objects(options: [], require_path: nil, &lazy_block)
get_objects(including: :all,
options: options,
require_path: require_path,
&lazy_block)
end end
private private
......
...@@ -9,7 +9,7 @@ module Gitlab ...@@ -9,7 +9,7 @@ module Gitlab
end end
def info def info
GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new) GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new, timeout: GitalyClient.fast_timeout)
end end
end end
end end
......
...@@ -557,6 +557,9 @@ msgstr "" ...@@ -557,6 +557,9 @@ msgstr ""
msgid "An error occurred. Please try again." msgid "An error occurred. Please try again."
msgstr "" msgstr ""
msgid "Any"
msgstr ""
msgid "Any Label" msgid "Any Label"
msgstr "" msgstr ""
...@@ -803,6 +806,9 @@ msgstr "" ...@@ -803,6 +806,9 @@ msgstr ""
msgid "Below are examples of regex for existing tools:" msgid "Below are examples of regex for existing tools:"
msgstr "" msgstr ""
msgid "Below you will find all the groups that are public."
msgstr ""
msgid "Billing" msgid "Billing"
msgstr "" msgstr ""
...@@ -2296,6 +2302,12 @@ msgstr "" ...@@ -2296,6 +2302,12 @@ msgstr ""
msgid "Discover GitLab Geo." msgid "Discover GitLab Geo."
msgstr "" msgstr ""
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr ""
msgid "Dismiss"
msgstr ""
msgid "Dismiss Cycle Analytics introduction box" msgid "Dismiss Cycle Analytics introduction box"
msgstr "" msgstr ""
...@@ -2620,6 +2632,12 @@ msgstr "" ...@@ -2620,6 +2632,12 @@ msgstr ""
msgid "Expand sidebar" msgid "Expand sidebar"
msgstr "" msgstr ""
msgid "Explore GitLab"
msgstr ""
msgid "Explore Groups"
msgstr ""
msgid "Explore groups" msgid "Explore groups"
msgstr "" msgstr ""
...@@ -3105,6 +3123,9 @@ msgstr "" ...@@ -3105,6 +3123,9 @@ msgstr ""
msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}" msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}"
msgstr "" msgstr ""
msgid "Groups"
msgstr ""
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}." msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr "" msgstr ""
...@@ -3831,6 +3852,9 @@ msgstr "" ...@@ -3831,6 +3852,9 @@ msgstr ""
msgid "More information is available|here" msgid "More information is available|here"
msgstr "" msgstr ""
msgid "Most stars"
msgstr ""
msgid "Move" msgid "Move"
msgstr "" msgstr ""
...@@ -3974,6 +3998,9 @@ msgstr "" ...@@ -3974,6 +3998,9 @@ msgstr ""
msgid "No messages were logged" msgid "No messages were logged"
msgstr "" msgstr ""
msgid "No public groups"
msgstr ""
msgid "No pushes for the selected time period." msgid "No pushes for the selected time period."
msgstr "" msgstr ""
...@@ -6087,6 +6114,9 @@ msgstr "" ...@@ -6087,6 +6114,9 @@ msgstr ""
msgid "Track time with quick actions" msgid "Track time with quick actions"
msgstr "" msgstr ""
msgid "Trending"
msgstr ""
msgid "Trigger this manual action" msgid "Trigger this manual action"
msgstr "" msgstr ""
...@@ -6266,6 +6296,9 @@ msgstr "" ...@@ -6266,6 +6296,9 @@ msgstr ""
msgid "Visibility and access controls" msgid "Visibility and access controls"
msgstr "" msgstr ""
msgid "Visibility:"
msgstr ""
msgid "VisibilityLevel|Internal" msgid "VisibilityLevel|Internal"
msgstr "" msgstr ""
...@@ -6491,6 +6524,9 @@ msgstr "" ...@@ -6491,6 +6524,9 @@ msgstr ""
msgid "You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}" msgid "You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}"
msgstr "" msgstr ""
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr "" msgstr ""
......
...@@ -55,7 +55,7 @@ Since the arguments would be passed to `rspec`, you could use all `rspec` ...@@ -55,7 +55,7 @@ Since the arguments would be passed to `rspec`, you could use all `rspec`
options there. For example, passing `--backtrace` and also line number: options there. For example, passing `--backtrace` and also line number:
``` ```
bin/qa Test::Instance http://localhost qa/specs/features/login/standard_spec.rb:3 --backtrace bin/qa Test::Instance http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace
``` ```
### Overriding the authenticated user ### Overriding the authenticated user
......
...@@ -41,14 +41,17 @@ module QA ...@@ -41,14 +41,17 @@ module QA
autoload :Project, 'qa/factory/resource/project' autoload :Project, 'qa/factory/resource/project'
autoload :MergeRequest, 'qa/factory/resource/merge_request' autoload :MergeRequest, 'qa/factory/resource/merge_request'
autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github' autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github'
autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork'
autoload :DeployKey, 'qa/factory/resource/deploy_key' autoload :DeployKey, 'qa/factory/resource/deploy_key'
autoload :Branch, 'qa/factory/resource/branch' autoload :Branch, 'qa/factory/resource/branch'
autoload :SecretVariable, 'qa/factory/resource/secret_variable' autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner' autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token' autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster' autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster'
autoload :User, 'qa/factory/resource/user'
autoload :ProjectMilestone, 'qa/factory/resource/project_milestone' autoload :ProjectMilestone, 'qa/factory/resource/project_milestone'
autoload :Wiki, 'qa/factory/resource/wiki' autoload :Wiki, 'qa/factory/resource/wiki'
autoload :Fork, 'qa/factory/resource/fork'
end end
module Repository module Repository
...@@ -107,6 +110,7 @@ module QA ...@@ -107,6 +110,7 @@ module QA
module Main module Main
autoload :Login, 'qa/page/main/login' autoload :Login, 'qa/page/main/login'
autoload :OAuth, 'qa/page/main/oauth' autoload :OAuth, 'qa/page/main/oauth'
autoload :SignUp, 'qa/page/main/sign_up'
end end
module Settings module Settings
...@@ -167,6 +171,10 @@ module QA ...@@ -167,6 +171,10 @@ module QA
autoload :Index, 'qa/page/project/issue/index' autoload :Index, 'qa/page/project/issue/index'
end end
module Fork
autoload :New, 'qa/page/project/fork/new'
end
module Milestone module Milestone
autoload :New, 'qa/page/project/milestone/new' autoload :New, 'qa/page/project/milestone/new'
autoload :Index, 'qa/page/project/milestone/index' autoload :Index, 'qa/page/project/milestone/index'
...@@ -200,6 +208,10 @@ module QA ...@@ -200,6 +208,10 @@ module QA
autoload :Sidebar, 'qa/page/issuable/sidebar' autoload :Sidebar, 'qa/page/issuable/sidebar'
end end
module Layout
autoload :Banner, 'qa/page/layout/banner'
end
module MergeRequest module MergeRequest
autoload :New, 'qa/page/merge_request/new' autoload :New, 'qa/page/merge_request/new'
autoload :Show, 'qa/page/merge_request/show' autoload :Show, 'qa/page/merge_request/show'
......
...@@ -11,6 +11,8 @@ module QA ...@@ -11,6 +11,8 @@ module QA
factory.output factory.output
end end
product(:project) { |factory| factory.project }
def initialize def initialize
@file_name = 'file.txt' @file_name = 'file.txt'
@file_content = '# This is test project' @file_content = '# This is test project'
......
...@@ -5,7 +5,8 @@ module QA ...@@ -5,7 +5,8 @@ module QA
module Repository module Repository
class Push < Factory::Base class Push < Factory::Base
attr_accessor :file_name, :file_content, :commit_message, attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_uri :branch_name, :new_branch, :output, :repository_uri,
:user
attr_writer :remote_branch attr_writer :remote_branch
...@@ -31,9 +32,20 @@ module QA ...@@ -31,9 +32,20 @@ module QA
def fabricate! def fabricate!
Git::Repository.perform do |repository| Git::Repository.perform do |repository|
repository.uri = repository_uri repository.uri = repository_uri
repository.use_default_credentials repository.use_default_credentials
username = 'GitLab QA'
email = 'root@gitlab.com'
if user
repository.username = user.username
repository.password = user.password
username = user.name
email = user.email
end
repository.clone repository.clone
repository.configure_identity('GitLab QA', 'root@gitlab.com') repository.configure_identity(username, email)
if new_branch if new_branch
repository.checkout_new_branch(branch_name) repository.checkout_new_branch(branch_name)
......
module QA
module Factory
module Resource
class Fork < Factory::Base
dependency Factory::Repository::ProjectPush, as: :push
dependency Factory::Resource::User, as: :user
product(:user) { |factory| factory.user }
def fabricate!
push.project.visit!
Page::Project::Show.act { fork_project }
Page::Project::Fork::New.perform do |fork_new|
fork_new.choose_namespace(user.name)
end
Page::Layout::Banner.act { has_notice?('The project was successfully forked.') }
end
end
end
end
end
module QA
module Factory
module Resource
class MergeRequestFromFork < MergeRequest
attr_accessor :fork_branch
dependency Factory::Resource::Fork, as: :fork
dependency Factory::Repository::ProjectPush, as: :push do |push, factory|
push.project = factory.fork
push.branch_name = factory.fork_branch
push.file_name = 'file2.txt'
push.user = factory.fork.user
end
def fabricate!
fork.visit!
Page::Project::Show.act { new_merge_request }
Page::MergeRequest::New.act { create_merge_request }
end
end
end
end
end
...@@ -37,6 +37,7 @@ module QA ...@@ -37,6 +37,7 @@ module QA
page.choose_test_namespace page.choose_test_namespace
page.choose_name(@name) page.choose_name(@name)
page.add_description(@description) page.add_description(@description)
page.set_visibility('Public')
page.create_new_project page.create_new_project
end end
end end
......
require 'securerandom'
module QA
module Factory
module Resource
class User < Factory::Base
attr_accessor :name, :username, :email, :password
def initialize
@name = "name-#{SecureRandom.hex(8)}"
@username = "username-#{SecureRandom.hex(8)}"
@email = "mail#{SecureRandom.hex(8)}@mail.com"
@password = 'password'
end
product(:name) { |factory| factory.name }
product(:username) { |factory| factory.username }
product(:email) { |factory| factory.email }
product(:password) { |factory| factory.password }
def fabricate!
Page::Menu::Main.act { sign_out }
Page::Main::Login.act { switch_to_register_tab }
Page::Main::SignUp.perform do |page|
page.sign_up!(name: name, username: username, email: email, password: password)
end
end
end
end
end
end
...@@ -4,6 +4,7 @@ module QA ...@@ -4,6 +4,7 @@ module QA
class Sidebar < Page::Base class Sidebar < Page::Base
view 'app/views/shared/issuable/_sidebar.html.haml' do view 'app/views/shared/issuable/_sidebar.html.haml' do
element :labels_block, ".issuable-show-labels" element :labels_block, ".issuable-show-labels"
element :milestones_block, '.block.milestone'
end end
def has_label?(label) def has_label?(label)
...@@ -11,6 +12,12 @@ module QA ...@@ -11,6 +12,12 @@ module QA
!!find('span', text: label) !!find('span', text: label)
end end
end end
def has_milestone?(milestone)
page.within('.block.milestone') do
!!find("[href*='/milestones/']", text: milestone)
end
end
end end
end end
end end
......
module QA
module Page
module Layout
class Banner < Page::Base
view 'app/views/layouts/header/_read_only_banner.html.haml' do
element :flash_notice, ".flash-notice"
end
def has_notice?(message)
page.within('.flash-notice') do
!!find('span', text: message)
end
end
end
end
end
end
...@@ -25,19 +25,24 @@ module QA ...@@ -25,19 +25,24 @@ module QA
element :standard_tab, "link_to 'Standard'" element :standard_tab, "link_to 'Standard'"
end end
view 'app/views/devise/shared/_tabs_normal.html.haml' do
element :sign_in_tab, /nav-link.*login-pane.*Sign in/
element :register_tab, /nav-link.*register-pane.*Register/
end
def initialize def initialize
# The login page is usually the entry point for all the scenarios so # The login page is usually the entry point for all the scenarios so
# we need to wait for the instance to start. That said, in some cases # we need to wait for the instance to start. That said, in some cases
# we are already logged-in so we check both cases here. # we are already logged-in so we check both cases here.
wait(max: 500) do wait(max: 500) do
page.has_css?('.login-page') || page.has_css?('.login-page') ||
Page::Menu::Main.act { has_personal_area? } Page::Menu::Main.act { has_personal_area?(wait: 0) }
end end
end end
def sign_in_using_credentials def sign_in_using_credentials
# Don't try to log-in if we're already logged-in # Don't try to log-in if we're already logged-in
return if Page::Menu::Main.act { has_personal_area? } return if Page::Menu::Main.act { has_personal_area?(wait: 0) }
using_wait_time 0 do using_wait_time 0 do
set_initial_password_if_present set_initial_password_if_present
...@@ -48,12 +53,22 @@ module QA ...@@ -48,12 +53,22 @@ module QA
sign_in_using_gitlab_credentials sign_in_using_gitlab_credentials
end end
end end
Page::Menu::Main.act { has_personal_area? }
end end
def self.path def self.path
'/users/sign_in' '/users/sign_in'
end end
def switch_to_sign_in_tab
click_on 'Sign in'
end
def switch_to_register_tab
click_on 'Register'
end
private private
def sign_in_using_ldap_credentials def sign_in_using_ldap_credentials
......
module QA
module Page
module Main
class SignUp < Page::Base
view 'app/views/devise/shared/_signup_box.html.haml' do
element :name, 'text_field :name'
element :username, 'text_field :username'
element :email_field, 'email_field :email'
element :email_confirmation, 'email_field :email_confirmation'
element :password, 'password_field :password'
element :register_button, 'submit "Register"'
end
def sign_up!(name:, username:, email:, password:)
fill_in :new_user_name, with: name
fill_in :new_user_username, with: username
fill_in :new_user_email, with: email
fill_in :new_user_email_confirmation, with: email
fill_in :new_user_password, with: password
click_button 'Register'
Page::Menu::Main.act { has_personal_area? }
end
end
end
end
end
...@@ -60,9 +60,9 @@ module QA ...@@ -60,9 +60,9 @@ module QA
end end
end end
def has_personal_area? def has_personal_area?(wait: Capybara.default_max_wait_time)
# No need to wait, either we're logged-in, or not. # No need to wait, either we're logged-in, or not.
using_wait_time(0) { page.has_selector?('.qa-user-avatar') } using_wait_time(wait) { page.has_selector?('.qa-user-avatar') }
end end
private private
......
...@@ -81,12 +81,6 @@ module QA ...@@ -81,12 +81,6 @@ module QA
click_element :squash_checkbox click_element :squash_checkbox
end end
def has_milestone?(milestone_title)
page.within('.issuable-sidebar') do
!!find("[href*='/milestones/']", text: milestone_title, wait: 1)
end
end
end end
end end
end end
......
module QA
module Page
module Project
module Fork
class New < Page::Base
view 'app/views/projects/forks/_fork_button.html.haml' do
element :namespace, 'link_to project_forks_path'
end
def choose_namespace(namespace = Runtime::Namespace.path)
click_on namespace
end
end
end
end
end
end
...@@ -14,6 +14,7 @@ module QA ...@@ -14,6 +14,7 @@ module QA
element :project_path, 'text_field :path' element :project_path, 'text_field :path'
element :project_description, 'text_area :description' element :project_description, 'text_area :description'
element :project_create_button, "submit 'Create project'" element :project_create_button, "submit 'Create project'"
element :visibility_radios, 'visibility_level:'
end end
view 'app/views/projects/_import_project_pane.html.haml' do view 'app/views/projects/_import_project_pane.html.haml' do
...@@ -42,6 +43,10 @@ module QA ...@@ -42,6 +43,10 @@ module QA
click_on 'Create project' click_on 'Create project'
end end
def set_visibility(visibility)
choose visibility
end
def go_to_github_import def go_to_github_import
click_link 'GitHub' click_link 'GitHub'
end end
......
...@@ -24,6 +24,11 @@ module QA ...@@ -24,6 +24,11 @@ module QA
element :branches_dropdown element :branches_dropdown
end end
view 'app/views/projects/buttons/_fork.html.haml' do
element :fork_label, "%span= s_('GoToYourFork|Fork')"
element :fork_link, "link_to new_project_fork_path(@project)"
end
view 'app/views/projects/_files.html.haml' do view 'app/views/projects/_files.html.haml' do
element :tree_holder, '.tree-holder' element :tree_holder, '.tree-holder'
end end
...@@ -63,6 +68,10 @@ module QA ...@@ -63,6 +68,10 @@ module QA
click_link 'New issue' click_link 'New issue'
end end
def fork_project
click_on 'Fork'
end
end end
end end
end end
......
module QA
describe 'standard user login', :core do
it 'user logs in using credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
#
Page::Menu::Main.perform do |menu|
expect(menu).to have_personal_area
end
end
end
end
...@@ -20,11 +20,12 @@ module QA ...@@ -20,11 +20,12 @@ module QA
merge_request.milestone = current_milestone merge_request.milestone = current_milestone
end end
Page::MergeRequest::Show.perform do |merge_request| expect(page).to have_content('This is a merge request with a milestone')
expect(page).to have_content('This is a merge request with a milestone') expect(page).to have_content('Great feature with milestone')
expect(page).to have_content('Great feature with milestone') expect(page).to have_content(/Opened [\w\s]+ ago/)
expect(page).to have_content(/Opened [\w\s]+ ago/)
expect(merge_request).to have_milestone(current_milestone.title) Page::Issuable::Sidebar.perform do |sidebar|
expect(sidebar).to have_milestone(current_milestone.title)
end end
end end
end end
......
module QA
describe 'Project fork', :core do
it 'can submit merge requests to upstream master' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
Page::Menu::Main.act { sign_out }
Page::Main::Login.act do
switch_to_sign_in_tab
sign_in_using_credentials
end
merge_request.visit!
Page::MergeRequest::Show.act { merge! }
expect(page).to have_content('The changes were merged')
end
end
end
...@@ -13,15 +13,11 @@ module QA ...@@ -13,15 +13,11 @@ module QA
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.act { sign_in_using_credentials }
end end
after do |example| after do
# We need to clear localStorage because we're using it for the dropdown, # We need to clear localStorage because we're using it for the dropdown,
# and capybara doesn't do this for us. # and capybara doesn't do this for us.
# https://github.com/teamcapybara/capybara/issues/1702 # https://github.com/teamcapybara/capybara/issues/1702
Capybara.execute_script 'localStorage.clear()' Capybara.execute_script 'localStorage.clear()'
# In order to help diagnose a false failure
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48241
log_push_output if example.exception
end end
context 'when developers and maintainers are allowed to push to a protected branch' do context 'when developers and maintainers are allowed to push to a protected branch' do
...@@ -31,9 +27,9 @@ module QA ...@@ -31,9 +27,9 @@ module QA
expect(protected_branch.name).to have_content(branch_name) expect(protected_branch.name).to have_content(branch_name)
expect(protected_branch.push_allowance).to have_content('Developers + Maintainers') expect(protected_branch.push_allowance).to have_content('Developers + Maintainers')
@push = push_new_file(branch_name) push = push_new_file(branch_name)
expect(@push.output).to match(/remote: To create a merge request for protected-branch, visit/) expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/)
end end
end end
...@@ -41,11 +37,11 @@ module QA ...@@ -41,11 +37,11 @@ module QA
it 'user without push rights fails to push to the protected branch' do it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false) create_protected_branch(allow_to_push: false)
@push = push_new_file(branch_name) push = push_new_file(branch_name)
expect(@push.output) expect(push.output)
.to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/) .to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
expect(@push.output) expect(push.output)
.to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/) .to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end end
end end
...@@ -69,13 +65,5 @@ module QA ...@@ -69,13 +65,5 @@ module QA
resource.new_branch = false resource.new_branch = false
end end
end end
def log_push_output
if defined?(@push)
filename = File.join('tmp', "push-output-#{project.name}")
puts "Exception detected. Push output will be saved to #{filename}"
IO.binwrite(filename, @push.output)
end
end
end end
end end
...@@ -2,6 +2,11 @@ require "spec_helper" ...@@ -2,6 +2,11 @@ require "spec_helper"
describe Gitlab::Git::Branch, seed_helper: true do describe Gitlab::Git::Branch, seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
let(:rugged) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository.rugged
end
end
subject { repository.branches } subject { repository.branches }
...@@ -124,6 +129,7 @@ describe Gitlab::Git::Branch, seed_helper: true do ...@@ -124,6 +129,7 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
def create_commit def create_commit
repository.create_commit(params.merge(committer: committer.merge(time: Time.now))) params[:message].delete!("\r")
Rugged::Commit.create(rugged, params.merge(committer: committer.merge(time: Time.now)))
end end
end end
...@@ -27,6 +27,7 @@ EOT ...@@ -27,6 +27,7 @@ EOT
too_large: false too_large: false
} }
# TODO use a Gitaly diff object instead
@rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do @rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths: repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths:
[".gitmodules"]).patches.first [".gitmodules"]).patches.first
...@@ -266,8 +267,12 @@ EOT ...@@ -266,8 +267,12 @@ EOT
describe '#submodule?' do describe '#submodule?' do
before do before do
commit = repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e') # TODO use a Gitaly diff object instead
@diffs = commit.parents[0].diff(commit).patches rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
end
@diffs = rugged_commit.parents[0].diff(rugged_commit).patches
end end
it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) } it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) }
......
...@@ -611,21 +611,6 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -611,21 +611,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
end end
describe "#remove_remote" do
before(:all) do
@repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
@repo.remove_remote("expendable")
end
it "should remove the remote" do
expect(@repo.rugged.remotes).not_to include("expendable")
end
after(:all) do
ensure_seeds
end
end
describe "#remote_update" do describe "#remote_update" do
before(:all) do before(:all) do
@repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
...@@ -633,7 +618,9 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -633,7 +618,9 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it "should add the remote" do it "should add the remote" do
expect(@repo.rugged.remotes["expendable"].url).to( rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access { @repo.rugged }
expect(rugged.remotes["expendable"].url).to(
eq(TEST_NORMAL_REPO_PATH) eq(TEST_NORMAL_REPO_PATH)
) )
end end
...@@ -1157,6 +1144,13 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1157,6 +1144,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
@repo.rugged.config['core.autocrlf'] = true @repo.rugged.config['core.autocrlf'] = true
end end
around do |example|
# OK because autocrlf is only used in gitaly-ruby
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
example.run
end
end
it 'return the value of the autocrlf option' do it 'return the value of the autocrlf option' do
expect(@repo.autocrlf).to be(true) expect(@repo.autocrlf).to be(true)
end end
...@@ -1172,6 +1166,13 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1172,6 +1166,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
@repo.rugged.config['core.autocrlf'] = false @repo.rugged.config['core.autocrlf'] = false
end end
around do |example|
# OK because autocrlf= is only used in gitaly-ruby
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
example.run
end
end
it 'should set the autocrlf option to the provided option' do it 'should set the autocrlf option to the provided option' do
@repo.autocrlf = :input @repo.autocrlf = :input
...@@ -2042,54 +2043,61 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -2042,54 +2043,61 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:repository) do let(:repository) do
Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
end end
let(:rugged) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged }
end
let(:remote_name) { 'my-remote' } let(:remote_name) { 'my-remote' }
let(:url) { 'http://my-repo.git' }
after do after do
ensure_seeds ensure_seeds
end end
describe '#add_remote' do describe '#add_remote' do
let(:url) { 'http://my-repo.git' }
let(:mirror_refmap) { '+refs/*:refs/*' } let(:mirror_refmap) { '+refs/*:refs/*' }
it 'creates a new remote via Gitaly' do shared_examples 'add_remote' do
expect_any_instance_of(Gitlab::GitalyClient::RemoteService) it 'added the remote' do
.to receive(:add_remote).with(remote_name, url, mirror_refmap) begin
rugged.remotes.delete(remote_name)
rescue Rugged::ConfigError
end
repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap)
repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) expect(rugged.remotes[remote_name]).not_to be_nil
expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true')
expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true')
expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap)
end
end end
context 'with Gitaly disabled', :skip_gitaly_mock do context 'using Gitaly' do
it 'creates a new remote via Rugged' do it_behaves_like 'add_remote'
expect_any_instance_of(Rugged::RemoteCollection).to receive(:create) end
.with(remote_name, url)
expect_any_instance_of(Rugged::Config).to receive(:[]=)
.with("remote.#{remote_name}.mirror", true)
expect_any_instance_of(Rugged::Config).to receive(:[]=)
.with("remote.#{remote_name}.prune", true)
expect_any_instance_of(Rugged::Config).to receive(:[]=)
.with("remote.#{remote_name}.fetch", mirror_refmap)
repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) context 'with Gitaly disabled', :disable_gitaly do
end it_behaves_like 'add_remote'
end end
end end
describe '#remove_remote' do describe '#remove_remote' do
it 'removes the remote via Gitaly' do shared_examples 'remove_remote' do
expect_any_instance_of(Gitlab::GitalyClient::RemoteService) it 'removes the remote' do
.to receive(:remove_remote).with(remote_name) rugged.remotes.create(remote_name, url)
repository.remove_remote(remote_name) repository.remove_remote(remote_name)
expect(rugged.remotes[remote_name]).to be_nil
end
end end
context 'with Gitaly disabled', :skip_gitaly_mock do context 'using Gitaly' do
it 'removes the remote via Rugged' do it_behaves_like 'remove_remote'
expect_any_instance_of(Rugged::RemoteCollection).to receive(:delete) end
.with(remote_name)
repository.remove_remote(remote_name) context 'with Gitaly disabled', :disable_gitaly do
end it_behaves_like 'remove_remote'
end end
end end
end end
...@@ -2281,20 +2289,25 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -2281,20 +2289,25 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') } let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') }
it 'cleans up the files' do it 'cleans up the files' do
repository.with_worktree(worktree_path, 'master', env: ENV) do create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master]
FileUtils.touch(worktree_path, mtime: Time.now - 8.hours) raise 'preparation failed' unless system(*create_worktree, err: '/dev/null')
# git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
# but the HEAD must be 40 characters long or git will ignore it.
File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
# git 2.16 fails with "fatal: bad object HEAD" FileUtils.touch(worktree_path, mtime: Time.now - 8.hours)
expect { repository.rev_list(including: :all) }.to raise_error(Gitlab::Git::Repository::GitError) # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object,
# but the HEAD must be 40 characters long or git will ignore it.
File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA)
repository.clean_stale_repository_files # git 2.16 fails with "fatal: bad object HEAD"
expect(rev_list_all).to be false
expect { repository.rev_list(including: :all) }.not_to raise_error repository.clean_stale_repository_files
expect(File.exist?(worktree_path)).to be_falsey
end expect(rev_list_all).to be true
expect(File.exist?(worktree_path)).to be_falsey
end
def rev_list_all
system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null')
end end
it 'increments a counter upon an error' do it 'increments a counter upon an error' do
......
...@@ -32,65 +32,4 @@ describe Gitlab::Git::RevList do ...@@ -32,65 +32,4 @@ describe Gitlab::Git::RevList do
expect(rev_list.new_refs).to eq(%w[sha1 sha2]) expect(rev_list.new_refs).to eq(%w[sha1 sha2])
end end
end end
context '#new_objects' do
it 'fetches list of newly pushed objects using rev-list' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
expect { |b| rev_list.new_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end
it 'can skip pathless objects' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file")
expect { |b| rev_list.new_objects(require_path: true, &b) }.to yield_with_args(%w[sha2])
end
it 'can handle non utf-8 paths' do
non_utf_char = [0x89].pack("c*").force_encoding("UTF-8")
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1")
rev_list.new_objects(require_path: true) do |object_ids|
expect(object_ids.force).to eq(%w[sha2])
end
end
it 'can yield a lazy enumerator' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
rev_list.new_objects do |object_ids|
expect(object_ids).to be_a Enumerator::Lazy
end
end
it 'returns the result of the block when given' do
stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2")
objects = rev_list.new_objects do |object_ids|
object_ids.first
end
expect(objects).to eq 'sha1'
end
it 'can accept list of references to exclude' do
stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2")
expect { |b| rev_list.new_objects(not_in: ['master'], &b) }.to yield_with_args(%w[sha1 sha2])
end
it 'handles empty list of references to exclude as listing all known objects' do
stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2")
expect { |b| rev_list.new_objects(not_in: [], &b) }.to yield_with_args(%w[sha1 sha2])
end
end
context '#all_objects' do
it 'fetches list of all pushed objects using rev-list' do
stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2")
expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2])
end
end
end end
...@@ -734,26 +734,18 @@ describe Gitlab::GitAccess do ...@@ -734,26 +734,18 @@ describe Gitlab::GitAccess do
merge_into_protected_branch: "0b4bc9a #{merge_into_protected_branch} refs/heads/feature" } merge_into_protected_branch: "0b4bc9a #{merge_into_protected_branch} refs/heads/feature" }
end end
def stub_git_hooks
# Running the `pre-receive` hook is expensive, and not necessary for this test.
allow_any_instance_of(Gitlab::Git::HooksService).to receive(:execute) do |service, &block|
block.call(service)
end
end
def merge_into_protected_branch def merge_into_protected_branch
@protected_branch_merge_commit ||= begin @protected_branch_merge_commit ||= begin
Gitlab::GitalyClient::StorageSettings.allow_disk_access do Gitlab::GitalyClient::StorageSettings.allow_disk_access do
stub_git_hooks
project.repository.add_branch(user, unprotected_branch, 'feature') project.repository.add_branch(user, unprotected_branch, 'feature')
target_branch = project.repository.lookup('feature') rugged = project.repository.rugged
target_branch = rugged.rev_parse('feature')
source_branch = project.repository.create_file( source_branch = project.repository.create_file(
user, user,
'filename', 'filename',
'This is the file content', 'This is the file content',
message: 'This is a good commit message', message: 'This is a good commit message',
branch_name: unprotected_branch) branch_name: unprotected_branch)
rugged = project.repository.rugged
author = { email: "email@example.com", time: Time.now, name: "Example Git User" } author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
merge_index = rugged.merge_commits(target_branch, source_branch) merge_index = rugged.merge_commits(target_branch, source_branch)
......
# coding: utf-8
require "spec_helper" require "spec_helper"
describe ProjectWiki do describe ProjectWiki do
...@@ -10,7 +11,6 @@ describe ProjectWiki do ...@@ -10,7 +11,6 @@ describe ProjectWiki do
subject { project_wiki } subject { project_wiki }
it { is_expected.to delegate_method(:empty?).to :pages }
it { is_expected.to delegate_method(:repository_storage).to :project } it { is_expected.to delegate_method(:repository_storage).to :project }
it { is_expected.to delegate_method(:hashed_storage?).to :project } it { is_expected.to delegate_method(:hashed_storage?).to :project }
...@@ -100,11 +100,19 @@ describe ProjectWiki do ...@@ -100,11 +100,19 @@ describe ProjectWiki do
context "when the wiki has pages" do context "when the wiki has pages" do
before do before do
project_wiki.create_page("index", "This is an awesome new Gollum Wiki") project_wiki.create_page("index", "This is an awesome new Gollum Wiki")
project_wiki.create_page("another-page", "This is another page")
end end
describe '#empty?' do describe '#empty?' do
subject { super().empty? } subject { super().empty? }
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
# Re-enable this when https://gitlab.com/gitlab-org/gitaly/issues/1204 is fixed
xit 'only instantiates a Wiki page once' do
expect(WikiPage).to receive(:new).once.and_call_original
subject
end
end end
end end
end end
......
...@@ -151,7 +151,9 @@ describe Repository do ...@@ -151,7 +151,9 @@ describe Repository do
it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) } it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) }
after do after do
repository.rugged.tags.delete(annotated_tag_name) Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository.rugged.tags.delete(annotated_tag_name)
end
end end
end end
end end
...@@ -2231,8 +2233,11 @@ describe Repository do ...@@ -2231,8 +2233,11 @@ describe Repository do
create_remote_branch('joe', 'remote_branch', masterrev) create_remote_branch('joe', 'remote_branch', masterrev)
repository.add_branch(user, 'local_branch', masterrev.id) repository.add_branch(user, 'local_branch', masterrev.id)
expect(repository.remote_branches('joe').any? { |branch| branch.name == 'local_branch' }).to eq(false) # TODO: move this test to gitaly https://gitlab.com/gitlab-org/gitaly/issues/1243
expect(repository.remote_branches('joe').any? { |branch| branch.name == 'remote_branch' }).to eq(true) Gitlab::GitalyClient::StorageSettings.allow_disk_access do
expect(repository.remote_branches('joe').any? { |branch| branch.name == 'local_branch' }).to eq(false)
expect(repository.remote_branches('joe').any? { |branch| branch.name == 'remote_branch' }).to eq(true)
end
end end
end end
......
...@@ -121,6 +121,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| ...@@ -121,6 +121,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
expect(json_response['body']).to eq('hi!') expect(json_response['body']).to eq('hi!')
expect(json_response['author']['username']).to eq(user.username) expect(json_response['author']['username']).to eq(user.username)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
expect(Time.parse(json_response['updated_at'])).to be_like_time(creation_time)
end end
end end
......
...@@ -209,12 +209,7 @@ describe GitGarbageCollectWorker do ...@@ -209,12 +209,7 @@ describe GitGarbageCollectWorker do
tree: old_commit.tree, tree: old_commit.tree,
parents: [old_commit] parents: [old_commit]
) )
Gitlab::Git::OperationService.new(nil, project.repository.raw_repository).send( rugged.references.create("refs/heads/#{SecureRandom.hex(6)}", new_commit_sha)
:update_ref,
"refs/heads/#{SecureRandom.hex(6)}",
new_commit_sha,
Gitlab::Git::BLANK_SHA
)
end end
def packs(project) def packs(project)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment