Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--CONTRIBUTING.md65
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/boards/components/board_card.js2
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es617
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.js92
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.js.es664
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js.es64
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es64
-rw-r--r--app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es65
-rw-r--r--app/assets/javascripts/notes.js35
-rw-r--r--app/assets/stylesheets/framework/calendar.scss1
-rw-r--r--app/assets/stylesheets/framework/files.scss1
-rw-r--r--app/assets/stylesheets/highlight/dark.scss17
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss17
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss17
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss18
-rw-r--r--app/assets/stylesheets/highlight/white.scss18
-rw-r--r--app/assets/stylesheets/pages/diff.scss9
-rw-r--r--app/controllers/application_controller.rb21
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects/notes_controller.rb11
-rw-r--r--app/controllers/root_controller.rb37
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb2
-rw-r--r--app/models/ci/build.rb9
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/user.rb36
-rw-r--r--app/serializers/pipeline_serializer.rb2
-rw-r--r--app/services/commits/change_service.rb4
-rw-r--r--app/services/files/base_service.rb2
-rw-r--r--app/services/files/multi_service.rb2
-rw-r--r--app/services/files/update_service.rb2
-rw-r--r--app/services/issuable_base_service.rb9
-rw-r--r--app/services/issues/move_service.rb2
-rw-r--r--app/services/merge_requests/resolve_service.rb3
-rw-r--r--app/services/notes/create_service.rb10
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/services/slash_commands/interpret_service.rb17
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml2
-rw-r--r--app/views/projects/blob/_actions.html.haml9
-rw-r--r--app/views/projects/blob/_blob.html.haml13
-rw-r--r--app/views/projects/blob/diff.html.haml10
-rw-r--r--app/views/projects/boards/components/_board_list.html.haml22
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml32
-rw-r--r--changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml4
-rw-r--r--changelogs/unreleased/25437-just-emoji.yml4
-rw-r--r--changelogs/unreleased/26136-list-repository-tree-api-doc.yml4
-rw-r--r--changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml4
-rw-r--r--changelogs/unreleased/27778-a11y-sidebar.yml5
-rw-r--r--changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml4
-rw-r--r--changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml4
-rw-r--r--changelogs/unreleased/28837-remove-help-duplicate.yml4
-rw-r--r--changelogs/unreleased/28850-fix-broken-migration.yml4
-rw-r--r--changelogs/unreleased/diff-make-obvious-cant-comment.yml4
-rw-r--r--changelogs/unreleased/long-file-name-overflow.yml4
-rw-r--r--changelogs/unreleased/remove-new-relic-gem.yml4
-rw-r--r--changelogs/unreleased/use-v3-api-on-frontend.yml4
-rw-r--r--changelogs/unreleased/user-calendar-border.yml4
-rw-r--r--config/newrelic.yml16
-rw-r--r--db/post_migrate/20170211073944_disable_invalid_service_templates.rb6
-rw-r--r--doc/api/enviroments.md34
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/api/tags.md4
-rw-r--r--doc/api/v3_to_v4.md1
-rw-r--r--doc/ci/variables/README.md10
-rw-r--r--doc/development/ux_guide/img/karolina-plaskaty.pngbin0 -> 33498 bytes
-rw-r--r--doc/development/ux_guide/img/nazim-ramesh.pngbin0 -> 31163 bytes
-rw-r--r--doc/development/ux_guide/users.md4
-rw-r--r--doc/gitlab-basics/img/create_new_project_button.pngbin4196 -> 6978 bytes
-rw-r--r--doc/install/installation.md7
-rw-r--r--doc/raketasks/backup_restore.md34
-rw-r--r--doc/user/project/pages/getting_started_part_four.md382
-rw-r--r--doc/user/project/pages/getting_started_part_one.md196
-rw-r--r--doc/user/project/pages/getting_started_part_three.md552
-rw-r--r--doc/user/project/pages/getting_started_part_two.md24
-rw-r--r--doc/user/project/pages/index.md39
-rw-r--r--doc/user/project/slash_commands.md1
-rw-r--r--doc/workflow/gitlab_flow.md2
-rw-r--r--features/project/issues/award_emoji.feature2
-rw-r--r--features/steps/project/issues/award_emoji.rb4
-rw-r--r--lib/api/api_guard.rb11
-rw-r--r--lib/api/entities.rb3
-rw-r--r--lib/api/environments.rb17
-rw-r--r--lib/bitbucket/error/unauthorized.rb3
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb2
-rw-r--r--lib/extracts_path.rb2
-rw-r--r--lib/gitlab/access.rb2
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb2
-rw-r--r--lib/gitlab/ci/config/entry/factory.rb2
-rw-r--r--lib/gitlab/ci/config/entry/node.rb2
-rw-r--r--lib/gitlab/ci/config/loader.rb2
-rw-r--r--lib/gitlab/conflict/file.rb3
-rw-r--r--lib/gitlab/conflict/file_collection.rb3
-rw-r--r--lib/gitlab/conflict/parser.rb22
-rw-r--r--lib/gitlab/conflict/resolution_error.rb3
-rw-r--r--lib/gitlab/email/receiver.rb26
-rw-r--r--lib/gitlab/git/diff.rb2
-rw-r--r--lib/gitlab/git/repository.rb6
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--lib/gitlab/import_export/error.rb2
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/gitlab/route_map.rb2
-rw-r--r--lib/gitlab/serializer/pagination.rb2
-rw-r--r--lib/gitlab/shell.rb2
-rw-r--r--lib/gitlab/template/finders/repo_template_finder.rb2
-rw-r--r--lib/gitlab/update_path_error.rb2
-rw-r--r--lib/mattermost/client.rb2
-rw-r--r--lib/mattermost/error.rb2
-rw-r--r--lib/mattermost/session.rb2
-rw-r--r--rubocop/cop/custom_error_class.rb64
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb3
-rw-r--r--spec/controllers/root_controller_spec.rb36
-rw-r--r--spec/factories/todos.rb14
-rw-r--r--spec/features/issues/award_emoji_spec.rb25
-rw-r--r--spec/javascripts/boards/board_card_spec.js2
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js191
-rw-r--r--spec/models/ci/build_spec.rb14
-rw-r--r--spec/requests/api/environments_spec.rb35
-rw-r--r--spec/requests/api/notes_spec.rb4
-rw-r--r--spec/requests/api/todos_spec.rb4
-rw-r--r--spec/requests/api/v3/notes_spec.rb4
-rw-r--r--spec/rubocop/cop/custom_error_class_spec.rb111
-rw-r--r--spec/services/notes/create_service_spec.rb50
-rw-r--r--spec/services/slash_commands/interpret_service_spec.rb39
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb89
135 files changed, 1821 insertions, 1054 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f279a57105c..e075de055e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 8.17.2 (2017-03-01)
+
+- Expire all webpack assets after 8.17.1 included a badly compiled asset. !9602
+
## 8.17.1 (2017-02-28)
- Replace setInterval with setTimeout to prevent highly frequent requests. !9271 (Takuya Noguchi)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index eed63127d9f..3cbc826e6db 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,32 +1,48 @@
+## Contributor license agreement
+
+By submitting code as an individual you agree to the
+[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md).
+By submitting code as an entity you agree to the
+[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
+
+_This notice should stay as the first item in the CONTRIBUTING.MD file._
+
+---
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+- [Contributor license agreement](#contributor-license-agreement)
- [Contribute to GitLab](#contribute-to-gitlab)
- - [Contributor license agreement](#contributor-license-agreement)
- - [Security vulnerability disclosure](#security-vulnerability-disclosure)
- - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- - [Helping others](#helping-others)
- - [I want to contribute!](#i-want-to-contribute)
- - [Implement design & UI elements](#implement-design-ui-elements)
- - [Issue tracker](#issue-tracker)
- - [Feature proposals](#feature-proposals)
- - [Issue tracker guidelines](#issue-tracker-guidelines)
- - [Issue weight](#issue-weight)
- - [Regression issues](#regression-issues)
- - [Technical debt](#technical-debt)
- - [Stewardship](#stewardship)
- - [Merge requests](#merge-requests)
- - [Merge request guidelines](#merge-request-guidelines)
- - [Contribution acceptance criteria](#contribution-acceptance-criteria)
- - [Changes for Stable Releases](#changes-for-stable-releases)
- - [Definition of done](#definition-of-done)
- - [Style guides](#style-guides)
- - [Code of conduct](#code-of-conduct)
+- [Security vulnerability disclosure](#security-vulnerability-disclosure)
+- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
+- [Helping others](#helping-others)
+- [I want to contribute!](#i-want-to-contribute)
+- [Implement design & UI elements](#implement-design-ui-elements)
+- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
+ - [Retrospective](#retrospective)
+ - [Kickoff](#kickoff)
+- [Issue tracker](#issue-tracker)
+ - [Feature proposals](#feature-proposals)
+ - [Issue tracker guidelines](#issue-tracker-guidelines)
+ - [Issue weight](#issue-weight)
+ - [Regression issues](#regression-issues)
+ - [Technical debt](#technical-debt)
+ - [Stewardship](#stewardship)
+- [Merge requests](#merge-requests)
+ - [Merge request guidelines](#merge-request-guidelines)
+ - [Contribution acceptance criteria](#contribution-acceptance-criteria)
+- [Changes for Stable Releases](#changes-for-stable-releases)
+- [Definition of done](#definition-of-done)
+- [Style guides](#style-guides)
+- [Code of conduct](#code-of-conduct)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
-# Contribute to GitLab
+---
+
+## Contribute to GitLab
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone.
@@ -41,13 +57,6 @@ operates please see [the GitLab contributing process](PROCESS.md).
- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
-## Contributor license agreement
-
-By submitting code as an individual you agree to the
-[individual contributor license agreement](doc/legal/individual_contributor_license_agreement.md).
-By submitting code as an entity you agree to the
-[corporate contributor license agreement](doc/legal/corporate_contributor_license_agreement.md).
-
## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to
diff --git a/Gemfile b/Gemfile
index 429454b9ed6..8e181fee73b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -329,8 +329,6 @@ group :test do
gem 'timecop', '~> 0.8.0'
end
-gem 'newrelic_rpm', '~> 3.16'
-
gem 'octokit', '~> 4.6.2'
gem 'mail_room', '~> 0.9.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 472cee510cb..4d6ce2a62b6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -427,7 +427,6 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
- newrelic_rpm (3.16.0.318)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
numerizer (0.1.1)
@@ -915,7 +914,6 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
net-ssh (~> 3.0.1)
- newrelic_rpm (~> 3.16)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0)
octokit (~> 4.6.2)
diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js
index 52f61d84517..795b3cf2ec0 100644
--- a/app/assets/javascripts/boards/components/board_card.js
+++ b/app/assets/javascripts/boards/components/board_card.js
@@ -3,7 +3,7 @@ require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore;
-module.exports = {
+export default {
name: 'BoardsIssueCard',
template: `
<li class="card"
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index d92047cc0f8..2d52e96e7fb 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -2,8 +2,8 @@
/* global Vue */
/* global Sortable */
-const boardCard = require('./board_card');
-require('./board_new_issue');
+import boardNewIssue from './board_new_issue';
+import boardCard from './board_card';
(() => {
const Store = gl.issueBoards.BoardsStore;
@@ -15,7 +15,7 @@ require('./board_new_issue');
template: '#js-board-list-template',
components: {
boardCard,
- 'board-new-issue': gl.issueBoards.BoardNewIssue
+ boardNewIssue,
},
props: {
disabled: Boolean,
@@ -81,6 +81,12 @@ require('./board_new_issue');
});
}
},
+ toggleForm() {
+ this.showIssueForm = !this.showIssueForm;
+ },
+ },
+ created() {
+ gl.IssueBoardsApp.$on(`hide-issue-form-${this.list.id}`, this.toggleForm);
},
mounted () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
@@ -115,6 +121,9 @@ require('./board_new_issue');
this.loadNextPage();
}
};
- }
+ },
+ beforeDestroy() {
+ gl.IssueBoardsApp.$off(`hide-issue-form-${this.list.id}`, this.toggleForm);
+ },
});
})();
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js
new file mode 100644
index 00000000000..b88f59dd6d4
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_new_issue.js
@@ -0,0 +1,92 @@
+/* global ListIssue */
+const Store = gl.issueBoards.BoardsStore;
+
+export default {
+ name: 'BoardNewIssue',
+ props: {
+ list: Object,
+ },
+ data() {
+ return {
+ title: '',
+ error: false,
+ };
+ },
+ methods: {
+ submit(e) {
+ e.preventDefault();
+ if (this.title.trim() === '') return;
+
+ this.error = false;
+
+ const labels = this.list.label ? [this.list.label] : [];
+ const issue = new ListIssue({
+ title: this.title,
+ labels,
+ subscribed: true,
+ });
+
+ this.list.newIssue(issue)
+ .then(() => {
+ // Need this because our jQuery very kindly disables buttons on ALL form submissions
+ $(this.$refs.submitButton).enable();
+
+ Store.detail.issue = issue;
+ Store.detail.list = this.list;
+ })
+ .catch(() => {
+ // Need this because our jQuery very kindly disables buttons on ALL form submissions
+ $(this.$refs.submitButton).enable();
+
+ // Remove the issue
+ this.list.removeIssue(issue);
+
+ // Show error message
+ this.error = true;
+ });
+
+ this.cancel();
+ },
+ cancel() {
+ this.title = '';
+ gl.IssueBoardsApp.$emit(`hide-issue-form-${this.list.id}`);
+ },
+ },
+ mounted() {
+ this.$refs.input.focus();
+ },
+ template: `
+ <div class="card board-new-issue-form">
+ <form @submit="submit($event)">
+ <div class="flash-container"
+ v-if="error">
+ <div class="flash-alert">
+ An error occured. Please try again.
+ </div>
+ </div>
+ <label class="label-light"
+ :for="list.id + '-title'">
+ Title
+ </label>
+ <input class="form-control"
+ type="text"
+ v-model="title"
+ ref="input"
+ :id="list.id + '-title'" />
+ <div class="clearfix prepend-top-10">
+ <button class="btn btn-success pull-left"
+ type="submit"
+ :disabled="title === ''"
+ ref="submit-button">
+ Submit issue
+ </button>
+ <button class="btn btn-default pull-right"
+ type="button"
+ @click="cancel">
+ Cancel
+ </button>
+ </div>
+ </form>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6
deleted file mode 100644
index b5c14a198ba..00000000000
--- a/app/assets/javascripts/boards/components/board_new_issue.js.es6
+++ /dev/null
@@ -1,64 +0,0 @@
-/* eslint-disable comma-dangle, no-unused-vars */
-/* global Vue */
-/* global ListIssue */
-
-(() => {
- const Store = gl.issueBoards.BoardsStore;
-
- window.gl = window.gl || {};
-
- gl.issueBoards.BoardNewIssue = Vue.extend({
- props: {
- list: Object,
- },
- data() {
- return {
- title: '',
- error: false
- };
- },
- methods: {
- submit(e) {
- e.preventDefault();
- if (this.title.trim() === '') return;
-
- this.error = false;
-
- const labels = this.list.label ? [this.list.label] : [];
- const issue = new ListIssue({
- title: this.title,
- labels,
- subscribed: true
- });
-
- this.list.newIssue(issue)
- .then((data) => {
- // Need this because our jQuery very kindly disables buttons on ALL form submissions
- $(this.$refs.submitButton).enable();
-
- Store.detail.issue = issue;
- Store.detail.list = this.list;
- })
- .catch(() => {
- // Need this because our jQuery very kindly disables buttons on ALL form submissions
- $(this.$refs.submitButton).enable();
-
- // Remove the issue
- this.list.removeIssue(issue);
-
- // Show error message
- this.error = true;
- });
-
- this.cancel();
- },
- cancel() {
- this.title = '';
- this.$parent.showIssueForm = false;
- }
- },
- mounted() {
- this.$refs.input.focus();
- },
- });
-})();
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
index e7c6c063413..cd2bd883d32 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
@@ -69,7 +69,9 @@ const PipelineStore = require('./pipelines_store');
return pipelinesService.all()
.then(response => response.json())
.then((json) => {
- this.store.storePipelines(json);
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = json.pipelines || json;
+ this.store.storePipelines(pipelines);
this.isLoading = false;
})
.catch(() => {
diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6
index e38f7852b1c..b271ea83330 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6
@@ -79,12 +79,12 @@ require('./comparison_pane');
<div class='help-button pull-right'
v-if='!showHelpState'
@click='toggleHelpState(true)'>
- <i class='fa fa-question-circle'></i>
+ <i class='fa fa-question-circle' aria-hidden='true'></i>
</div>
<div class='close-help-button pull-right'
v-if='showHelpState'
@click='toggleHelpState(false)'>
- <i class='fa fa-close'></i>
+ <i class='fa fa-close' aria-hidden='true'></i>
</div>
</div>
<div class='time-tracking-content hide-collapsed'>
diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6
index 1ca01d3bdb9..958a0cc6d50 100644
--- a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6
@@ -39,8 +39,9 @@ require('../../subbable_resource');
listenForSlashCommands() {
$(document).on('ajax:success', '.gfm-form', (e, data) => {
const subscribedCommands = ['spend_time', 'time_estimate'];
- const changedCommands = data.commands_changes;
-
+ const changedCommands = data.commands_changes
+ ? Object.keys(data.commands_changes)
+ : [];
if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) {
this.fetchIssuable();
}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 03504255bda..47fa0f2eb96 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -246,12 +246,21 @@ require('./task_list');
};
Notes.prototype.handleCreateChanges = function(note) {
+ var votesBlock;
if (typeof note === 'undefined') {
return;
}
- if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) {
- $.get(mrRefreshWidgetUrl);
+ if (note.commands_changes) {
+ if ('merge' in note.commands_changes) {
+ $.get(mrRefreshWidgetUrl);
+ }
+
+ if ('emoji_award' in note.commands_changes) {
+ votesBlock = $('.js-awards-block').eq(0);
+ gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.commands_changes.emoji_award);
+ return gl.awardsHandler.scrollToAwards();
+ }
}
};
@@ -262,26 +271,16 @@ require('./task_list');
*/
Notes.prototype.renderNote = function(note) {
- var $notesList, votesBlock;
+ var $notesList;
if (!note.valid) {
- if (note.award) {
- new Flash('You have already awarded this emoji!', 'alert', this.parentTimeline);
- }
- else {
- if (note.errors.commands_only) {
- new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
- this.refresh();
- }
+ if (note.errors.commands_only) {
+ new Flash(note.errors.commands_only, 'notice', this.parentTimeline);
+ this.refresh();
}
return;
}
- if (note.award) {
- votesBlock = $('.js-awards-block').eq(0);
- gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
- return gl.awardsHandler.scrollToAwards();
- // render note if it not present in loaded list
- // or skip if rendered
- } else if (this.isNewNote(note)) {
+
+ if (this.isNewNote(note)) {
this.note_ids.push(note.id);
$notesList = $('ul.main-notes-list');
$notesList.append(note.html).syntaxHighlight();
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index fb8ea18d122..9a0f7a14e57 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,6 +1,7 @@
.calender-block {
padding-left: 0;
padding-right: 0;
+ border-top: 0;
direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 30f242a35db..ffece53a093 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -271,6 +271,7 @@ span.idiff {
font-size: 13px;
line-height: 28px;
display: inline-block;
+ float: none;
}
}
}
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 9c76e58dfc8..09951fe3d3e 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -21,6 +21,7 @@ $dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5;
+$dark-expanded-bg: #3e3e3e;
$dark-c: #969896;
$dark-err: #c66;
$dark-k: #b294bb;
@@ -155,6 +156,22 @@ $dark-il: #de935f;
.line_content.match {
@include dark-diff-match-line;
}
+
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $black;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $dark-expanded-bg;
+ border-color: $dark-expanded-bg;
+ }
+ }
}
// highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 6488a099c74..b6a6d298adf 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5;
+$monokai-expanded-bg: #3e3e3e;
$monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15);
@@ -155,6 +156,22 @@ $monokai-gi: #a6e22e;
.line_content.match {
@include dark-diff-match-line;
}
+
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $black;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $monokai-expanded-bg;
+ border-color: $monokai-expanded-bg;
+ }
+ }
}
// highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 00079cc2b5b..4f7a50dcb4f 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5;
+$solarized-dark-expanded-bg: #010d10;
$solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1;
@@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198;
.line_content.match {
@include dark-diff-match-line;
}
+
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $black;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $solarized-dark-expanded-bg;
+ border-color: $solarized-dark-expanded-bg;
+ }
+ }
}
// highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 2e3b68f1a58..6463fe96c1b 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc;
+$solarized-light-expanded-border: #d2cdbd;
+$solarized-light-expanded-bg: #ece6d4;
$solarized-light-c: #93a1a1;
$solarized-light-err: #586e75;
$solarized-light-g: #586e75;
@@ -166,6 +168,22 @@ $solarized-light-il: #2aa198;
.line_content.match {
@include matchLine;
}
+
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $solarized-light-expanded-border;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $solarized-light-expanded-bg;
+ border-color: $solarized-light-expanded-bg;
+ }
+ }
}
// highlight line via anchor
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 0eca30e570f..ab2018bfbca 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -8,6 +8,8 @@ $white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
+$white-expanded-border: #e0e0e0;
+$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
@@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5;
}
}
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $white-expanded-border;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $white-expanded-bg;
+ border-color: $white-expanded-bg;
+ }
+ }
+
.line_content {
&.old {
background-color: $line-removed;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 339cdcde480..5d0c247dea8 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -133,8 +133,13 @@
width: 35px;
font-weight: normal;
- &:hover {
- text-decoration: underline;
+ &[disabled] {
+ cursor: default;
+
+ &:hover,
+ &:active {
+ text-decoration: none;
+ }
}
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e42e48f87d2..32484f810da 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -72,14 +72,6 @@ class ApplicationController < ActionController::Base
end
end
- def authenticate_user!(*args)
- if redirect_to_home_page_url?
- return redirect_to current_application_settings.home_page_url
- end
-
- super(*args)
- end
-
def log_exception(exception)
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
@@ -287,19 +279,6 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
- def redirect_to_home_page_url?
- # If user is not signed-in and tries to access root_path - redirect him to landing page
- # Don't redirect to the default URL to prevent endless redirections
- return false unless current_application_settings.home_page_url.present?
-
- home_page_url = current_application_settings.home_page_url.chomp('/')
- root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
-
- return false if root_urls.include?(home_page_url)
-
- current_user.nil? && root_path == request.path
- end
-
# U2F (universal 2nd factor) devices need a unique identifier for the application
# to perform authentication.
# https://developers.yubico.com/U2F/App_ID.html
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 39ba815cfca..f9a5ef46786 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -5,7 +5,7 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
- class InvalidPathError < StandardError; end
+ InvalidPathError = Class.new(StandardError)
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index efb95579755..76519022381 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -245,9 +245,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
format.json do
define_pipelines_vars
- render json: PipelineSerializer
+ render json: {
+ pipelines: PipelineSerializer
.new(project: @project, user: @current_user)
.represent(@pipelines)
+ }
end
end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index b033f7b5ea9..5cf3a7f593b 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -148,17 +148,10 @@ class Projects::NotesController < Projects::ApplicationController
def note_json(note)
attrs = {
- award: false,
id: note.id
}
- if note.is_a?(AwardEmoji)
- attrs.merge!(
- valid: note.valid?,
- award: true,
- name: note.name
- )
- elsif note.persisted?
+ if note.persisted?
Banzai::NoteRenderer.render([note], @project, current_user)
attrs.merge!(
@@ -198,7 +191,7 @@ class Projects::NotesController < Projects::ApplicationController
)
end
- attrs[:commands_changes] = note.commands_changes unless attrs[:award]
+ attrs[:commands_changes] = note.commands_changes
attrs
end
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index db2817fadf6..1b4545e4a49 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -8,7 +8,9 @@
# `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController
skip_before_action :authenticate_user!, only: [:index]
- before_action :redirect_to_custom_dashboard, only: [:index]
+
+ before_action :redirect_unlogged_user, if: -> { current_user.nil? }
+ before_action :redirect_logged_user, if: -> { current_user.present? }
def index
super
@@ -16,23 +18,38 @@ class RootController < Dashboard::ProjectsController
private
- def redirect_to_custom_dashboard
- return redirect_to new_user_session_path unless current_user
+ def redirect_unlogged_user
+ if redirect_to_home_page_url?
+ redirect_to(current_application_settings.home_page_url)
+ else
+ redirect_to(new_user_session_path)
+ end
+ end
+ def redirect_logged_user
case current_user.dashboard
when 'stars'
flash.keep
- redirect_to starred_dashboard_projects_path
+ redirect_to(starred_dashboard_projects_path)
when 'project_activity'
- redirect_to activity_dashboard_path
+ redirect_to(activity_dashboard_path)
when 'starred_project_activity'
- redirect_to activity_dashboard_path(filter: 'starred')
+ redirect_to(activity_dashboard_path(filter: 'starred'))
when 'groups'
- redirect_to dashboard_groups_path
+ redirect_to(dashboard_groups_path)
when 'todos'
- redirect_to dashboard_todos_path
- else
- return
+ redirect_to(dashboard_todos_path)
end
end
+
+ def redirect_to_home_page_url?
+ # If user is not signed-in and tries to access root_path - redirect him to landing page
+ # Don't redirect to the default URL to prevent endless redirections
+ return false unless current_application_settings.home_page_url.present?
+
+ home_page_url = current_application_settings.home_page_url.chomp('/')
+ root_urls = [Gitlab.config.gitlab['url'].chomp('/'), root_url.chomp('/')]
+
+ root_urls.exclude?(home_page_url)
+ end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 195094730aa..0b30471f2ae 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -19,7 +19,7 @@ module ButtonHelper
title = data[:title] || 'Copy to clipboard'
data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
- icon('clipboard'),
+ icon('clipboard', 'aria-hidden': 'true'),
class: "btn #{css_class}",
data: data,
type: :button,
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 860a665ae26..c2b399041c6 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -1,6 +1,6 @@
module IssuablesHelper
def sidebar_gutter_toggle_icon
- sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
+ sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
end
def sidebar_gutter_collapsed_class
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 77aba91f65c..f2989eff22d 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -55,15 +55,6 @@ module Ci
pending.unstarted.order('created_at ASC').first
end
- def create_from(build)
- new_build = build.dup
- new_build.status = 'pending'
- new_build.runner_id = nil
- new_build.trigger_request_id = nil
- new_build.token = nil
- new_build.save
- end
-
def retry(build, current_user)
Ci::RetryBuildService
.new(build.project, current_user)
diff --git a/app/models/note.rb b/app/models/note.rb
index d6d5396afa5..4c97e4a986c 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -231,10 +231,6 @@ class Note < ActiveRecord::Base
note =~ /\A#{Banzai::Filter::EmojiFilter.emoji_pattern}\s?\Z/
end
- def award_emoji_name
- note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
- end
-
def to_ability_name
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
end
diff --git a/app/models/project.rb b/app/models/project.rb
index e06fc30dc8a..0c2494d3c32 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -19,7 +19,7 @@ class Project < ActiveRecord::Base
extend Gitlab::ConfigHelper
- class BoardLimitExceeded < StandardError; end
+ BoardLimitExceeded = Class.new(StandardError)
NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 9891f5edf41..539b31780b3 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -7,7 +7,7 @@ class ProjectWiki
'AsciiDoc' => :asciidoc
}.freeze unless defined?(MARKUPS)
- class CouldNotCreateWikiError < StandardError; end
+ CouldNotCreateWikiError = Class.new(StandardError)
# Returns a string describing what went wrong after
# an operation fails.
diff --git a/app/models/user.rb b/app/models/user.rb
index 6fb5ac4a4ef..8443594c055 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -346,7 +346,11 @@ class User < ActiveRecord::Base
# Return (create if necessary) the ghost user. The ghost user
# owns records previously belonging to deleted users.
def ghost
- User.find_by_ghost(true) || create_ghost_user
+ unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u|
+ u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
+ u.state = :blocked
+ u.name = 'Ghost User'
+ end
end
end
@@ -1017,10 +1021,14 @@ class User < ActiveRecord::Base
end
end
- def self.create_ghost_user
- # Since we only want a single ghost user in an instance, we use an
+ def self.unique_internal(scope, username, email_pattern, &b)
+ scope.first || create_unique_internal(scope, username, email_pattern, &b)
+ end
+
+ def self.create_unique_internal(scope, username, email_pattern, &creation_block)
+ # Since we only want a single one of these in an instance, we use an
# exclusive lease to ensure than this block is never run concurrently.
- lease_key = "ghost_user_creation"
+ lease_key = "user:unique_internal:#{username}"
lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
until uuid = lease.try_obtain
@@ -1029,25 +1037,25 @@ class User < ActiveRecord::Base
sleep(1)
end
- # Recheck if a ghost user is already present. One might have been
+ # Recheck if the user is already present. One might have been
# added between the time we last checked (first line of this method)
# and the time we acquired the lock.
- ghost_user = User.find_by_ghost(true)
- return ghost_user if ghost_user.present?
+ existing_user = uncached { scope.first }
+ return existing_user if existing_user.present?
uniquify = Uniquify.new
- username = uniquify.string("ghost") { |s| User.find_by_username(s) }
+ username = uniquify.string(username) { |s| User.find_by_username(s) }
- email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s|
+ email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
User.find_by_email(s)
end
- bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
-
- User.create(
- username: username, password: Devise.friendly_token, bio: bio,
- email: email, name: "Ghost User", state: :blocked, ghost: true
+ scope.create(
+ username: username,
+ password: Devise.friendly_token,
+ email: email,
+ &creation_block
)
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index 2bc6cf3266e..ab2d3d5a3ec 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -1,5 +1,5 @@
class PipelineSerializer < BaseSerializer
- class InvalidResourceError < StandardError; end
+ InvalidResourceError = Class.new(StandardError)
entity PipelineEntity
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index 25e22f14e60..8a9bcd2d053 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -1,7 +1,7 @@
module Commits
class ChangeService < ::BaseService
- class ValidationError < StandardError; end
- class ChangeError < StandardError; end
+ ValidationError = Class.new(StandardError)
+ ChangeError = Class.new(StandardError)
def execute
@start_project = params[:start_project] || @project
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 0a25f56d24c..31869c2f01e 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -1,6 +1,6 @@
module Files
class BaseService < ::BaseService
- class ValidationError < StandardError; end
+ ValidationError = Class.new(StandardError)
def execute
@start_project = params[:start_project] || @project
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index 0609c6219e7..700f9f4f6f0 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -1,6 +1,6 @@
module Files
class MultiService < Files::BaseService
- class FileChangedError < StandardError; end
+ FileChangedError = Class.new(StandardError)
ACTIONS = %w[create update delete move].freeze
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 54e1aaf3f67..fbbab97632e 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -1,6 +1,6 @@
module Files
class UpdateService < Files::BaseService
- class FileChangedError < StandardError; end
+ FileChangedError = Class.new(StandardError)
def commit
repository.update_file(current_user, @file_path, @file_content,
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 9500faf2862..b618c3e038e 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -203,6 +203,7 @@ class IssuableBaseService < BaseService
change_state(issuable)
change_subscription(issuable)
change_todo(issuable)
+ toggle_award(issuable)
filter_params(issuable)
old_labels = issuable.labels.to_a
old_mentioned_users = issuable.mentioned_users.to_a
@@ -263,6 +264,14 @@ class IssuableBaseService < BaseService
end
end
+ def toggle_award(issuable)
+ award = params.delete(:emoji_award)
+ if award
+ todo_service.new_award_emoji(issuable, current_user)
+ issuable.toggle_award_emoji(award, current_user)
+ end
+ end
+
def has_changes?(issuable, old_labels: [])
valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch]
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index a2a5f57d069..711f4035c55 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -1,6 +1,6 @@
module Issues
class MoveService < Issues::BaseService
- class MoveError < StandardError; end
+ MoveError = Class.new(StandardError)
def execute(issue, new_project)
@old_issue = issue
diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb
index d22a1d3e0ad..82cd89d9a0b 100644
--- a/app/services/merge_requests/resolve_service.rb
+++ b/app/services/merge_requests/resolve_service.rb
@@ -1,7 +1,6 @@
module MergeRequests
class ResolveService < MergeRequests::BaseService
- class MissingFiles < Gitlab::Conflict::ResolutionError
- end
+ MissingFiles = Class.new(Gitlab::Conflict::ResolutionError)
attr_accessor :conflicts, :rugged, :merge_index, :merge_request
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index b4f8b33d564..61d66a26932 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -8,14 +8,6 @@ module Notes
note.author = current_user
note.system = false
- if note.award_emoji?
- noteable = note.noteable
- if noteable.user_can_award?(current_user, note.award_emoji_name)
- todo_service.new_award_emoji(noteable, current_user)
- return noteable.create_award_emoji(note.award_emoji_name, current_user)
- end
- end
-
# We execute commands (extracted from `params[:note]`) on the noteable
# **before** we save the note because if the note consists of commands
# only, there is no need be create a note!
@@ -48,7 +40,7 @@ module Notes
note.errors.add(:commands_only, 'Commands applied')
end
- note.commands_changes = command_params.keys
+ note.commands_changes = command_params
end
note
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 2e06826c311..a7142d5950e 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -2,7 +2,7 @@ module Projects
class DestroyService < BaseService
include Gitlab::ShellAdapter
- class DestroyError < StandardError; end
+ DestroyError = Class.new(StandardError)
DELETED_FLAG = '+deleted'.freeze
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index cd230528743..1c5a549feb9 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -2,7 +2,7 @@ module Projects
class ImportService < BaseService
include Gitlab::ShellAdapter
- class Error < StandardError; end
+ Error = Class.new(StandardError)
def execute
add_repository_to_project unless project.gitlab_project_import?
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 20dfbddc823..da6e6acd4a7 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -9,7 +9,7 @@
module Projects
class TransferService < BaseService
include Gitlab::ShellAdapter
- class TransferError < StandardError; end
+ TransferError = Class.new(StandardError)
def execute(new_namespace)
if allowed_transfer?(current_user, project, new_namespace)
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index 33a94d9731e..595653ea58a 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -255,6 +255,18 @@ module SlashCommands
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
end
+ desc 'Toggle emoji reward'
+ params ':emoji:'
+ condition do
+ issuable.persisted?
+ end
+ command :award do |emoji|
+ name = award_emoji_name(emoji)
+ if name && issuable.user_can_award?(current_user, name)
+ @updates[:emoji_award] = name
+ end
+ end
+
desc 'Set time estimate'
params '<1w 3d 2h 14m>'
condition do
@@ -329,5 +341,10 @@ module SlashCommands
ext.references(type)
end
+
+ def award_emoji_name(emoji)
+ match = emoji.match(Banzai::Filter::EmojiFilter.emoji_pattern)
+ match[1] if match
+ end
end
end
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index c28661c2351..555ec8ad079 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -55,8 +55,6 @@
= link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
%li
= link_to "Settings", profile_path, aria: { label: "Settings" }
- %li
- = link_to "Help", help_path, aria: { label: "Help" }
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" }
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5d4178f03d7..4c9749205de 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -36,4 +36,4 @@
Snippets
%li.divider
%li
- = link_to "About GitLab CE", help_path, title: 'About GitLab CE', class: 'about-gitlab'
+ = link_to "Help", help_path, title: 'About GitLab CE', class: 'about-gitlab'
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 7b9cfbbd067..c44d8fcd430 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -1,7 +1,8 @@
-.btn-group
- = view_on_environment_button(@commit.sha, @path, @environment) if @environment
+- if @environment
+ .btn-group<
+ = view_on_environment_button(@commit.sha, @path, @environment)
-.btn-group.tree-btn-group
+.btn-group{ role: "group" }<
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
@@ -18,7 +19,7 @@
tree_join(@commit.sha, @path)), class: 'btn btn-sm js-data-file-blob-permalink-url'
- if current_user
- .btn-group{ role: "group" }
+ .btn-group{ role: "group" }<
- if blob_text_viewable?(@blob)
= edit_blob_link
= replace_blob_link
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 19fa4c78501..41a7191302d 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -24,12 +24,13 @@
#blob-content-holder.blob-content-holder
%article.file-holder
- .js-file-title.file-title
- = blob_icon blob.mode, blob.name
- %strong
- = blob.name
- %small
- = number_to_human_size(blob_size(blob))
+ .js-file-title.file-title-flex-parent
+ .file-header-content
+ = blob_icon blob.mode, blob.name
+ %strong.file-title-name
+ = blob.name
+ %small
+ = number_to_human_size(blob_size(blob))
.file-actions.hidden-xs
= render "actions"
= render blob, blob: blob
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index c48d9dd982c..d1d448f0d4c 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -9,20 +9,20 @@
- line_old = line_new - @form.offset
- line_content = capture do
%td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line}
- %tr.line_holder{ id: line_old, class: line_class }
+ %tr.line_holder.diff-expanded{ id: line_old, class: line_class }
- case diff_view
- when :inline
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- %a{ href: "##{line_old}", data: { linenumber: line_old } }
+ %a{ href: "#", data: { linenumber: line_old }, disabled: true }
%td.new_line.diff-line-num{ data: { linenumber: line_new } }
- %a{ href: "##{line_new}", data: { linenumber: line_new } }
+ %a{ href: "#", data: { linenumber: line_new }, disabled: true }
= line_content
- when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- %a{ href: "##{line_old}", data: { linenumber: line_old } }
+ %a{ href: "##{line_old}", data: { linenumber: line_old }, disabled: true }
= line_content
%td.new_line.diff-line-num{ data: { linenumber: line_new } }
- %a{ href: "##{line_new}", data: { linenumber: line_new } }
+ %a{ href: "##{line_new}", data: { linenumber: line_new }, disabled: true }
= line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
diff --git a/app/views/projects/boards/components/_board_list.html.haml b/app/views/projects/boards/components/_board_list.html.haml
index f413a5e94c1..0993e880da9 100644
--- a/app/views/projects/boards/components/_board_list.html.haml
+++ b/app/views/projects/boards/components/_board_list.html.haml
@@ -2,28 +2,8 @@
.board-list-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
- if can? current_user, :create_issue, @project
- %board-new-issue{ "inline-template" => true,
- ":list" => "list",
+ %board-new-issue{ ":list" => "list",
"v-if" => 'list.type !== "done" && showIssueForm' }
- .card.board-new-issue-form
- %form{ "@submit" => "submit($event)" }
- .flash-container{ "v-if" => "error" }
- .flash-alert
- An error occured. Please try again.
- %label.label-light{ ":for" => 'list.id + "-title"' }
- Title
- %input.form-control{ type: "text",
- "v-model" => "title",
- "ref" => "input",
- ":id" => 'list.id + "-title"' }
- .clearfix.prepend-top-10
- %button.btn.btn-success.pull-left{ type: "submit",
- ":disabled" => 'title === ""',
- "ref" => "submit-button" }
- Submit issue
- %button.btn.btn-default.pull-right{ type: "button",
- "@click" => "cancel" }
- Cancel
%ul.board-list{ "ref" => "list",
"v-show" => "!loading",
":data-board" => "list.id",
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 6c730e16f67..0f8c4318a2d 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -9,16 +9,16 @@
- if current_user
%span.issuable-header-text.hide-collapsed.pull-left
Todo
- %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } }
+ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => "Toggle sidebar" }
= sidebar_gutter_toggle_icon
- if current_user
- %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } }
+ %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", "aria-label" => (todo.nil? ? "Add todo" : "Mark done"), data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } }
%span.js-issuable-todo-text
- if todo
Mark done
- else
Add todo
- = icon('spin spinner', class: 'hidden js-issuable-todo-loading')
+ = icon('spin spinner', class: 'hidden js-issuable-todo-loading', 'aria-hidden': 'true')
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
.block.assignee
@@ -26,10 +26,10 @@
- if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 24)
- else
- = icon('user')
+ = icon('user', 'aria-hidden': 'true')
.title.hide-collapsed
Assignee
- = icon('spinner spin', class: 'block-loading')
+ = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
@@ -37,7 +37,7 @@
= link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' }
- = icon('exclamation-triangle')
+ = icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username
= issuable.assignee.to_reference
- else
@@ -54,7 +54,7 @@
.block.milestone
.sidebar-collapsed-icon
- = icon('clock-o')
+ = icon('clock-o', 'aria-hidden': 'true')
%span
- if issuable.milestone
%span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } }
@@ -63,7 +63,7 @@
None
.title.hide-collapsed
Milestone
- = icon('spinner spin', class: 'block-loading')
+ = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
@@ -81,16 +81,16 @@
// Fallback while content is loading
.title.hide-collapsed
Time tracking
- = icon('spinner spin')
+ = icon('spinner spin', 'aria-hidden': 'true')
- if issuable.has_attribute?(:due_date)
.block.due_date
.sidebar-collapsed-icon
- = icon('calendar')
+ = icon('calendar', 'aria-hidden': 'true')
%span.js-due-date-sidebar-value
= issuable.due_date.try(:to_s, :medium) || 'None'
.title.hide-collapsed
Due date
- = icon('spinner spin', class: 'block-loading')
+ = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
@@ -110,7 +110,7 @@
.dropdown
%button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } }
%span.dropdown-toggle-text Due date
- = icon('chevron-down')
+ = icon('chevron-down', 'aria-hidden': 'true')
.dropdown-menu.dropdown-menu-due-date
= dropdown_title('Due date')
= dropdown_content do
@@ -120,12 +120,12 @@
- selected_labels = issuable.labels
.block.labels
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
- = icon('tags')
+ = icon('tags', 'aria-hidden': 'true')
%span
= selected_labels.size
.title.hide-collapsed
Labels
- = icon('spinner spin', class: 'block-loading')
+ = icon('spinner spin', class: 'block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
@@ -141,7 +141,7 @@
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
- = icon('chevron-down')
+ = icon('chevron-down', 'aria-hidden': 'true')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
- if can? current_user, :admin_label, @project and @project
@@ -152,7 +152,7 @@
- subscribed = issuable.subscribed?(current_user, @project)
.block.light.subscription{ data: { url: toggle_subscription_path(issuable) } }
.sidebar-collapsed-icon
- = icon('rss')
+ = icon('rss', 'aria-hidden': 'true')
%span.issuable-header-text.hide-collapsed.pull-left
Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
diff --git a/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml b/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml
new file mode 100644
index 00000000000..a53e7d77c16
--- /dev/null
+++ b/changelogs/unreleased/22951-fix-todos-api-endpoint-error-for-commits.yml
@@ -0,0 +1,4 @@
+---
+title: Add spec for todo with target_type Commit
+merge_request: 9351
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/25437-just-emoji.yml b/changelogs/unreleased/25437-just-emoji.yml
new file mode 100644
index 00000000000..ceb81a47f2d
--- /dev/null
+++ b/changelogs/unreleased/25437-just-emoji.yml
@@ -0,0 +1,4 @@
+---
+title: Introduce /award slash command; Allow posting of just an emoji in comment
+merge_request: 9382
+author: mhasbini
diff --git a/changelogs/unreleased/26136-list-repository-tree-api-doc.yml b/changelogs/unreleased/26136-list-repository-tree-api-doc.yml
new file mode 100644
index 00000000000..85d8bc6ca8a
--- /dev/null
+++ b/changelogs/unreleased/26136-list-repository-tree-api-doc.yml
@@ -0,0 +1,4 @@
+---
+title: Make documentation of list repository tree API call more detailed
+merge_request: 9532
+author: Marius Kleiner
diff --git a/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml b/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml
new file mode 100644
index 00000000000..ee236310a71
--- /dev/null
+++ b/changelogs/unreleased/27142-api-replace-destroy-with-stop-environment.yml
@@ -0,0 +1,4 @@
+---
+title: API: Add environment stop action
+merge_request: 8808
+author:
diff --git a/changelogs/unreleased/27778-a11y-sidebar.yml b/changelogs/unreleased/27778-a11y-sidebar.yml
new file mode 100644
index 00000000000..fb37d7fdb35
--- /dev/null
+++ b/changelogs/unreleased/27778-a11y-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Improves a11y in sidebar by adding aria-hidden attributes in i tags and by
+ fixing two broken aria-hidden attributes
+merge_request:
+author:
diff --git a/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml b/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml
new file mode 100644
index 00000000000..baf832d4495
--- /dev/null
+++ b/changelogs/unreleased/28609-fix-redirect-to-home-page-url.yml
@@ -0,0 +1,4 @@
+---
+title: Fix the redirect to custom home page URL
+merge_request: 9518
+author:
diff --git a/changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml b/changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml
new file mode 100644
index 00000000000..e38e5d0db5b
--- /dev/null
+++ b/changelogs/unreleased/28696-improve-grammar-gitlab-flow-doc.yml
@@ -0,0 +1,4 @@
+---
+title: Improve grammar in GitLab flow documentation
+merge_request: 9552
+author: infogrind
diff --git a/changelogs/unreleased/28837-remove-help-duplicate.yml b/changelogs/unreleased/28837-remove-help-duplicate.yml
new file mode 100644
index 00000000000..b1001245663
--- /dev/null
+++ b/changelogs/unreleased/28837-remove-help-duplicate.yml
@@ -0,0 +1,4 @@
+---
+title: Remove help link from right dropdown
+merge_request:
+author:
diff --git a/changelogs/unreleased/28850-fix-broken-migration.yml b/changelogs/unreleased/28850-fix-broken-migration.yml
new file mode 100644
index 00000000000..7f59a7708bc
--- /dev/null
+++ b/changelogs/unreleased/28850-fix-broken-migration.yml
@@ -0,0 +1,4 @@
+---
+title: Fix broken migration when upgrading straight to 8.17.1
+merge_request: 9613
+author:
diff --git a/changelogs/unreleased/diff-make-obvious-cant-comment.yml b/changelogs/unreleased/diff-make-obvious-cant-comment.yml
new file mode 100644
index 00000000000..2cb95947939
--- /dev/null
+++ b/changelogs/unreleased/diff-make-obvious-cant-comment.yml
@@ -0,0 +1,4 @@
+---
+title: Visually show expanded diff lines cant have comments
+merge_request:
+author:
diff --git a/changelogs/unreleased/long-file-name-overflow.yml b/changelogs/unreleased/long-file-name-overflow.yml
new file mode 100644
index 00000000000..7ccf05491e1
--- /dev/null
+++ b/changelogs/unreleased/long-file-name-overflow.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed long file names overflowing under action buttons
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-new-relic-gem.yml b/changelogs/unreleased/remove-new-relic-gem.yml
new file mode 100644
index 00000000000..b15ecd3e4e7
--- /dev/null
+++ b/changelogs/unreleased/remove-new-relic-gem.yml
@@ -0,0 +1,4 @@
+---
+title: Remove the newrelic gem
+merge_request: 9622
+author: Robert Schilling
diff --git a/changelogs/unreleased/use-v3-api-on-frontend.yml b/changelogs/unreleased/use-v3-api-on-frontend.yml
new file mode 100644
index 00000000000..467ad3c8276
--- /dev/null
+++ b/changelogs/unreleased/use-v3-api-on-frontend.yml
@@ -0,0 +1,4 @@
+---
+title: Make projects dropdown only show projects you are a member of
+merge_request: 9614
+author:
diff --git a/changelogs/unreleased/user-calendar-border.yml b/changelogs/unreleased/user-calendar-border.yml
new file mode 100644
index 00000000000..8ebcca83256
--- /dev/null
+++ b/changelogs/unreleased/user-calendar-border.yml
@@ -0,0 +1,4 @@
+---
+title: Removed top border from user contribution calendar
+merge_request:
+author:
diff --git a/config/newrelic.yml b/config/newrelic.yml
deleted file mode 100644
index 9ef922a38d9..00000000000
--- a/config/newrelic.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-# New Relic configuration file
-#
-# This file is here to make sure the New Relic gem stays
-# quiet by default.
-#
-# To enable and configure New Relic, please use
-# environment variables, e.g. NEW_RELIC_ENABLED=true
-
-production:
- enabled: false
-
-development:
- enabled: false
-
-test:
- enabled: false
diff --git a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb
index 84954b1ef64..603efc43782 100644
--- a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb
+++ b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb
@@ -1,10 +1,8 @@
class DisableInvalidServiceTemplates < ActiveRecord::Migration
DOWNTIME = false
- unless defined?(Service)
- class Service < ActiveRecord::Base
- self.inheritance_column = nil
- end
+ class Service < ActiveRecord::Base
+ self.inheritance_column = nil
end
def up
diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md
index e510f723e26..61677569254 100644
--- a/doc/api/enviroments.md
+++ b/doc/api/enviroments.md
@@ -33,7 +33,7 @@ Example response:
Creates a new environment with the given name and external_url.
-It returns 201 if the environment was successfully created, 400 for wrong parameters.
+It returns `201` if the environment was successfully created, `400` for wrong parameters.
```
POST /projects/:id/environment
@@ -64,7 +64,7 @@ Example response:
Updates an existing environment's name and/or external_url.
-It returns 200 if the environment was successfully updated. In case of an error, a status code 400 is returned.
+It returns `200` if the environment was successfully updated. In case of an error, a status code `400` is returned.
```
PUT /projects/:id/environments/:environments_id
@@ -94,7 +94,7 @@ Example response:
## Delete an environment
-It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist.
+It returns `200` if the environment was successfully deleted, and `404` if the environment does not exist.
```
DELETE /projects/:id/environments/:environment_id
@@ -108,3 +108,31 @@ DELETE /projects/:id/environments/:environment_id
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1"
```
+
+## Stop an environment
+
+It returns `200` if the environment was successfully stopped, and `404` if the environment does not exist.
+
+```
+POST /projects/:id/environments/:environment_id/stop
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the project |
+| `environment_id` | integer | yes | The ID of the environment |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1/stop"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "deploy",
+ "slug": "deploy",
+ "external_url": "https://deploy.example.gitlab.com"
+}
+```
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 727617f1ecc..ddd11bb2a14 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -5,6 +5,8 @@
Get a list of repository files and directories in a project. This endpoint can
be accessed without authentication if the repository is publicly accessible.
+This command provides essentially the same functionality as the `git ls-tree` command. For more information, see the section _Tree Objects_ in the [Git internals documentation](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects/#_tree_objects).
+
```
GET /projects/:id/repository/tree
```
diff --git a/doc/api/tags.md b/doc/api/tags.md
index abeb4bfb40e..4a2c9720b8a 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -26,7 +26,7 @@ Parameters:
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
- "parents_ids": [
+ "parent_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
@@ -110,7 +110,7 @@ Parameters:
"committer_email": "jack@example.com",
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"message": "Initial commit",
- "parents_ids": [
+ "parent_ids": [
"2a4b78934375d7f53875269ffd4f45fd83a84ebe"
]
},
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 7b65cab3bf5..538fe800fee 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -30,6 +30,7 @@ changes are in V4:
- Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` [!9410](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9410)
- Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters [!8962](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8962)
- Return pagination headers for all endpoints that return an array [!8606](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8606)
+- Added `POST /environments/:environment_id/stop` to stop an environment [!8808](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8808)
- Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead [!9366](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9366)
- Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` [!9371](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9371)
- Make subscription API more RESTful. Use `post ":project_id/:subscribable_type/:subscribable_id/subscribe"` to subscribe and `post ":project_id/:subscribable_type/:subscribable_id/unsubscribe"` to unsubscribe from a resource. [!9325](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9325)
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 620d4744685..04c0af44237 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -131,6 +131,16 @@ job_name:
variables: []
```
+You are able to use other variables inside your variable definition (or escape them with `$$`):
+
+```yaml
+variables:
+ LS_CMD: 'ls $FLAGS $$TMP_DIR'
+ FLAGS: '-al'
+script:
+ - 'eval $LS_CMD' # will execute 'ls -al $TMP_DIR'
+```
+
## Secret variables
>**Notes:**
diff --git a/doc/development/ux_guide/img/karolina-plaskaty.png b/doc/development/ux_guide/img/karolina-plaskaty.png
new file mode 100644
index 00000000000..2e356c99762
--- /dev/null
+++ b/doc/development/ux_guide/img/karolina-plaskaty.png
Binary files differ
diff --git a/doc/development/ux_guide/img/nazim-ramesh.png b/doc/development/ux_guide/img/nazim-ramesh.png
new file mode 100644
index 00000000000..01ba0391630
--- /dev/null
+++ b/doc/development/ux_guide/img/nazim-ramesh.png
Binary files differ
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index 137154e24f3..cbd7c17de41 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -14,7 +14,7 @@
### Nazim Ramesh
- Small to medium size organisations using GitLab CE
-<img src="img/steven-lyons.png" width="300px">
+<img src="img/nazim-ramesh.png" width="300px">
#### Demographics
@@ -131,7 +131,7 @@ James and his team use CI quite heavily for several projects. Whilst they’ve w
- Would like to use GitLab at work
- Working for a medium to large size organisation
-<img src="img/harry-robison.png" width="300px">
+<img src="img/karolina-plaskaty.png" width="300px">
#### Demographics
diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png
index a19f0e57b56..8d7a69e55ed 100644
--- a/doc/gitlab-basics/img/create_new_project_button.png
+++ b/doc/gitlab-basics/img/create_new_project_button.png
Binary files differ
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 5ba338ba7d1..bb4141c6cd3 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -155,10 +155,9 @@ page](https://golang.org/dl).
## 4. Node
Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile
-javascript assets, and starting in GitLab 9.0, yarn >= v0.17.0 is required to
-manage javascript dependencies. In many distros the versions provided by the
-official package repositories are out of date, so we'll need to install through
-the following commands:
+javascript assets, and yarn >= v0.17.0 to manage javascript dependencies.
+In many distros the versions provided by the official package repositories
+are out of date, so we'll need to install through the following commands:
# install node v7.x
curl --location https://deb.nodesource.com/setup_7.x | bash -
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index a5b8cd6455c..96ec1b178b6 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -38,23 +38,6 @@ If you are running GitLab within a Docker container, you can run the backup from
docker exec -t <container name> gitlab-rake gitlab:backup:create
```
-You can specify that portions of the application data be skipped using the
-environment variable `SKIP`. You can skip:
-
-- `db` (database)
-- `uploads` (attachments)
-- `repositories` (Git repositories data)
-- `builds` (CI job output logs)
-- `artifacts` (CI job artifacts)
-- `lfs` (LFS objects)
-- `registry` (Container Registry images)
-
-Separate multiple data types to skip using a comma. For example:
-
-```
-sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
-```
-
Example output:
```
@@ -111,13 +94,14 @@ To use the `copy` strategy instead of the default streaming strategy, specify
You can choose what should be backed up by adding the environment variable `SKIP`.
The available options are:
-* `db`
-* `uploads` (attachments)
-* `repositories`
-* `builds` (CI build output logs)
-* `artifacts` (CI build artifacts)
-* `lfs` (LFS objects)
-* `pages` (pages content)
+- `db` (database)
+- `uploads` (attachments)
+- `repositories` (Git repositories data)
+- `builds` (CI job output logs)
+- `artifacts` (CI job artifacts)
+- `lfs` (LFS objects)
+- `registry` (Container Registry images)
+- `pages` (Pages content)
Use a comma to specify several options at the same time:
@@ -416,7 +400,7 @@ sudo gitlab-rake gitlab:check SANITIZE=true
If there is a GitLab version mismatch between your backup tar file and the installed
version of GitLab, the restore command will abort with an error. Install the
-[correct GitLab version](https://www.gitlab.com/downloads/archives/) and try again.
+[correct GitLab version](https://about.gitlab.com/downloads/archives/) and try again.
## Configure cron to make daily backups
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
new file mode 100644
index 00000000000..6edf99239ea
--- /dev/null
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -0,0 +1,382 @@
+# GitLab Pages from A to Z: Part 4
+
+- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md)
+- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
+- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md)
+- **Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages**
+
+## Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages
+
+[GitLab CI](https://about.gitlab.com/gitlab-ci/) serves
+numerous purposes, to build, test, and deploy your app
+from GitLab through
+[Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
+methods. You will need it to build your website with GitLab Pages,
+and deploy it to the Pages server.
+
+What this file actually does is telling the
+[GitLab Runner](https://docs.gitlab.com/runner/) to run scripts
+as you would do from the command line. The Runner acts as your
+terminal. GitLab CI tells the Runner which commands to run.
+Both are built-in in GitLab, and you don't need to set up
+anything for them to work.
+
+Explaining [every detail of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
+and GitLab Runner is out of the scope of this guide, but we'll
+need to understand just a few things to be able to write our own
+`.gitlab-ci.yml` or tweak an existing one. It's an
+[Yaml](http://docs.ansible.com/ansible/YAMLSyntax.html) file,
+with its own syntax. You can always check your CI syntax with
+the [GitLab CI Lint Tool](https://gitlab.com/ci/lint).
+
+**Practical Example:**
+
+Let's consider you have a [Jekyll](https://jekyllrb.com/) site.
+To build it locally, you would open your terminal, and run `jekyll build`.
+Of course, before building it, you had to install Jekyll in your computer.
+For that, you had to open your terminal and run `gem install jekyll`.
+Right? GitLab CI + GitLab Runner do the same thing. But you need to
+write in the `.gitlab-ci.yml` the script you want to run so
+GitLab Runner will do it for you. It looks more complicated then it
+is. What you need to tell the Runner:
+
+```
+$ gem install jekyll
+$ jekyll build
+```
+
+### Script
+
+To transpose this script to Yaml, it would be like this:
+
+```yaml
+script:
+ - gem install jekyll
+ - jekyll build
+```
+
+### Job
+
+So far so good. Now, each `script`, in GitLab is organized by
+a `job`, which is a bunch of scripts and settings you want to
+apply to that specific task.
+
+```yaml
+job:
+ script:
+ - gem install jekyll
+ - jekyll build
+```
+
+For GitLab Pages, this `job` has a specific name, called `pages`,
+which tells the Runner you want that task to deploy your website
+with GitLab Pages:
+
+```yaml
+pages:
+ script:
+ - gem install jekyll
+ - jekyll build
+```
+
+### The `public` directory
+
+We also need to tell Jekyll where do you want the website to build,
+and GitLab Pages will only consider files in a directory called `public`.
+To do that with Jekyll, we need to add a flag specifying the
+[destination (`-d`)](https://jekyllrb.com/docs/usage/) of the
+built website: `jekyll build -d public`. Of course, we need
+to tell this to our Runner:
+
+```yaml
+pages:
+ script:
+ - gem install jekyll
+ - jekyll build -d public
+```
+
+### Artifacts
+
+We also need to tell the Runner that this _job_ generates
+_artifacts_, which is the site built by Jekyll.
+Where are these artifacts stored? In the `public` directory:
+
+```yaml
+pages:
+ script:
+ - gem install jekyll
+ - jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+The script above would be enough to build your Jekyll
+site with GitLab Pages. But, from Jekyll 3.4.0 on, its default
+template originated by `jekyll new project` requires
+[Bundler](http://bundler.io/) to install Jekyll dependencies
+and the default theme. To adjust our script to meet these new
+requirements, we only need to install and build Jekyll with Bundler:
+
+```yaml
+pages:
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+That's it! A `.gitlab-ci.yml` with the content above would deploy
+your Jekyll 3.4.0 site with GitLab Pages. This is the minimum
+configuration for our example. On the steps below, we'll refine
+the script by adding extra options to our GitLab CI.
+
+### Image
+
+At this point, you probably ask yourself: "okay, but to install Jekyll
+I need Ruby. Where is Ruby on that script?". The answer is simple: the
+first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a
+[Docker](https://www.docker.com/) image specifying what do you need in
+your container to run that script:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+```
+
+In this case, you're telling the Runner to pull this image, which
+contains Ruby 2.3 as part of its file system. When you don't specify
+this image in your configuration, the Runner will use a default
+image, which is Ruby 2.1.
+
+If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll
+need to specify which image you want to use, and this image should
+contain NodeJS as part of its file system. E.g., for a
+[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:4.2.2`.
+
+>**Note:**
+We're not trying to explain what a Docker image is,
+we just need to introduce the concept with a minimum viable
+explanation. To know more about Docker images, please visit
+their website or take a look at a
+[summarized explanation](http://paislee.io/how-to-automate-docker-deployments/) here.
+
+Let's go a little further.
+
+### Branching
+
+If you use GitLab as a version control platform, you will have your
+branching strategy to work on your project. Meaning, you will have
+other branches in your project, but you'll want only pushes to the
+default branch (usually `master`) to be deployed to your website.
+To do that, we need to add another line to our CI, telling the Runner
+to only perform that _job_ called `pages` on the `master` branch `only`:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+### Stages
+
+Another interesting concept to keep in mind are build stages.
+Your web app can pass through a lot of tests and other tasks
+until it's deployed to staging or production environments.
+There are three default stages on GitLab CI: build, test,
+and deploy. To specify which stage your _job_ is running,
+simply add another line to your CI:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ stage: deploy
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+You might ask yourself: "why should I bother with stages
+at all?" Well, let's say you want to be able to test your
+script and check the built site before deploying your site
+to production. You want to run the test exactly as your
+script will do when you push to `master`. It's simple,
+let's add another task (_job_) to our CI, telling it to
+test every push to other branches, `except` the `master` branch:
+
+```yaml
+image: ruby:2.3
+
+pages:
+ stage: deploy
+ script:
+ - bundle install
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+
+test:
+ stage: test
+ script:
+ - bundle install
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+```
+
+The `test` job is running on the stage `test`, Jekyll
+will build the site in a directory called `test`, and
+this job will affect all the branches except `master`.
+
+The best benefit of applying _stages_ to different
+_jobs_ is that every job in the same stage builds in
+parallel. So, if your web app needs more than one test
+before being deployed, you can run all your test at the
+same time, it's not necessary to wait one test to finish
+to run the other. Of course, this is just a brief
+introduction of GitLab CI and GitLab Runner, which are
+tools much more powerful than that. This is what you
+need to be able to create and tweak your builds for
+your GitLab Pages site.
+
+### Before Script
+
+To avoid running the same script multiple times across
+your _jobs_, you can add the parameter `before_script`,
+in which you specify which commands you want to run for
+every single _job_. In our example, notice that we run
+`bundle install` for both jobs, `pages` and `test`.
+We don't need to repeat it:
+
+```yaml
+image: ruby:2.3
+
+before_script:
+ - bundle install
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+```
+
+### Caching Dependencies
+
+If you want to cache the installation files for your
+projects dependencies, for building faster, you can
+use the parameter `cache`. For this example, we'll
+cache Jekyll dependencies in a `vendor` directory
+when we run `bundle install`:
+
+```yaml
+image: ruby:2.3
+
+cache:
+ paths:
+ - vendor/
+
+before_script:
+ - bundle install --path vendor
+
+pages:
+ stage: deploy
+ script:
+ - bundle exec jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+
+test:
+ stage: test
+ script:
+ - bundle exec jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+```
+
+For this specific case, we need to exclude `/vendor`
+from Jekyll `_config.yml` file, otherwise Jekyll will
+understand it as a regular directory to build
+together with the site:
+
+```yml
+exclude:
+ - vendor
+```
+
+There we go! Now our GitLab CI not only builds our website,
+but also **continuously test** pushes to feature-branches,
+**caches** dependencies installed with Bundler, and
+**continuously deploy** every push to the `master` branch.
+
+## Advanced GitLab CI for GitLab Pages
+
+What you can do with GitLab CI is pretty much up to your
+creativity. Once you get used to it, you start creating
+awesome scripts that automate most of tasks you'd do
+manually in the past. Read through the
+[documentation of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
+to understand how to go even further on your scripts.
+
+- On this blog post, understand the concept of
+[using GitLab CI `environments` to deploy your
+web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/).
+- On this post, learn [how to run jobs sequentially,
+in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
+- On this blog post, we go through the process of
+[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+to deploy this website you're looking at, docs.gitlab.com.
+- On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
+
+|||
+|:--|--:|
+|[**← Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates**](getting_started_part_three.md)||
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index c5b1aa4b654..582a4afbab4 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -1,15 +1,28 @@
# GitLab Pages from A to Z: Part 1
-- **Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates**
-- _[Part 2: Quick Start Guide - Setting Up GitLab Pages](getting_started_part_two.md)_
-- _[Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)_
+- **Part 1: Static sites and GitLab Pages domains**
+- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
+- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md)
+- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
-----
+## GitLab Pages form A to Z
This is a comprehensive guide, made for those who want to
publish a website with GitLab Pages but aren't familiar with
the entire process involved.
+This [first part](#what-you-need-to-know-before-getting-started) of this series will present you to the concepts of
+static sites, and go over how the default Pages domains work.
+
+The [second part](getting_started_part_two.md) covers how to get started with GitLab Pages: deploy
+a website from a forked project or create a new one from scratch.
+
+The [third part](getting_started_part_three.md) will show you how to set up a custom domain or subdomain
+to your site already deployed.
+
+The [fourth part](getting_started_part_four.md) will show you how to create and tweak GitLab CI for
+GitLab Pages.
+
To **enable** GitLab Pages for GitLab CE (Community Edition)
and GitLab EE (Enterprise Edition), please read the
[admin documentation](https://docs.gitlab.com/ce/administration/pages/index.html),
@@ -33,7 +46,7 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/)
to simplify your code and build the static site for you,
which is highly recommendable and much faster than hardcoding.
----
+#### Further Reading
- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
@@ -88,179 +101,6 @@ your website will be published under `https://websites.gitlab.io`.
- On your GitLab instance, replace `gitlab.io` above with your
Pages server domain. Ask your sysadmin for this information.
-### DNS Records
-
-A Domain Name System (DNS) web service routes visitors to websites
-by translating domain names (such as `www.example.com`) into the
-numeric IP addresses (such as `192.0.2.1`) that computers use to
-connect to each other.
-
-A DNS record is created to point a (sub)domain to a certain location,
-which can be an IP address or another domain. In case you want to use
-GitLab Pages with your own (sub)domain, you need to access your domain's
-registrar control panel to add a DNS record pointing it back to your
-GitLab Pages site.
-
-Note that **how to** add DNS records depends on which server your domain
-is hosted on. Every control panel has its own place to do it. If you are
-not an admin of your domain, and don't have access to your registrar,
-you'll need to ask for the technical support of your hosting service
-to do it for you.
-
-To help you out, we've gathered some instructions on how to do that
-for the most popular hosting services:
-
-- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html)
-- [Bluehost](https://my.bluehost.com/cgi/help/559)
-- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-)
-- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone)
-- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-)
-- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238)
-- [Hostgator](http://support.hostgator.com/articles/changing-dns-records)
-- [Inmotion hosting](https://my.bluehost.com/cgi/help/559)
-- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain)
-- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx)
-
-If your hosting service is not listed above, you can just try to
-search the web for "how to add dns record on <my hosting service>".
-
-#### DNS A record
-
-In case you want to point a root domain (`example.com`) to your
-GitLab Pages site, deployed to `namespace.gitlab.io`, you need to
-log into your domain's admin control panel and add a DNS `A` record
-pointing your domain to Pages' server IP address. For projects on
-GitLab.com, this IP is `104.208.235.32`. For projects leaving in
-other GitLab instances (CE or EE), please contact your sysadmin
-asking for this information (which IP address is Pages server
-running on your instance).
-
-**Practical Example:**
-
-![DNS A record pointing to GitLab.com Pages server](img/dns_a_record_example.png)
-
-#### DNS CNAME record
-
-In case you want to point a subdomain (`hello-world.example.com`)
-to your GitLab Pages site initially deployed to `namespace.gitlab.io`,
-you need to log into your domain's admin control panel and add a DNS
-`CNAME` record pointing your subdomain to your website URL
-(`namespace.gitlab.io`) address.
-
-Notice that, despite it's a user or project website, the `CNAME`
-should point to your Pages domain (`namespace.gitlab.io`),
-without any `/project-name`.
-
-**Practical Example:**
-
-![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png)
-
-#### TL;DR
-
-| From | DNS Record | To |
-| ---- | ---------- | -- |
-| domain.com | A | 104.208.235.32 |
-| subdomain.domain.com | CNAME | namespace.gitlab.io |
-
-> **Notes**:
->
-> - **Do not** use a CNAME record if you want to point your
-`domain.com` to your GitLab Pages site. Use an `A` record instead.
-> - **Do not** add any special chars after the default Pages
-domain. E.g., **do not** point your `subdomain.domain.com` to
-`namespace.gitlab.io.` or `namespace.gitlab.io/`.
-
-### SSL/TLS Certificates
-
-Every GitLab Pages project on GitLab.com will be available under
-HTTPS for the default Pages domain (`*.gitlab.io`). Once you set
-up your Pages project with your custom (sub)domain, if you want
-it secured by HTTPS, you will have to issue a certificate for that
-(sub)domain and install it on your project.
-
->**Note:**
-Certificates are NOT required to add to your custom
-(sub)domain on your GitLab Pages project, though they are
-highly recommendable.
-
-The importance of having any website securely served under HTTPS
-is explained on the introductory section of the blog post
-[Secure GitLab Pages with StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/#https-a-quick-overview).
-
-The reason why certificates are so important is that they encrypt
-the connection between the **client** (you, me, your visitors)
-and the **server** (where you site lives), through a keychain of
-authentications and validations.
-
-### Issuing Certificates
-
-GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by
-[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority)
-and self-signed certificates. Of course,
-[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate),
-for security reasons and for having browsers trusting your
-site's certificate.
-
-There are several different kinds of certificates, each one
-with certain security level. A static personal website will
-not require the same security level as an online banking web app,
-for instance. There are a couple Certificate Authorities that
-offer free certificates, aiming to make the internet more secure
-to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/),
-which issues certificates trusted by most of browsers, it's open
-source, and free to use. Please read through this tutorial to
-understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/).
-
-With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/),
-which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/).
-Their certs are valid up to 15 years. Read through the tutorial on
-[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/).
-
-### Adding certificates to your project
-
-Regardless the CA you choose, the steps to add your certificate to
-your Pages project are the same.
-
-#### What do you need
-
-1. A PEM certificate
-1. An intermediate certificate
-1. A public key
-
-![Pages project - adding certificates](img/add_certificate_to_pages.png)
-
-These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**.
-
-#### What's what?
-
-- A PEM certificate is the certificate generated by the CA,
-which needs to be added to the field **Certificate (PEM)**.
-- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is
-the part of the encryption keychain that identifies the CA.
-Usually it's combined with the PEM certificate, but there are
-some cases in which you need to add them manually.
-[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
-are one of these cases.
-- A public key is an encrypted key which validates
-your PEM against your domain.
-
-#### Now what?
-
-Now that you hopefully understand why you need all
-of this, it's simple:
-
-- Your PEM certificate needs to be added to the first field
-- If your certificate is missing its intermediate, copy
-and paste the root certificate (usually available from your CA website)
-and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
-just jumping a line between them.
-- Copy your public key and paste it in the last field
-
->**Note:**
-**Do not** open certificates or encryption keys in
-regular text editors. Always use code editors (such as
-Sublime Text, Atom, Dreamweaver, Brackets, etc).
-
|||
|:--|--:|
||[**Part 2: Quick start guide - Setting up GitLab Pages →**](getting_started_part_two.md)|
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index ef47abef3a0..dba5fb6c17a 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -1,383 +1,189 @@
# GitLab Pages from A to Z: Part 3
-- _[Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates](getting_started_part_one.md)_
-- _[Part 2: Quick Start Guide - Setting Up GitLab Pages](getting_started_part_two.md)_
-- **Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages**
-
----
-
-## Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages
-
-[GitLab CI](https://about.gitlab.com/gitlab-ci/) serves
-numerous purposes, to build, test, and deploy your app
-from GitLab through
-[Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
-methods. You will need it to build your website with GitLab Pages,
-and deploy it to the Pages server.
-
-What this file actually does is telling the
-[GitLab Runner](https://docs.gitlab.com/runner/) to run scripts
-as you would do from the command line. The Runner acts as your
-terminal. GitLab CI tells the Runner which commands to run.
-Both are built-in in GitLab, and you don't need to set up
-anything for them to work.
-
-Explaining [every detail of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
-and GitLab Runner is out of the scope of this guide, but we'll
-need to understand just a few things to be able to write our own
-`.gitlab-ci.yml` or tweak an existing one. It's an
-[Yaml](http://docs.ansible.com/ansible/YAMLSyntax.html) file,
-with its own syntax. You can always check your CI syntax with
-the [GitLab CI Lint Tool](https://gitlab.com/ci/lint).
+- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md)
+- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
+- **Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates**
+- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
+
+## Setting Up Custom Domains - DNS Records and SSL/TLS Certificates
+
+As described in the previous part of this series, setting up GitLab Pages with custom domains, and adding SSL/TLS certificates to them, are optional features of GitLab Pages.
+
+These steps assume you've already [set your site up](getting_started_part_two.md) and and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`.
+
+### DNS Records
+
+A Domain Name System (DNS) web service routes visitors to websites
+by translating domain names (such as `www.example.com`) into the
+numeric IP addresses (such as `192.0.2.1`) that computers use to
+connect to each other.
+
+A DNS record is created to point a (sub)domain to a certain location,
+which can be an IP address or another domain. In case you want to use
+GitLab Pages with your own (sub)domain, you need to access your domain's
+registrar control panel to add a DNS record pointing it back to your
+GitLab Pages site.
+
+Note that **how to** add DNS records depends on which server your domain
+is hosted on. Every control panel has its own place to do it. If you are
+not an admin of your domain, and don't have access to your registrar,
+you'll need to ask for the technical support of your hosting service
+to do it for you.
+
+To help you out, we've gathered some instructions on how to do that
+for the most popular hosting services:
+
+- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html)
+- [Bluehost](https://my.bluehost.com/cgi/help/559)
+- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-)
+- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone)
+- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-)
+- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238)
+- [Hostgator](http://support.hostgator.com/articles/changing-dns-records)
+- [Inmotion hosting](https://my.bluehost.com/cgi/help/559)
+- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain)
+- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx)
+
+If your hosting service is not listed above, you can just try to
+search the web for "how to add dns record on <my hosting service>".
+
+#### DNS A record
+
+In case you want to point a root domain (`example.com`) to your
+GitLab Pages site, deployed to `namespace.gitlab.io`, you need to
+log into your domain's admin control panel and add a DNS `A` record
+pointing your domain to Pages' server IP address. For projects on
+GitLab.com, this IP is `104.208.235.32`. For projects leaving in
+other GitLab instances (CE or EE), please contact your sysadmin
+asking for this information (which IP address is Pages server
+running on your instance).
**Practical Example:**
-Let's consider you have a [Jekyll](https://jekyllrb.com/) site.
-To build it locally, you would open your terminal, and run `jekyll build`.
-Of course, before building it, you had to install Jekyll in your computer.
-For that, you had to open your terminal and run `gem install jekyll`.
-Right? GitLab CI + GitLab Runner do the same thing. But you need to
-write in the `.gitlab-ci.yml` the script you want to run so
-GitLab Runner will do it for you. It looks more complicated then it
-is. What you need to tell the Runner:
-
-```
-$ gem install jekyll
-$ jekyll build
-```
-
-### Script
-
-To transpose this script to Yaml, it would be like this:
-
-```yaml
-script:
- - gem install jekyll
- - jekyll build
-```
-
-### Job
-
-So far so good. Now, each `script`, in GitLab is organized by
-a `job`, which is a bunch of scripts and settings you want to
-apply to that specific task.
-
-```yaml
-job:
- script:
- - gem install jekyll
- - jekyll build
-```
-
-For GitLab Pages, this `job` has a specific name, called `pages`,
-which tells the Runner you want that task to deploy your website
-with GitLab Pages:
-
-```yaml
-pages:
- script:
- - gem install jekyll
- - jekyll build
-```
-
-### The `public` directory
-
-We also need to tell Jekyll where do you want the website to build,
-and GitLab Pages will only consider files in a directory called `public`.
-To do that with Jekyll, we need to add a flag specifying the
-[destination (`-d`)](https://jekyllrb.com/docs/usage/) of the
-built website: `jekyll build -d public`. Of course, we need
-to tell this to our Runner:
-
-```yaml
-pages:
- script:
- - gem install jekyll
- - jekyll build -d public
-```
-
-### Artifacts
-
-We also need to tell the Runner that this _job_ generates
-_artifacts_, which is the site built by Jekyll.
-Where are these artifacts stored? In the `public` directory:
-
-```yaml
-pages:
- script:
- - gem install jekyll
- - jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-The script above would be enough to build your Jekyll
-site with GitLab Pages. But, from Jekyll 3.4.0 on, its default
-template originated by `jekyll new project` requires
-[Bundler](http://bundler.io/) to install Jekyll dependencies
-and the default theme. To adjust our script to meet these new
-requirements, we only need to install and build Jekyll with Bundler:
-
-```yaml
-pages:
- script:
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-That's it! A `.gitlab-ci.yml` with the content above would deploy
-your Jekyll 3.4.0 site with GitLab Pages. This is the minimum
-configuration for our example. On the steps below, we'll refine
-the script by adding extra options to our GitLab CI.
-
-### Image
-
-At this point, you probably ask yourself: "okay, but to install Jekyll
-I need Ruby. Where is Ruby on that script?". The answer is simple: the
-first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a
-[Docker](https://www.docker.com/) image specifying what do you need in
-your container to run that script:
-
-```yaml
-image: ruby:2.3
-
-pages:
- script:
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
-```
-
-In this case, you're telling the Runner to pull this image, which
-contains Ruby 2.3 as part of its file system. When you don't specify
-this image in your configuration, the Runner will use a default
-image, which is Ruby 2.1.
-
-If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll
-need to specify which image you want to use, and this image should
-contain NodeJS as part of its file system. E.g., for a
-[Hexo](https://gitlab.com/pages/hexo) site, you can use `image: node:4.2.2`.
+![DNS A record pointing to GitLab.com Pages server](img/dns_a_record_example.png)
+
+#### DNS CNAME record
+
+In case you want to point a subdomain (`hello-world.example.com`)
+to your GitLab Pages site initially deployed to `namespace.gitlab.io`,
+you need to log into your domain's admin control panel and add a DNS
+`CNAME` record pointing your subdomain to your website URL
+(`namespace.gitlab.io`) address.
+
+Notice that, despite it's a user or project website, the `CNAME`
+should point to your Pages domain (`namespace.gitlab.io`),
+without any `/project-name`.
+
+**Practical Example:**
+
+![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png)
+
+#### TL;DR
+
+| From | DNS Record | To |
+| ---- | ---------- | -- |
+| domain.com | A | 104.208.235.32 |
+| subdomain.domain.com | CNAME | namespace.gitlab.io |
+
+> **Notes**:
+>
+> - **Do not** use a CNAME record if you want to point your
+`domain.com` to your GitLab Pages site. Use an `A` record instead.
+> - **Do not** add any special chars after the default Pages
+domain. E.g., **do not** point your `subdomain.domain.com` to
+`namespace.gitlab.io.` or `namespace.gitlab.io/`.
+
+### SSL/TLS Certificates
+
+Every GitLab Pages project on GitLab.com will be available under
+HTTPS for the default Pages domain (`*.gitlab.io`). Once you set
+up your Pages project with your custom (sub)domain, if you want
+it secured by HTTPS, you will have to issue a certificate for that
+(sub)domain and install it on your project.
+
+>**Note:**
+Certificates are NOT required to add to your custom
+(sub)domain on your GitLab Pages project, though they are
+highly recommendable.
+
+The importance of having any website securely served under HTTPS
+is explained on the introductory section of the blog post
+[Secure GitLab Pages with StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/#https-a-quick-overview).
+
+The reason why certificates are so important is that they encrypt
+the connection between the **client** (you, me, your visitors)
+and the **server** (where you site lives), through a keychain of
+authentications and validations.
+
+### Issuing Certificates
+
+GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by
+[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority)
+and self-signed certificates. Of course,
+[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate),
+for security reasons and for having browsers trusting your
+site's certificate.
+
+There are several different kinds of certificates, each one
+with certain security level. A static personal website will
+not require the same security level as an online banking web app,
+for instance. There are a couple Certificate Authorities that
+offer free certificates, aiming to make the internet more secure
+to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/),
+which issues certificates trusted by most of browsers, it's open
+source, and free to use. Please read through this tutorial to
+understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/).
+
+With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/),
+which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/).
+Their certs are valid up to 15 years. Read through the tutorial on
+[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/).
+
+### Adding certificates to your project
+
+Regardless the CA you choose, the steps to add your certificate to
+your Pages project are the same.
+
+#### What do you need
+
+1. A PEM certificate
+1. An intermediate certificate
+1. A public key
+
+![Pages project - adding certificates](img/add_certificate_to_pages.png)
+
+These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**.
+
+#### What's what?
+
+- A PEM certificate is the certificate generated by the CA,
+which needs to be added to the field **Certificate (PEM)**.
+- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is
+the part of the encryption keychain that identifies the CA.
+Usually it's combined with the PEM certificate, but there are
+some cases in which you need to add them manually.
+[CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+are one of these cases.
+- A public key is an encrypted key which validates
+your PEM against your domain.
+
+#### Now what?
+
+Now that you hopefully understand why you need all
+of this, it's simple:
+
+- Your PEM certificate needs to be added to the first field
+- If your certificate is missing its intermediate, copy
+and paste the root certificate (usually available from your CA website)
+and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
+just jumping a line between them.
+- Copy your public key and paste it in the last field
>**Note:**
-We're not trying to explain what a Docker image is,
-we just need to introduce the concept with a minimum viable
-explanation. To know more about Docker images, please visit
-their website or take a look at a
-[summarized explanation](http://paislee.io/how-to-automate-docker-deployments/) here.
-
-Let's go a little further.
-
-### Branching
-
-If you use GitLab as a version control platform, you will have your
-branching strategy to work on your project. Meaning, you will have
-other branches in your project, but you'll want only pushes to the
-default branch (usually `master`) to be deployed to your website.
-To do that, we need to add another line to our CI, telling the Runner
-to only perform that _job_ called `pages` on the `master` branch `only`:
-
-```yaml
-image: ruby:2.3
-
-pages:
- script:
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-### Stages
-
-Another interesting concept to keep in mind are build stages.
-Your web app can pass through a lot of tests and other tasks
-until it's deployed to staging or production environments.
-There are three default stages on GitLab CI: build, test,
-and deploy. To specify which stage your _job_ is running,
-simply add another line to your CI:
-
-```yaml
-image: ruby:2.3
-
-pages:
- stage: deploy
- script:
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-You might ask yourself: "why should I bother with stages
-at all?" Well, let's say you want to be able to test your
-script and check the built site before deploying your site
-to production. You want to run the test exactly as your
-script will do when you push to `master`. It's simple,
-let's add another task (_job_) to our CI, telling it to
-test every push to other branches, `except` the `master` branch:
-
-```yaml
-image: ruby:2.3
-
-pages:
- stage: deploy
- script:
- - bundle install
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- only:
- - master
-
-test:
- stage: test
- script:
- - bundle install
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- except:
- - master
-```
-
-The `test` job is running on the stage `test`, Jekyll
-will build the site in a directory called `test`, and
-this job will affect all the branches except `master`.
-
-The best benefit of applying _stages_ to different
-_jobs_ is that every job in the same stage builds in
-parallel. So, if your web app needs more than one test
-before being deployed, you can run all your test at the
-same time, it's not necessary to wait one test to finish
-to run the other. Of course, this is just a brief
-introduction of GitLab CI and GitLab Runner, which are
-tools much more powerful than that. This is what you
-need to be able to create and tweak your builds for
-your GitLab Pages site.
-
-### Before Script
-
-To avoid running the same script multiple times across
-your _jobs_, you can add the parameter `before_script`,
-in which you specify which commands you want to run for
-every single _job_. In our example, notice that we run
-`bundle install` for both jobs, `pages` and `test`.
-We don't need to repeat it:
-
-```yaml
-image: ruby:2.3
-
-before_script:
- - bundle install
-
-pages:
- stage: deploy
- script:
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- only:
- - master
-
-test:
- stage: test
- script:
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- except:
- - master
-```
-
-### Caching Dependencies
-
-If you want to cache the installation files for your
-projects dependencies, for building faster, you can
-use the parameter `cache`. For this example, we'll
-cache Jekyll dependencies in a `vendor` directory
-when we run `bundle install`:
-
-```yaml
-image: ruby:2.3
-
-cache:
- paths:
- - vendor/
-
-before_script:
- - bundle install --path vendor
-
-pages:
- stage: deploy
- script:
- - bundle exec jekyll build -d public
- artifacts:
- paths:
- - public
- only:
- - master
-
-test:
- stage: test
- script:
- - bundle exec jekyll build -d test
- artifacts:
- paths:
- - test
- except:
- - master
-```
-
-For this specific case, we need to exclude `/vendor`
-from Jekyll `_config.yml` file, otherwise Jekyll will
-understand it as a regular directory to build
-together with the site:
-
-```yml
-exclude:
- - vendor
-```
-
-There we go! Now our GitLab CI not only builds our website,
-but also **continuously test** pushes to feature-branches,
-**caches** dependencies installed with Bundler, and
-**continuously deploy** every push to the `master` branch.
-
-## Advanced GitLab CI for GitLab Pages
-
-What you can do with GitLab CI is pretty much up to your
-creativity. Once you get used to it, you start creating
-awesome scripts that automate most of tasks you'd do
-manually in the past. Read through the
-[documentation of GitLab CI](https://docs.gitlab.com/ce/ci/yaml/README.html)
-to understand how to go even further on your scripts.
-
-- On this blog post, understand the concept of
-[using GitLab CI `environments` to deploy your
-web app to staging and production](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/).
-- On this post, learn [how to run jobs sequentially,
-in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
-- On this blog post, we go through the process of
-[pulling specific directories from different projects](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
-to deploy this website you're looking at, docs.gitlab.com.
-- On this blog post, we teach you [how to use GitLab Pages to produce a code coverage report](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/).
+**Do not** open certificates or encryption keys in
+regular text editors. Always use code editors (such as
+Sublime Text, Atom, Dreamweaver, Brackets, etc).
|||
|:--|--:|
-|[**← Part 2: Quick start guide - Setting up GitLab Pages**](getting_started_part_two.md)||
+|[**← Part 2: Quick start guide - Setting up GitLab Pages**](getting_started_part_two.md)|[**Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages →**](getting_started_part_four.md)|
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 07dd24122c4..d0e2c467fee 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -1,14 +1,9 @@
# GitLab Pages from A to Z: Part 2
-> Type: user guide
->
-> Level: beginner
-
-- _[Part 1: Static Sites, Domains, DNS Records, and SSL/TLS Certificates](getting_started_part_one.md)_
-- **Part 2: Quick Start Guide - Setting Up GitLab Pages**
-- _[Part 3: Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)_
-
-----
+- [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md)
+- **Part 2: Quick start guide - Setting up GitLab Pages**
+- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md)
+- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
## Setting up GitLab Pages
@@ -30,6 +25,8 @@ Optional Features:
1. **Optional**: an SSL/TLS certificate so your custom
domain is accessible under HTTPS.
+The optional settings, custom domain, DNS records, and SSL/TLS certificates, are described in [Part 3](getting_started_part_three.md)).
+
## Project
Your GitLab Pages project is a regular project created the
@@ -106,7 +103,7 @@ where you'll find its default URL.
> - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but,
if you don't find yours among the templates, you'll need
to configure your own `.gitlab-ci.yml`. Do do that, please
-read through the article [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md). New SSGs are very welcome among
+read through the article [Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among
the [example projects](https://gitlab.com/pages). If you set
up a new one, please
[contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md)
@@ -147,6 +144,11 @@ example we've just mentioned, you'd have to change Jekyll's `_config.yml` to:
baseurl: ""
```
+### Custom Domains
+
+GitLab Pages supports custom domains and subdomains, served under HTTPS or HTTPS.
+Please check the [next part](getting_started_part_three.md) of this series for an overview.
+
|||
|:--|--:|
-|[**← Part 1: Static sites, domains, DNS records, and SSL/TLS certificates**](getting_started_part_one.md)|[**Part 3: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages →**](getting_started_part_three.md)|
+|[**← Part 1: Static sites, domains, DNS records, and SSL/TLS certificates**](getting_started_part_one.md)|[**Setting Up Custom Domains - DNS Records and SSL/TLS Certificates →**](getting_started_part_three.md)|
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index dbb9d9ad9c4..1366756d593 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -1,49 +1,48 @@
-# All you need to know about GitLab Pages
+# GitLab Pages documentation
With GitLab Pages you can create static websites for your GitLab projects,
groups, or user accounts. You can use any static website generator: Jekyll,
Middleman, Hexo, Hugo, Pelican, you name it! Connect as many customs domains
as you like and bring your own TLS certificate to secure them.
-Here's some info we have gathered to get you started.
+Here's some info we've gathered to get you started.
## General info
- [Product webpage](https://pages.gitlab.io)
-- [We're bringing GitLab Pages to CE](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/)
+- ["We're bringing GitLab Pages to CE" blog post](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/)
- [Pages group - templates](https://gitlab.com/pages)
+- [General user documentation](introduction.md)
+- [Admin documentation - Set GitLab Pages on your own GitLab instance](../../../administration/pages/index.md)
## Getting started
-- GitLab Pages from A to Z
- - [Part 1: Static sites, domains, DNS records, and SSL/TLS certificates](getting_started_part_one.md)
+- **GitLab Pages from A to Z**
+ - [Part 1: Static sites and GitLab Pages domains](getting_started_part_one.md)
- [Part 2: Quick start guide - Setting up GitLab Pages](getting_started_part_two.md)
- - [Part 3: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_three.md)
-- [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) a comprehensive step-by-step guide
-- Secure GitLab Pages custom domain with SSL/TLS certificates
- - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/)
- - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
- - [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
-- Static Site Generators - Blog posts series
+ - [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md)
+ - [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
+- **Static Site Generators - Blog posts series**
- [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/)
- [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
+- **Secure GitLab Pages custom domain with SSL/TLS certificates**
+ - [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/)
+ - [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+ - [StartSSL](https://about.gitlab.com/2016/06/24/secure-gitlab-pages-with-startssl/)
+- **General**
+ - [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) a comprehensive step-by-step guide
+ - [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
## Video tutorials
- [How to publish a website with GitLab Pages on GitLab.com: from a forked project](https://youtu.be/TWqh9MtT4Bg)
-- [How to Enable GitLab Pages for GitLab CE and EE](https://youtu.be/dD8c7WNcc6s)
+- [How to Enable GitLab Pages for GitLab CE and EE (for Admins only)](https://youtu.be/dD8c7WNcc6s)
## Advanced use
-- Blog Posts:
+- **Blog Posts**
- [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
- [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
- [Building a new GitLab docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
- [Publish code coverage reports with GitLab Pages](https://about.gitlab.com/2016/11/03/publish-code-coverage-report-with-gitlab-pages/)
-
-## Specific documentation
-
-- [User docs](introduction.md)
-- [Admin docs](../../../administration/pages/index.md)
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index ad5d51d34f2..45176fde9db 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -35,3 +35,4 @@ do.
| <code>/spend &lt;1h 30m &#124; -1h 5m&gt;</code> | Add or subtract spent time |
| `/remove_time_spent` | Remove time spent |
| `/target_branch <Branch Name>` | Set target branch for current merge request |
+| `/award :emoji:` | Toggle award for :emoji: |
diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md
index 4889e3ec50c..d12c0c6d0c4 100644
--- a/doc/workflow/gitlab_flow.md
+++ b/doc/workflow/gitlab_flow.md
@@ -203,7 +203,7 @@ But the advantages of having stable identifiers outweigh this drawback.
And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch.
After you merge multiple commits from a feature branch into the master branch this is harder to undo.
-If you would have squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
+If you had squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed.
Fortunately [reverting a merge made some time ago](https://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git.
This however, requires having specific merge commits for the commits your want to revert.
If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise.
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index f0fd414a9f9..1d7adfdd2c2 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -42,4 +42,4 @@ Feature: Award Emoji
@javascript
Scenario: I add award emoji using regular comment
Given I leave comment with a single emoji
- Then I have award added
+ Then I have new comment with emoji added
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index cbe5738e7e4..dd7a58b454a 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -44,6 +44,10 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
end
+ step 'I have new comment with emoji added' do
+ expect(page).to have_selector ".emoji[title=':smile:']"
+ end
+
step 'I have award added' do
page.within '.awards' do
expect(page).to have_selector '.js-emoji-btn'
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index c11f8529183..409cb5b924f 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -160,13 +160,10 @@ module API
# Exceptions
#
- class MissingTokenError < StandardError; end
-
- class TokenNotFoundError < StandardError; end
-
- class ExpiredError < StandardError; end
-
- class RevokedError < StandardError; end
+ MissingTokenError = Class.new(StandardError)
+ TokenNotFoundError = Class.new(StandardError)
+ ExpiredError = Class.new(StandardError)
+ RevokedError = Class.new(StandardError)
class InsufficientScopeError < StandardError
attr_reader :scopes
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 89ef5299912..9dccaff369e 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -394,7 +394,8 @@ module API
expose :target_type
expose :target do |todo, options|
- Entities.const_get(todo.target_type).represent(todo.target, options)
+ target = todo.target_type == 'Commit' ? 'RepoCommit' : todo.target_type
+ Entities.const_get(target).represent(todo.target, options)
end
expose :target_url do |todo, options|
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index dbdf29a9640..ebe8c3a5b2c 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -81,6 +81,23 @@ module API
environment.destroy
end
+
+ desc 'Stops an existing environment' do
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ end
+ post ':id/environments/:environment_id/stop' do
+ authorize! :create_deployment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+
+ environment.stop_with_action!(current_user)
+
+ status 200
+ present environment, with: Entities::Environment
+ end
end
end
end
diff --git a/lib/bitbucket/error/unauthorized.rb b/lib/bitbucket/error/unauthorized.rb
index 5e2eb57bb0e..efe10542f19 100644
--- a/lib/bitbucket/error/unauthorized.rb
+++ b/lib/bitbucket/error/unauthorized.rb
@@ -1,6 +1,5 @@
module Bitbucket
module Error
- class Unauthorized < StandardError
- end
+ Unauthorized = Class.new(StandardError)
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 649ee4d018b..e390919ae1d 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -1,6 +1,6 @@
module Ci
class GitlabCiYamlProcessor
- class ValidationError < StandardError; end
+ ValidationError = Class.new(StandardError)
include Gitlab::Ci::Config::Entry::LegacyValidationHelpers
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 9ece84cc469..dd864eea3fa 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -2,7 +2,7 @@
# file path string when combined in a request parameter
module ExtractsPath
# Raised when given an invalid file path
- class InvalidPathError < StandardError; end
+ InvalidPathError = Class.new(StandardError)
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 3b210eeda9d..8c28009b9c6 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -5,7 +5,7 @@
#
module Gitlab
module Access
- class AccessDeniedError < StandardError; end
+ AccessDeniedError = Class.new(StandardError)
NO_ACCESS = 0
GUEST = 10
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 89db6c3da46..0a5abc92190 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,6 +1,6 @@
module Gitlab
module Auth
- class MissingPersonalTokenError < StandardError; end
+ MissingPersonalTokenError = Class.new(StandardError)
SCOPES = [:api, :read_user].freeze
DEFAULT_SCOPES = [:api].freeze
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index cd2e83b4c27..a375ccbece0 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -6,7 +6,7 @@ module Gitlab
module Build
module Artifacts
class Metadata
- class ParserError < StandardError; end
+ ParserError = Class.new(StandardError)
VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/
INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}
diff --git a/lib/gitlab/ci/config/entry/factory.rb b/lib/gitlab/ci/config/entry/factory.rb
index 9f5e393d191..6be8288748f 100644
--- a/lib/gitlab/ci/config/entry/factory.rb
+++ b/lib/gitlab/ci/config/entry/factory.rb
@@ -6,7 +6,7 @@ module Gitlab
# Factory class responsible for fabricating entry objects.
#
class Factory
- class InvalidFactory < StandardError; end
+ InvalidFactory = Class.new(StandardError)
def initialize(entry)
@entry = entry
diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb
index 5eef2868cd6..55a5447ab51 100644
--- a/lib/gitlab/ci/config/entry/node.rb
+++ b/lib/gitlab/ci/config/entry/node.rb
@@ -6,7 +6,7 @@ module Gitlab
# Base abstract class for each configuration entry node.
#
class Node
- class InvalidError < StandardError; end
+ InvalidError = Class.new(StandardError)
attr_reader :config, :metadata
attr_accessor :key, :parent, :description
diff --git a/lib/gitlab/ci/config/loader.rb b/lib/gitlab/ci/config/loader.rb
index dbf6eb0edbe..e7d9f6a7761 100644
--- a/lib/gitlab/ci/config/loader.rb
+++ b/lib/gitlab/ci/config/loader.rb
@@ -2,7 +2,7 @@ module Gitlab
module Ci
class Config
class Loader
- class FormatError < StandardError; end
+ FormatError = Class.new(StandardError)
def initialize(config)
@config = YAML.safe_load(config, [Symbol], [], true)
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index d80bc748209..75a213ef752 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -4,8 +4,7 @@ module Gitlab
include Gitlab::Routing.url_helpers
include IconsHelper
- class MissingResolution < ResolutionError
- end
+ MissingResolution = Class.new(ResolutionError)
CONTEXT_LINES = 3
diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb
index fa5bd4649d4..990b719ecfd 100644
--- a/lib/gitlab/conflict/file_collection.rb
+++ b/lib/gitlab/conflict/file_collection.rb
@@ -1,8 +1,7 @@
module Gitlab
module Conflict
class FileCollection
- class ConflictSideMissing < StandardError
- end
+ ConflictSideMissing = Class.new(StandardError)
attr_reader :merge_request, :our_commit, :their_commit
diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb
index ddd657903fb..d3524c338ee 100644
--- a/lib/gitlab/conflict/parser.rb
+++ b/lib/gitlab/conflict/parser.rb
@@ -1,25 +1,15 @@
module Gitlab
module Conflict
class Parser
- class UnresolvableError < StandardError
- end
-
- class UnmergeableFile < UnresolvableError
- end
-
- class UnsupportedEncoding < UnresolvableError
- end
+ UnresolvableError = Class.new(StandardError)
+ UnmergeableFile = Class.new(UnresolvableError)
+ UnsupportedEncoding = Class.new(UnresolvableError)
# Recoverable errors - the conflict can be resolved in an editor, but not with
# sections.
- class ParserError < StandardError
- end
-
- class UnexpectedDelimiter < ParserError
- end
-
- class MissingEndDelimiter < ParserError
- end
+ ParserError = Class.new(StandardError)
+ UnexpectedDelimiter = Class.new(ParserError)
+ MissingEndDelimiter = Class.new(ParserError)
def parse(text, our_path:, their_path:, parent_file: nil)
raise UnmergeableFile if text.blank? # Typically a binary file
diff --git a/lib/gitlab/conflict/resolution_error.rb b/lib/gitlab/conflict/resolution_error.rb
index a0f2006bc24..0b61256b35a 100644
--- a/lib/gitlab/conflict/resolution_error.rb
+++ b/lib/gitlab/conflict/resolution_error.rb
@@ -1,6 +1,5 @@
module Gitlab
module Conflict
- class ResolutionError < StandardError
- end
+ ResolutionError = Class.new(StandardError)
end
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index b64db5d01ae..ec0529b5a4b 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -4,19 +4,19 @@ require_dependency 'gitlab/email/handler'
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
module Email
- class ProcessingError < StandardError; end
- class EmailUnparsableError < ProcessingError; end
- class SentNotificationNotFoundError < ProcessingError; end
- class ProjectNotFound < ProcessingError; end
- class EmptyEmailError < ProcessingError; end
- class AutoGeneratedEmailError < ProcessingError; end
- class UserNotFoundError < ProcessingError; end
- class UserBlockedError < ProcessingError; end
- class UserNotAuthorizedError < ProcessingError; end
- class NoteableNotFoundError < ProcessingError; end
- class InvalidNoteError < ProcessingError; end
- class InvalidIssueError < ProcessingError; end
- class UnknownIncomingEmail < ProcessingError; end
+ ProcessingError = Class.new(StandardError)
+ EmailUnparsableError = Class.new(ProcessingError)
+ SentNotificationNotFoundError = Class.new(ProcessingError)
+ ProjectNotFound = Class.new(ProcessingError)
+ EmptyEmailError = Class.new(ProcessingError)
+ AutoGeneratedEmailError = Class.new(ProcessingError)
+ UserNotFoundError = Class.new(ProcessingError)
+ UserBlockedError = Class.new(ProcessingError)
+ UserNotAuthorizedError = Class.new(ProcessingError)
+ NoteableNotFoundError = Class.new(ProcessingError)
+ InvalidNoteError = Class.new(ProcessingError)
+ InvalidIssueError = Class.new(ProcessingError)
+ UnknownIncomingEmail = Class.new(ProcessingError)
class Receiver
def initialize(raw)
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index d6b3b5705a9..2a017c93f57 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -2,7 +2,7 @@
module Gitlab
module Git
class Diff
- class TimeoutError < StandardError; end
+ TimeoutError = Class.new(StandardError)
include Gitlab::Git::EncodingHelper
# Diff properties
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 8ec90885231..0e9b812ffdd 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -10,9 +10,9 @@ module Gitlab
SEARCH_CONTEXT_LINES = 3
- class NoRepository < StandardError; end
- class InvalidBlobName < StandardError; end
- class InvalidRef < StandardError; end
+ NoRepository = Class.new(StandardError)
+ InvalidBlobName = Class.new(StandardError)
+ InvalidRef = Class.new(StandardError)
# Full path to repo
attr_reader :path
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 101b1b80c1e..9c384069661 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -1,7 +1,7 @@
module Gitlab
module GonHelper
def add_gon_variables
- gon.api_version = API::API.version
+ gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen"
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
diff --git a/lib/gitlab/import_export/error.rb b/lib/gitlab/import_export/error.rb
index e341c4d9cf8..788eedf2686 100644
--- a/lib/gitlab/import_export/error.rb
+++ b/lib/gitlab/import_export/error.rb
@@ -1,5 +1,5 @@
module Gitlab
module ImportExport
- class Error < StandardError; end
+ Error = Class.new(StandardError)
end
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 95d2f559588..fcf51b7fc5b 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -5,7 +5,7 @@
#
module Gitlab
module OAuth
- class SignupDisabledError < StandardError; end
+ SignupDisabledError = Class.new(StandardError)
class User
attr_accessor :auth_hash, :gl_user
diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb
index 72d00abfcc2..36791fae60f 100644
--- a/lib/gitlab/route_map.rb
+++ b/lib/gitlab/route_map.rb
@@ -1,6 +1,6 @@
module Gitlab
class RouteMap
- class FormatError < StandardError; end
+ FormatError = Class.new(StandardError)
def initialize(data)
begin
diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb
index bf2c0acc729..9c92b83dddc 100644
--- a/lib/gitlab/serializer/pagination.rb
+++ b/lib/gitlab/serializer/pagination.rb
@@ -1,7 +1,7 @@
module Gitlab
module Serializer
class Pagination
- class InvalidResourceError < StandardError; end
+ InvalidResourceError = Class.new(StandardError)
include ::API::Helpers::Pagination
def initialize(request, response)
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 7374d2bc8b8..da8d8ddb8ed 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -2,7 +2,7 @@ require 'securerandom'
module Gitlab
class Shell
- class Error < StandardError; end
+ Error = Class.new(StandardError)
KeyAdder = Struct.new(:io) do
def add_key(id, key)
diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb
index 22c39436cb2..cb7957e2af9 100644
--- a/lib/gitlab/template/finders/repo_template_finder.rb
+++ b/lib/gitlab/template/finders/repo_template_finder.rb
@@ -4,7 +4,7 @@ module Gitlab
module Finders
class RepoTemplateFinder < BaseTemplateFinder
# Raised when file is not found
- class FileNotFoundError < StandardError; end
+ FileNotFoundError = Class.new(StandardError)
def initialize(project, base_dir, extension, categories = {})
@categories = categories
diff --git a/lib/gitlab/update_path_error.rb b/lib/gitlab/update_path_error.rb
index ce14cc887d0..8947ecfb92e 100644
--- a/lib/gitlab/update_path_error.rb
+++ b/lib/gitlab/update_path_error.rb
@@ -1,3 +1,3 @@
module Gitlab
- class UpdatePathError < StandardError; end
+ UpdatePathError = Class.new(StandardError)
end
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
index e55c0d6ac49..ad6df246091 100644
--- a/lib/mattermost/client.rb
+++ b/lib/mattermost/client.rb
@@ -1,5 +1,5 @@
module Mattermost
- class ClientError < Mattermost::Error; end
+ ClientError = Class.new(Mattermost::Error)
class Client
attr_reader :user
diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb
index 014df175be0..dee6deb7974 100644
--- a/lib/mattermost/error.rb
+++ b/lib/mattermost/error.rb
@@ -1,3 +1,3 @@
module Mattermost
- class Error < StandardError; end
+ Error = Class.new(StandardError)
end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index 377cb7b1021..5388966605d 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -5,7 +5,7 @@ module Mattermost
end
end
- class ConnectionError < Mattermost::Error; end
+ ConnectionError = Class.new(Mattermost::Error)
# This class' prime objective is to obtain a session token on a Mattermost
# instance with SSO configured where this GitLab instance is the provider.
diff --git a/rubocop/cop/custom_error_class.rb b/rubocop/cop/custom_error_class.rb
new file mode 100644
index 00000000000..38d93acfe88
--- /dev/null
+++ b/rubocop/cop/custom_error_class.rb
@@ -0,0 +1,64 @@
+module RuboCop
+ module Cop
+ # This cop makes sure that custom error classes, when empty, are declared
+ # with Class.new.
+ #
+ # @example
+ # # bad
+ # class FooError < StandardError
+ # end
+ #
+ # # okish
+ # class FooError < StandardError; end
+ #
+ # # good
+ # FooError = Class.new(StandardError)
+ class CustomErrorClass < RuboCop::Cop::Cop
+ MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'.freeze
+
+ def on_class(node)
+ _klass, parent, body = node.children
+
+ return if body
+
+ parent_klass = class_name_from_node(parent)
+
+ return unless parent_klass && parent_klass.to_s.end_with?('Error')
+
+ add_offense(node, :expression)
+ end
+
+ def autocorrect(node)
+ klass, parent, _body = node.children
+ replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
+
+ lambda do |corrector|
+ corrector.replace(node.source_range, replacement)
+ end
+ end
+
+ private
+
+ # The nested constant `Foo::Bar::Baz` looks like:
+ #
+ # s(:const,
+ # s(:const,
+ # s(:const, nil, :Foo), :Bar), :Baz)
+ #
+ # So recurse through that to get the name as written in the source.
+ #
+ def class_name_from_node(node, suffix = nil)
+ return unless node&.type == :const
+
+ name = node.children[1].to_s
+ name = "#{name}::#{suffix}" if suffix
+
+ if node.children[0]
+ class_name_from_node(node.children[0], name)
+ else
+ name
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index d4266d0deae..ea8e0f64b0d 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,3 +1,4 @@
+require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default'
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index b0e1970d463..250d64f7055 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -43,7 +43,8 @@ describe Projects::MergeRequestsController do
submit_new_merge_request(format: :json)
expect(response).to be_ok
- expect(json_response).not_to be_empty
+ expect(json_response).to have_key 'pipelines'
+ expect(json_response['pipelines']).not_to be_empty
end
end
end
diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
index b14d275f7fa..b32eb39b1fb 100644
--- a/spec/controllers/root_controller_spec.rb
+++ b/spec/controllers/root_controller_spec.rb
@@ -2,6 +2,26 @@ require 'spec_helper'
describe RootController do
describe 'GET index' do
+ context 'when user is not logged in' do
+ it 'redirects to the sign-in page' do
+ get :index
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+
+ context 'when a custom home page URL is defined' do
+ before do
+ stub_application_setting(home_page_url: 'https://gitlab.com')
+ end
+
+ it 'redirects the user to the custom home page URL' do
+ get :index
+
+ expect(response).to redirect_to('https://gitlab.com')
+ end
+ end
+ end
+
context 'with a user' do
let(:user) { create(:user) }
@@ -12,55 +32,60 @@ describe RootController do
context 'who has customized their dashboard setting for starred projects' do
before do
- user.update_attribute(:dashboard, 'stars')
+ user.dashboard = 'stars'
end
it 'redirects to their specified dashboard' do
get :index
+
expect(response).to redirect_to starred_dashboard_projects_path
end
end
context 'who has customized their dashboard setting for project activities' do
before do
- user.update_attribute(:dashboard, 'project_activity')
+ user.dashboard = 'project_activity'
end
it 'redirects to the activity list' do
get :index
+
expect(response).to redirect_to activity_dashboard_path
end
end
context 'who has customized their dashboard setting for starred project activities' do
before do
- user.update_attribute(:dashboard, 'starred_project_activity')
+ user.dashboard = 'starred_project_activity'
end
it 'redirects to the activity list' do
get :index
+
expect(response).to redirect_to activity_dashboard_path(filter: 'starred')
end
end
context 'who has customized their dashboard setting for groups' do
before do
- user.update_attribute(:dashboard, 'groups')
+ user.dashboard = 'groups'
end
it 'redirects to their group list' do
get :index
+
expect(response).to redirect_to dashboard_groups_path
end
end
context 'who has customized their dashboard setting for todos' do
before do
- user.update_attribute(:dashboard, 'todos')
+ user.dashboard = 'todos'
end
it 'redirects to their todo list' do
get :index
+
expect(response).to redirect_to dashboard_todos_path
end
end
@@ -68,6 +93,7 @@ describe RootController do
context 'who uses the default dashboard setting' do
it 'renders the default dashboard' do
get :index
+
expect(response).to render_template 'dashboard/projects/index'
end
end
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index a5265f1b189..c1ac3bb84ad 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -18,11 +18,6 @@ FactoryGirl.define do
action { Todo::DIRECTLY_ADDRESSED }
end
- trait :on_commit do
- commit_id RepoHelpers.sample_commit.id
- target_type "Commit"
- end
-
trait :build_failed do
action { Todo::BUILD_FAILED }
target factory: :merge_request
@@ -48,4 +43,13 @@ FactoryGirl.define do
state :done
end
end
+
+ factory :on_commit_todo, class: Todo do
+ project factory: :empty_project
+ author
+ user
+ action { Todo::ASSIGNED }
+ commit_id RepoHelpers.sample_commit.id
+ target_type "Commit"
+ end
end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 73e43316dc7..3ab3d2d4229 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -67,6 +67,18 @@ describe 'Awards Emoji', feature: true do
expect(page).not_to have_selector(emoji_counter)
end
end
+
+ context 'execute /award slash command' do
+ it 'toggles the emoji award on noteable', js: true do
+ execute_slash_command('/award :100:')
+
+ expect(find(noteable_award_counter)).to have_text("1")
+
+ execute_slash_command('/award :100:')
+
+ expect(page).not_to have_selector(noteable_award_counter)
+ end
+ end
end
end
@@ -80,6 +92,15 @@ describe 'Awards Emoji', feature: true do
end
end
+ def execute_slash_command(cmd)
+ within('.js-main-target-form') do
+ fill_in 'note[note]', with: cmd
+ click_button 'Comment'
+ end
+
+ wait_for_ajax
+ end
+
def thumbsup_emoji
page.all(emoji_counter).first
end
@@ -92,6 +113,10 @@ describe 'Awards Emoji', feature: true do
'span.js-counter'
end
+ def noteable_award_counter
+ ".awards .active"
+ end
+
def toggle_smiley_emoji(status)
within('.note') do
find('.note-emoji-button').click
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 192916fbc6a..be31f644e20 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -8,7 +8,7 @@
require('~/boards/models/list');
require('~/boards/models/label');
require('~/boards/stores/boards_store');
-const boardCard = require('~/boards/components/board_card');
+const boardCard = require('~/boards/components/board_card').default;
require('./mock_data');
describe('Issue card', () => {
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
new file mode 100644
index 00000000000..22c9f12951b
--- /dev/null
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -0,0 +1,191 @@
+/* global boardsMockInterceptor */
+/* global BoardService */
+/* global List */
+/* global listObj */
+
+import Vue from 'vue';
+import boardNewIssue from '~/boards/components/board_new_issue';
+
+require('~/boards/models/list');
+require('./mock_data');
+require('es6-promise').polyfill();
+
+describe('Issue boards new issue form', () => {
+ let vm;
+ let list;
+ const promiseReturn = {
+ json() {
+ return {
+ iid: 100,
+ };
+ },
+ };
+ const submitIssue = () => {
+ vm.$el.querySelector('.btn-success').click();
+ };
+
+ beforeEach((done) => {
+ const BoardNewIssueComp = Vue.extend(boardNewIssue);
+
+ Vue.http.interceptors.push(boardsMockInterceptor);
+ gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.issueBoards.BoardsStore.create();
+ gl.IssueBoardsApp = new Vue();
+
+ setTimeout(() => {
+ list = new List(listObj);
+
+ spyOn(gl.boardService, 'newIssue').and.callFake(() => new Promise((resolve, reject) => {
+ if (vm.title === 'error') {
+ reject();
+ } else {
+ resolve(promiseReturn);
+ }
+ }));
+
+ vm = new BoardNewIssueComp({
+ propsData: {
+ list,
+ },
+ }).$mount();
+
+ done();
+ }, 0);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor);
+ });
+
+ it('disables submit button if title is empty', () => {
+ expect(vm.$el.querySelector('.btn-success').disabled).toBe(true);
+ });
+
+ it('enables submit button if title is not empty', (done) => {
+ vm.title = 'Testing Title';
+
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title');
+ expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true);
+
+ done();
+ }, 0);
+ });
+
+ it('clears title after clicking cancel', (done) => {
+ vm.$el.querySelector('.btn-default').click();
+
+ setTimeout(() => {
+ expect(vm.title).toBe('');
+ done();
+ }, 0);
+ });
+
+ it('does not create new issue if title is empty', (done) => {
+ submitIssue();
+
+ setTimeout(() => {
+ expect(gl.boardService.newIssue).not.toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+
+ describe('submit success', () => {
+ it('creates new issue', (done) => {
+ vm.title = 'submit title';
+
+ setTimeout(() => {
+ submitIssue();
+
+ expect(gl.boardService.newIssue).toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+
+ it('enables button after submit', (done) => {
+ vm.title = 'submit issue';
+
+ setTimeout(() => {
+ submitIssue();
+
+ expect(vm.$el.querySelector('.btn-success').disbled).not.toBe(true);
+ done();
+ }, 0);
+ });
+
+ it('clears title after submit', (done) => {
+ vm.title = 'submit issue';
+
+ setTimeout(() => {
+ submitIssue();
+
+ expect(vm.title).toBe('');
+ done();
+ }, 0);
+ });
+
+ it('adds new issue to list after submit', (done) => {
+ vm.title = 'submit issue';
+
+ setTimeout(() => {
+ submitIssue();
+
+ expect(list.issues.length).toBe(2);
+ expect(list.issues[1].title).toBe('submit issue');
+ expect(list.issues[1].subscribed).toBe(true);
+ done();
+ }, 0);
+ });
+
+ it('sets detail issue after submit', (done) => {
+ vm.title = 'submit issue';
+
+ setTimeout(() => {
+ submitIssue();
+
+ expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
+ done();
+ });
+ });
+
+ it('sets detail list after submit', (done) => {
+ vm.title = 'submit issue';
+
+ setTimeout(() => {
+ submitIssue();
+
+ expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
+ done();
+ }, 0);
+ });
+ });
+
+ describe('submit error', () => {
+ it('removes issue', (done) => {
+ vm.title = 'error';
+
+ setTimeout(() => {
+ submitIssue();
+
+ setTimeout(() => {
+ expect(list.issues.length).toBe(1);
+ done();
+ }, 500);
+ }, 0);
+ });
+
+ it('shows error', (done) => {
+ vm.title = 'error';
+ submitIssue();
+
+ setTimeout(() => {
+ submitIssue();
+
+ setTimeout(() => {
+ expect(vm.error).toBe(true);
+ done();
+ }, 500);
+ }, 0);
+ });
+ });
+});
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index b963ca4e542..5743c555cbe 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -181,20 +181,6 @@ describe Ci::Build, :models do
end
end
- describe '#create_from' do
- before do
- build.status = 'success'
- build.save
- end
- let(:create_from_build) { Ci::Build.create_from build }
-
- it 'exists a pending task' do
- expect(Ci::Build.pending.count(:all)).to eq 0
- create_from_build
- expect(Ci::Build.pending.count(:all)).to be > 0
- end
- end
-
describe '#depends_on_builds' do
let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index d66eb63fd0a..8aac0546513 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -141,4 +141,39 @@ describe API::Environments, api: true do
end
end
end
+
+ describe 'POST /projects/:id/environments/:environment_id/stop' do
+ context 'as a master' do
+ context 'with a stoppable environment' do
+ before do
+ environment.update(state: :available)
+
+ post api("/projects/#{project.id}/environments/#{environment.id}/stop", user)
+ end
+
+ it 'returns a 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'actually stops the environment' do
+ expect(environment.reload).to be_stopped
+ end
+ end
+
+ it 'returns a 404 for non existing id' do
+ post api("/projects/#{project.id}/environments/12345/stop", user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+ end
+
+ context 'a non member' do
+ it 'rejects the request' do
+ post api("/projects/#{project.id}/environments/#{environment.id}/stop", non_member)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 9d3c821b692..347f8f6fa3b 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -225,11 +225,11 @@ describe API::Notes, api: true do
context 'when the user is posting an award emoji on an issue created by someone else' do
let(:issue2) { create(:issue, project: project) }
- it 'returns an award emoji' do
+ it 'creates a new issue note' do
post api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:'
expect(response).to have_http_status(201)
- expect(json_response['awardable_id']).to eq issue2.id
+ expect(json_response['body']).to eq(':+1:')
end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index f35e963a14b..1e401935662 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe API::Todos, api: true do
include ApiHelpers
- let(:project_1) { create(:empty_project) }
+ let(:project_1) { create(:empty_project, :test_repo) }
let(:project_2) { create(:empty_project) }
let(:author_1) { create(:user) }
let(:author_2) { create(:user) }
@@ -11,7 +11,7 @@ describe API::Todos, api: true do
let(:merge_request) { create(:merge_request, source_project: project_1) }
let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) }
let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) }
- let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) }
+ let!(:pending_3) { create(:on_commit_todo, project: project_1, author: author_2, user: john_doe) }
let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) }
before do
diff --git a/spec/requests/api/v3/notes_spec.rb b/spec/requests/api/v3/notes_spec.rb
index b8f0260c6a2..ddef2d5eb04 100644
--- a/spec/requests/api/v3/notes_spec.rb
+++ b/spec/requests/api/v3/notes_spec.rb
@@ -228,11 +228,11 @@ describe API::V3::Notes, api: true do
context 'when the user is posting an award emoji on an issue created by someone else' do
let(:issue2) { create(:issue, project: project) }
- it 'returns an award emoji' do
+ it 'creates a new issue note' do
post v3_api("/projects/#{project.id}/issues/#{issue2.id}/notes", user), body: ':+1:'
expect(response).to have_http_status(201)
- expect(json_response['awardable_id']).to eq issue2.id
+ expect(json_response['body']).to eq(':+1:')
end
end
diff --git a/spec/rubocop/cop/custom_error_class_spec.rb b/spec/rubocop/cop/custom_error_class_spec.rb
new file mode 100644
index 00000000000..381d7871a40
--- /dev/null
+++ b/spec/rubocop/cop/custom_error_class_spec.rb
@@ -0,0 +1,111 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/custom_error_class'
+
+describe RuboCop::Cop::CustomErrorClass do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when a class has a body' do
+ it 'does nothing' do
+ inspect_source(cop, 'class CustomError < StandardError; def foo; end; end')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when a class has no explicit superclass' do
+ it 'does nothing' do
+ inspect_source(cop, 'class CustomError; end')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when a class has a superclass that does not end in Error' do
+ it 'does nothing' do
+ inspect_source(cop, 'class CustomError < BasicObject; end')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'when a class is empty and inherits from a class ending in Error' do
+ context 'when the class is on a single line' do
+ let(:source) do
+ <<-SOURCE
+ module Foo
+ class CustomError < Bar::Baz::BaseError; end
+ end
+ SOURCE
+ end
+
+ let(:expected) do
+ <<-EXPECTED
+ module Foo
+ CustomError = Class.new(Bar::Baz::BaseError)
+ end
+ EXPECTED
+ end
+
+ it 'registers an offense' do
+ expected_highlights = source.split("\n")[1].strip
+
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([2])
+ expect(cop.highlights).to contain_exactly(expected_highlights)
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb')
+
+ expect(autocorrected).to eq(expected)
+ end
+ end
+
+ context 'when the class is on multiple lines' do
+ let(:source) do
+ <<-SOURCE
+ module Foo
+ class CustomError < Bar::Baz::BaseError
+ end
+ end
+ SOURCE
+ end
+
+ let(:expected) do
+ <<-EXPECTED
+ module Foo
+ CustomError = Class.new(Bar::Baz::BaseError)
+ end
+ EXPECTED
+ end
+
+ it 'registers an offense' do
+ expected_highlights = source.split("\n")[1..2].join("\n").strip
+
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([2])
+ expect(cop.highlights).to contain_exactly(expected_highlights)
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb')
+
+ expect(autocorrected).to eq(expected)
+ end
+ end
+ end
+end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 9c92a5080c6..152c6d20daa 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -102,47 +102,19 @@ describe Notes::CreateService, services: true do
expect(subject.note).to eq(params[:note])
end
end
- end
-
- describe "award emoji" do
- before do
- project.team << [user, :master]
- end
-
- it "creates an award emoji" do
- opts = {
- note: ':smile: ',
- noteable_type: 'Issue',
- noteable_id: issue.id
- }
- note = described_class.new(project, user, opts).execute
-
- expect(note).to be_valid
- expect(note.name).to eq('smile')
- end
- it "creates regular note if emoji name is invalid" do
- opts = {
- note: ':smile: moretext:',
- noteable_type: 'Issue',
- noteable_id: issue.id
- }
- note = described_class.new(project, user, opts).execute
-
- expect(note).to be_valid
- expect(note.note).to eq(opts[:note])
- end
-
- it "normalizes the emoji name" do
- opts = {
- note: ':+1:',
- noteable_type: 'Issue',
- noteable_id: issue.id
- }
-
- expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user)
+ describe 'note with emoji only' do
+ it 'creates regular note' do
+ opts = {
+ note: ':smile: ',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+ note = described_class.new(project, user, opts).execute
- described_class.new(project, user, opts).execute
+ expect(note).to be_valid
+ expect(note.note).to eq(':smile:')
+ end
end
end
end
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index 0b0925983eb..52e8678cb9d 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -267,6 +267,14 @@ describe SlashCommands::InterpretService, services: true do
end
end
+ shared_examples 'award command' do
+ it 'toggle award 100 emoji if content containts /award :100:' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(emoji_award: "100")
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -654,6 +662,37 @@ describe SlashCommands::InterpretService, services: true do
end
end
+ context '/award command' do
+ it_behaves_like 'award command' do
+ let(:content) { '/award :100:' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'award command' do
+ let(:content) { '/award :100:' }
+ let(:issuable) { merge_request }
+ end
+
+ context 'ignores command with no argument' do
+ it_behaves_like 'empty command' do
+ let(:content) { '/award' }
+ let(:issuable) { issue }
+ end
+ end
+
+ context 'ignores non-existing / invalid emojis' do
+ it_behaves_like 'empty command' do
+ let(:content) { '/award noop' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/award :lorem_ipsum:' }
+ let(:issuable) { issue }
+ end
+ end
+ end
+
context '/target_branch command' do
let(:non_empty_project) { create(:project) }
let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) }
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
new file mode 100644
index 00000000000..c62450fb8e2
--- /dev/null
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe 'ci/status/_badge', :view do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :private) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when rendering status for build' do
+ let(:build) do
+ create(:ci_build, :success, pipeline: pipeline)
+ end
+
+ context 'when user has ability to see details' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'has link to build details page' do
+ details_path = namespace_project_build_path(
+ project.namespace, project, build)
+
+ render_status(build)
+
+ expect(rendered).to have_link 'passed', href: details_path
+ end
+ end
+
+ context 'when user do not have ability to see build details' do
+ before do
+ render_status(build)
+ end
+
+ it 'contains build status text' do
+ expect(rendered).to have_content 'passed'
+ end
+
+ it 'does not contain links' do
+ expect(rendered).not_to have_link 'passed'
+ end
+ end
+ end
+
+ context 'when rendering status for external job' do
+ context 'when user has ability to see commit status details' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'status has external target url' do
+ before do
+ external_job = create(:generic_commit_status,
+ status: :running,
+ pipeline: pipeline,
+ target_url: 'http://gitlab.com')
+
+ render_status(external_job)
+ end
+
+ it 'contains valid commit status text' do
+ expect(rendered).to have_content 'running'
+ end
+
+ it 'has link to external status page' do
+ expect(rendered).to have_link 'running', href: 'http://gitlab.com'
+ end
+ end
+
+ context 'status do not have external target url' do
+ before do
+ external_job = create(:generic_commit_status, status: :canceled)
+
+ render_status(external_job)
+ end
+
+ it 'contains valid commit status text' do
+ expect(rendered).to have_content 'canceled'
+ end
+
+ it 'has link to external status page' do
+ expect(rendered).not_to have_link 'canceled'
+ end
+ end
+ end
+ end
+
+ def render_status(resource)
+ render 'ci/status/badge', status: resource.detailed_status(user)
+ end
+end