summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ajax_loading_spinner.js35
-rw-r--r--app/assets/javascripts/application.js183
-rw-r--r--app/assets/javascripts/boards/components/board_card.js69
-rw-r--r--app/assets/javascripts/boards/components/board_card.js.es661
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es64
-rw-r--r--app/assets/javascripts/boards/models/list.js.es612
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js.es65
-rw-r--r--app/assets/javascripts/dispatcher.js.es68
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js8
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js18
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js201
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js542
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js265
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es652
-rw-r--r--app/assets/javascripts/milestone.js1
-rw-r--r--app/assets/javascripts/milestone_select.js5
-rw-r--r--app/assets/javascripts/new_branch_form.js30
-rw-r--r--app/assets/javascripts/profile/profile_bundle.js5
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js.es69
-rw-r--r--app/assets/javascripts/protected_branches/protected_branches_bundle.js8
-rw-r--r--app/assets/javascripts/snippet/snippet_bundle.js4
-rw-r--r--app/assets/javascripts/user_callout.js58
-rw-r--r--app/assets/javascripts/users/users_bundle.js4
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es616
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js.es62
-rw-r--r--app/assets/javascripts/vue_pipelines_index/stage.js.es636
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/framework/jquery.scss11
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss16
-rw-r--r--app/assets/stylesheets/pages/profile.scss38
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/system_info_controller.rb4
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/concerns/creates_commit.rb15
-rw-r--r--app/controllers/concerns/service_params.rb4
-rw-r--r--app/controllers/concerns/spammable_actions.rb2
-rw-r--r--app/controllers/jwt_controller.rb8
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb30
-rw-r--r--app/controllers/projects/git_http_client_controller.rb11
-rw-r--r--app/controllers/projects/merge_requests_controller.rb17
-rw-r--r--app/controllers/projects/pipelines_controller.rb12
-rw-r--r--app/controllers/sessions_controller.rb11
-rw-r--r--app/controllers/snippets_controller.rb5
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/notes_finder.rb11
-rw-r--r--app/finders/pipelines_finder.rb6
-rw-r--r--app/finders/todos_finder.rb4
-rw-r--r--app/helpers/application_helper.rb11
-rw-r--r--app/helpers/application_settings_helper.rb25
-rw-r--r--app/helpers/blob_helper.rb21
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/emails_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb6
-rw-r--r--app/helpers/javascript_helper.rb1
-rw-r--r--app/helpers/namespaces_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb11
-rw-r--r--app/helpers/sorting_helper.rb2
-rw-r--r--app/helpers/submodule_helper.rb8
-rw-r--r--app/helpers/tab_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb10
-rw-r--r--app/mailers/repository_check_mailer.rb11
-rw-r--r--app/models/application_setting.rb21
-rw-r--r--app/models/ci/build.rb12
-rw-r--r--app/models/ci/pipeline.rb19
-rw-r--r--app/models/ci/runner.rb4
-rw-r--r--app/models/ci/runner_project.rb2
-rw-r--r--app/models/ci/trigger.rb4
-rw-r--r--app/models/commit.rb17
-rw-r--r--app/models/commit_status.rb5
-rw-r--r--app/models/concerns/cache_markdown_field.rb11
-rw-r--r--app/models/concerns/case_sensitivity.rb11
-rw-r--r--app/models/concerns/has_status.rb12
-rw-r--r--app/models/concerns/issuable.rb28
-rw-r--r--app/models/concerns/reactive_service.rb2
-rw-r--r--app/models/concerns/sortable.rb11
-rw-r--r--app/models/concerns/uniquify.rb30
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/members/group_member.rb4
-rw-r--r--app/models/members/project_member.rb4
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/namespace.rb10
-rw-r--r--app/models/network/graph.rb11
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/notification_setting.rb4
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/project.rb54
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/chat_message/issue_message.rb3
-rw-r--r--app/models/project_services/drone_ci_service.rb6
-rw-r--r--app/models/project_services/hipchat_service.rb4
-rw-r--r--app/models/project_services/irker_service.rb3
-rw-r--r--app/models/project_services/kubernetes_service.rb16
-rw-r--r--app/models/project_services/mattermost_service.rb14
-rw-r--r--app/models/project_services/pivotaltracker_service.rb2
-rw-r--r--app/models/project_services/pushover_service.rb45
-rw-r--r--app/models/project_services/slack_service.rb14
-rw-r--r--app/models/project_statistics.rb2
-rw-r--r--app/models/project_wiki.rb13
-rw-r--r--app/models/protected_branch.rb4
-rw-r--r--app/models/repository.rb231
-rw-r--r--app/models/todo.rb2
-rw-r--r--app/models/user.rb63
-rw-r--r--app/policies/base_policy.rb4
-rw-r--r--app/policies/user_policy.rb8
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/base_service.rb4
-rw-r--r--app/services/ci/create_pipeline_builds_service.rb4
-rw-r--r--app/services/ci/retry_build_service.rb7
-rw-r--r--app/services/ci/retry_pipeline_service.rb6
-rw-r--r--app/services/create_branch_service.rb14
-rw-r--r--app/services/files/create_dir_service.rb2
-rw-r--r--app/services/files/create_service.rb7
-rw-r--r--app/services/files/destroy_service.rb2
-rw-r--r--app/services/files/multi_service.rb33
-rw-r--r--app/services/files/update_service.rb4
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/notes/slash_commands_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/download_service.rb4
-rw-r--r--app/services/projects/update_pages_service.rb2
-rw-r--r--app/services/protected_branches/api_update_service.rb8
-rw-r--r--app/services/system_hooks_service.rb4
-rw-r--r--app/services/system_note_service.rb17
-rw-r--r--app/services/users/destroy_service.rb21
-rw-r--r--app/uploaders/uploader_helper.rb6
-rw-r--r--app/validators/addressable_url_validator.rb2
-rw-r--r--app/validators/duration_validator.rb17
-rw-r--r--app/views/admin/application_settings/_form.html.haml12
-rw-r--r--app/views/admin/users/_user.html.haml2
-rw-r--r--app/views/admin/users/show.html.haml5
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml5
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml2
-rw-r--r--app/views/projects/blob/diff.html.haml4
-rw-r--r--app/views/projects/boards/_show.html.haml1
-rw-r--r--app/views/projects/boards/components/_card.html.haml10
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml8
-rw-r--r--app/views/projects/diffs/_file_header.html.haml4
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/pipelines/index.html.haml22
-rw-r--r--app/views/projects/pipelines/new.html.haml6
-rw-r--r--app/views/shared/_group_form.html.haml2
-rw-r--r--app/views/shared/icons/_icon_customization.svg1
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/users/show.html.haml1
-rw-r--r--app/workers/irker_worker.rb6
158 files changed, 1715 insertions, 1274 deletions
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
new file mode 100644
index 00000000000..38a8317dbd7
--- /dev/null
+++ b/app/assets/javascripts/ajax_loading_spinner.js
@@ -0,0 +1,35 @@
+class AjaxLoadingSpinner {
+ static init() {
+ const $elements = $('.js-ajax-loading-spinner');
+
+ $elements.on('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
+ $elements.on('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
+ }
+
+ static ajaxBeforeSend(e) {
+ e.target.setAttribute('disabled', '');
+ const iconElement = e.target.querySelector('i');
+ // get first fa- icon
+ const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g).first();
+ iconElement.dataset.icon = originalIcon;
+ AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
+ $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
+ }
+
+ static ajaxComplete(e) {
+ e.target.removeAttribute('disabled');
+ const iconElement = e.target.querySelector('i');
+ AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
+ $(e.target).off('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
+ }
+
+ static toggleLoadingIcon(iconElement) {
+ const classList = iconElement.classList;
+ classList.toggle(iconElement.dataset.icon);
+ classList.toggle('fa-spinner');
+ classList.toggle('fa-spin');
+ }
+}
+
+window.gl = window.gl || {};
+gl.AjaxLoadingSpinner = AjaxLoadingSpinner;
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 8e468faedbf..53d8d313e39 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -6,12 +6,8 @@
/* global AwardsHandler */
/* global Aside */
-function requireAll(context) { return context.keys().map(context); }
-
window.$ = window.jQuery = require('jquery');
-require('jquery-ui/ui/autocomplete');
require('jquery-ui/ui/draggable');
-require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
@@ -46,15 +42,176 @@ require('./shortcuts_dashboard_navigation');
require('./shortcuts_issuable');
require('./shortcuts_network');
require('vendor/jquery.nicescroll');
-requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
-requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
+
+// behaviors
+require('./behaviors/autosize');
+require('./behaviors/details_behavior');
+require('./behaviors/quick_submit');
+require('./behaviors/requires_input');
+require('./behaviors/toggler_behavior');
+
+// blob
+require('./blob/blob_ci_yaml');
+require('./blob/blob_dockerfile_selector');
+require('./blob/blob_dockerfile_selectors');
+require('./blob/blob_file_dropzone');
+require('./blob/blob_gitignore_selector');
+require('./blob/blob_gitignore_selectors');
+require('./blob/blob_license_selector');
+require('./blob/blob_license_selectors');
+require('./blob/template_selector');
+
+// templates
+require('./templates/issuable_template_selector');
+require('./templates/issuable_template_selectors');
+
+// commit
+require('./commit/file.js');
+require('./commit/image_file.js');
+
+// extensions
+require('./extensions/array');
+require('./extensions/custom_event');
+require('./extensions/element');
+require('./extensions/jquery');
+require('./extensions/object');
+
+// lib/utils
+require('./lib/utils/animate');
+require('./lib/utils/bootstrap_linked_tabs');
+require('./lib/utils/common_utils');
+require('./lib/utils/datetime_utility');
+require('./lib/utils/notify');
+require('./lib/utils/pretty_time');
+require('./lib/utils/text_utility');
+require('./lib/utils/type_utility');
+require('./lib/utils/url_utility');
+
+// u2f
+require('./u2f/authenticate');
+require('./u2f/error');
+require('./u2f/register');
+require('./u2f/util');
+
+// droplab
+require('./droplab/droplab');
+require('./droplab/droplab_ajax');
+require('./droplab/droplab_ajax_filter');
+require('./droplab/droplab_filter');
+
+// everything else
+require('./abuse_reports');
+require('./activities');
+require('./admin');
+require('./ajax_loading_spinner');
+require('./api');
+require('./aside');
+require('./autosave');
+require('./awards_handler');
+require('./breakpoints');
+require('./broadcast_message');
+require('./build');
+require('./build_artifacts');
+require('./build_variables');
+require('./ci_lint_editor');
+require('./commit');
+require('./commits');
+require('./compare');
+require('./compare_autocomplete');
+require('./confirm_danger_modal');
+require('./copy_as_gfm');
+require('./copy_to_clipboard');
+require('./create_label');
+require('./diff');
+require('./dispatcher');
+require('./dropzone_input');
+require('./due_date_select');
+require('./files_comment_button');
+require('./flash');
+require('./gfm_auto_complete');
+require('./gl_dropdown');
+require('./gl_field_error');
+require('./gl_field_errors');
+require('./gl_form');
+require('./group_avatar');
+require('./group_label_subscription');
+require('./groups_select');
+require('./header');
+require('./importer_status');
+require('./issuable');
+require('./issuable_context');
+require('./issuable_form');
+require('./issue');
+require('./issue_status_select');
+require('./issues_bulk_assignment');
+require('./label_manager');
+require('./labels');
+require('./labels_select');
+require('./layout_nav');
+require('./line_highlighter');
+require('./logo');
+require('./member_expiration_date');
+require('./members');
+require('./merge_request');
+require('./merge_request_tabs');
+require('./merge_request_widget');
+require('./merged_buttons');
+require('./milestone');
+require('./milestone_select');
+require('./mini_pipeline_graph_dropdown');
+require('./namespace_select');
+require('./new_branch_form');
+require('./new_commit_form');
+require('./notes');
+require('./notifications_dropdown');
+require('./notifications_form');
+require('./pager');
+require('./pipelines');
+require('./preview_markdown');
+require('./project');
+require('./project_avatar');
+require('./project_find_file');
+require('./project_fork');
+require('./project_import');
+require('./project_label_subscription');
+require('./project_new');
+require('./project_select');
+require('./project_show');
+require('./project_variables');
+require('./projects_list');
+require('./render_gfm');
+require('./render_math');
+require('./right_sidebar');
+require('./search');
+require('./search_autocomplete');
+require('./shortcuts');
+require('./shortcuts_blob');
+require('./shortcuts_dashboard_navigation');
+require('./shortcuts_find_file');
+require('./shortcuts_issuable');
+require('./shortcuts_navigation');
+require('./shortcuts_network');
+require('./signin_tabs_memoizer');
+require('./single_file_diff');
+require('./smart_interval');
+require('./snippets_list');
+require('./star');
+require('./subbable_resource');
+require('./subscription');
+require('./subscription_select');
+require('./syntax_highlight');
+require('./task_list');
+require('./todos');
+require('./tree');
+require('./user');
+require('./user_tabs');
+require('./username_validator');
+require('./users_select');
+require('./version_check_image');
+require('./visibility_select');
+require('./wikis');
+require('./zen_mode');
+
require('vendor/fuzzaldrin-plus');
require('es6-promise').polyfill();
diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js
new file mode 100644
index 00000000000..52f61d84517
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_card.js
@@ -0,0 +1,69 @@
+/* global Vue */
+require('./issue_card_inner');
+
+const Store = gl.issueBoards.BoardsStore;
+
+module.exports = {
+ name: 'BoardsIssueCard',
+ template: `
+ <li class="card"
+ :class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
+ :index="index"
+ :data-issue-id="issue.id"
+ @mousedown="mouseDown"
+ @mousemove="mouseMove"
+ @mouseup="showIssue($event)">
+ <issue-card-inner
+ :list="list"
+ :issue="issue"
+ :issue-link-base="issueLinkBase"
+ :root-path="rootPath" />
+ </li>
+ `,
+ components: {
+ 'issue-card-inner': gl.issueBoards.IssueCardInner,
+ },
+ props: {
+ list: Object,
+ issue: Object,
+ issueLinkBase: String,
+ disabled: Boolean,
+ index: Number,
+ rootPath: String,
+ },
+ data() {
+ return {
+ showDetail: false,
+ detailIssue: Store.detail,
+ };
+ },
+ computed: {
+ issueDetailVisible() {
+ return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
+ },
+ },
+ methods: {
+ mouseDown() {
+ this.showDetail = true;
+ },
+ mouseMove() {
+ this.showDetail = false;
+ },
+ showIssue(e) {
+ const targetTagName = e.target.tagName.toLowerCase();
+
+ if (targetTagName === 'a' || targetTagName === 'button') return;
+
+ if (this.showDetail) {
+ this.showDetail = false;
+
+ if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
+ Store.detail.issue = {};
+ } else {
+ Store.detail.issue = this.issue;
+ Store.detail.list = this.list;
+ }
+ }
+ },
+ },
+};
diff --git a/app/assets/javascripts/boards/components/board_card.js.es6 b/app/assets/javascripts/boards/components/board_card.js.es6
deleted file mode 100644
index 0ea66bd027c..00000000000
--- a/app/assets/javascripts/boards/components/board_card.js.es6
+++ /dev/null
@@ -1,61 +0,0 @@
-/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
-/* global Vue */
-
-require('./issue_card_inner');
-
-(() => {
- const Store = gl.issueBoards.BoardsStore;
-
- window.gl = window.gl || {};
- window.gl.issueBoards = window.gl.issueBoards || {};
-
- gl.issueBoards.BoardCard = Vue.extend({
- template: '#js-board-list-card',
- components: {
- 'issue-card-inner': gl.issueBoards.IssueCardInner,
- },
- props: {
- list: Object,
- issue: Object,
- issueLinkBase: String,
- disabled: Boolean,
- index: Number,
- rootPath: String,
- },
- data () {
- return {
- showDetail: false,
- detailIssue: Store.detail
- };
- },
- computed: {
- issueDetailVisible () {
- return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
- }
- },
- methods: {
- mouseDown () {
- this.showDetail = true;
- },
- mouseMove() {
- this.showDetail = false;
- },
- showIssue (e) {
- const targetTagName = e.target.tagName.toLowerCase();
-
- if (targetTagName === 'a' || targetTagName === 'button') return;
-
- if (this.showDetail) {
- this.showDetail = false;
-
- if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
- Store.detail.issue = {};
- } else {
- Store.detail.issue = this.issue;
- Store.detail.list = this.list;
- }
- }
- }
- }
- });
-})();
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index 60b0a30af3f..d92047cc0f8 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -2,7 +2,7 @@
/* global Vue */
/* global Sortable */
-require('./board_card');
+const boardCard = require('./board_card');
require('./board_new_issue');
(() => {
@@ -14,7 +14,7 @@ require('./board_new_issue');
gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template',
components: {
- 'board-card': gl.issueBoards.BoardCard,
+ boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
},
props: {
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index 5152be56b66..8158ed4ec2c 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -123,14 +123,18 @@ class List {
if (listFrom) {
this.issuesSize += 1;
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
- .then(() => {
- listFrom.getIssues(false);
- });
+ this.updateIssueLabel(issue, listFrom);
}
}
}
+ updateIssueLabel(issue, listFrom) {
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
+ .then(() => {
+ listFrom.getIssues(false);
+ });
+ }
+
findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0];
}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index 50842ecbaaa..56436c8fdc7 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -92,9 +92,12 @@
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
- // Add to new lists issues if it doesn't already exist
if (!issueTo) {
+ // Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
+ } else {
+ listTo.updateIssueLabel(issue, listFrom);
+ issueTo.removeLabel(listFrom.label);
}
if (listTo.type === 'done') {
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index f55db02f0fd..0f678492d4c 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -36,6 +36,7 @@
/* global Shortcuts */
const ShortcutsBlob = require('./shortcuts_blob');
+const UserCallout = require('./user_callout');
(function() {
var Dispatcher;
@@ -108,6 +109,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:compare:show':
new gl.Diff();
break;
+ case 'projects:branches:index':
+ gl.AjaxLoadingSpinner.init();
+ break;
case 'projects:issues:new':
case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation();
@@ -274,6 +278,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show':
new gl.CILintEditor();
break;
+ case 'users:show':
+ new UserCallout();
+ break;
}
switch (path.first()) {
case 'sessions':
@@ -310,6 +317,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard':
case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
+ new UserCallout();
break;
case 'profiles':
new NotificationsForm();
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index 086dcb34571..ea5afbd9d29 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,4 +1,4 @@
-require('./stat_graph_contributors_graph');
-require('./stat_graph_contributors_util');
-require('./stat_graph_contributors');
-require('./stat_graph');
+import ContributorsStatGraph from './stat_graph_contributors';
+
+// export to global scope
+window.ContributorsStatGraph = ContributorsStatGraph;
diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js
deleted file mode 100644
index 75a53aae33c..00000000000
--- a/app/assets/javascripts/graphs/stat_graph.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, max-len */
-(function() {
- this.StatGraph = (function() {
- function StatGraph() {}
-
- StatGraph.log = {};
-
- StatGraph.get_log = function() {
- return this.log;
- };
-
- StatGraph.set_log = function(data) {
- return this.log = data;
- };
-
- return StatGraph;
- })();
-}).call(window);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index bbfb467ad50..c6be4c9e8fe 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -1,116 +1,111 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */
-/* global ContributorsGraph */
-/* global ContributorsAuthorGraph */
-/* global ContributorsMasterGraph */
-/* global ContributorsStatGraphUtil */
-/* global d3 */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
-window.d3 = require('d3');
+import d3 from 'd3';
+import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
+import ContributorsStatGraphUtil from './stat_graph_contributors_util';
-(function() {
- this.ContributorsStatGraph = (function() {
- function ContributorsStatGraph() {}
+export default (function() {
+ function ContributorsStatGraph() {}
- ContributorsStatGraph.prototype.init = function(log) {
- var author_commits, total_commits;
- this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
- this.set_current_field("commits");
- total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
- author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
- this.add_master_graph(total_commits);
- this.add_authors_graph(author_commits);
- return this.change_date_header();
- };
+ ContributorsStatGraph.prototype.init = function(log) {
+ var author_commits, total_commits;
+ this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
+ this.set_current_field("commits");
+ total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+ author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
+ this.add_master_graph(total_commits);
+ this.add_authors_graph(author_commits);
+ return this.change_date_header();
+ };
- ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
- this.master_graph = new ContributorsMasterGraph(total_data);
- return this.master_graph.draw();
- };
+ ContributorsStatGraph.prototype.add_master_graph = function(total_data) {
+ this.master_graph = new ContributorsMasterGraph(total_data);
+ return this.master_graph.draw();
+ };
- ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
- var limited_author_data;
- this.authors = [];
- limited_author_data = author_data.slice(0, 100);
- return _.each(limited_author_data, (function(_this) {
- return function(d) {
- var author_graph, author_header;
- author_header = _this.create_author_header(d);
- $(".contributors-list").append(author_header);
- _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
- return author_graph.draw();
- };
- })(this));
- };
+ ContributorsStatGraph.prototype.add_authors_graph = function(author_data) {
+ var limited_author_data;
+ this.authors = [];
+ limited_author_data = author_data.slice(0, 100);
+ return _.each(limited_author_data, (function(_this) {
+ return function(d) {
+ var author_graph, author_header;
+ author_header = _this.create_author_header(d);
+ $(".contributors-list").append(author_header);
+ _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates);
+ return author_graph.draw();
+ };
+ })(this));
+ };
- ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
- var commits;
- commits = $('<span/>', {
- "class": 'graph-author-commits-count'
- });
- commits.text(author.commits + " commits");
- return $('<span/>').append(commits);
- };
+ ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
+ var commits;
+ commits = $('<span/>', {
+ "class": 'graph-author-commits-count'
+ });
+ commits.text(author.commits + " commits");
+ return $('<span/>').append(commits);
+ };
- ContributorsStatGraph.prototype.create_author_header = function(author) {
- var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
- list_item = $('<li/>', {
- "class": 'person',
- style: 'display: block;'
- });
- author_name = $('<h4>' + author.author_name + '</h4>');
- author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
- author_commit_info_span = $('<span/>', {
- "class": 'commits'
- });
- author_commit_info = this.format_author_commit_info(author);
- author_commit_info_span.html(author_commit_info);
- list_item.append(author_name);
- list_item.append(author_email);
- list_item.append(author_commit_info_span);
- return list_item;
- };
+ ContributorsStatGraph.prototype.create_author_header = function(author) {
+ var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
+ list_item = $('<li/>', {
+ "class": 'person',
+ style: 'display: block;'
+ });
+ author_name = $('<h4>' + author.author_name + '</h4>');
+ author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
+ author_commit_info_span = $('<span/>', {
+ "class": 'commits'
+ });
+ author_commit_info = this.format_author_commit_info(author);
+ author_commit_info_span.html(author_commit_info);
+ list_item.append(author_name);
+ list_item.append(author_email);
+ list_item.append(author_commit_info_span);
+ return list_item;
+ };
- ContributorsStatGraph.prototype.redraw_master = function() {
- var total_data;
- total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
- this.master_graph.set_data(total_data);
- return this.master_graph.redraw();
- };
+ ContributorsStatGraph.prototype.redraw_master = function() {
+ var total_data;
+ total_data = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
+ this.master_graph.set_data(total_data);
+ return this.master_graph.redraw();
+ };
- ContributorsStatGraph.prototype.redraw_authors = function() {
- var author_commits, x_domain;
- $("ol").html("");
- x_domain = ContributorsGraph.prototype.x_domain;
- author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
- return _.each(author_commits, (function(_this) {
- return function(d) {
- _this.redraw_author_commit_info(d);
- $(_this.authors[d.author_name].list_item).appendTo("ol");
- _this.authors[d.author_name].set_data(d.dates);
- return _this.authors[d.author_name].redraw();
- };
- })(this));
- };
+ ContributorsStatGraph.prototype.redraw_authors = function() {
+ var author_commits, x_domain;
+ $("ol").html("");
+ x_domain = ContributorsGraph.prototype.x_domain;
+ author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
+ return _.each(author_commits, (function(_this) {
+ return function(d) {
+ _this.redraw_author_commit_info(d);
+ $(_this.authors[d.author_name].list_item).appendTo("ol");
+ _this.authors[d.author_name].set_data(d.dates);
+ return _this.authors[d.author_name].redraw();
+ };
+ })(this));
+ };
- ContributorsStatGraph.prototype.set_current_field = function(field) {
- return this.field = field;
- };
+ ContributorsStatGraph.prototype.set_current_field = function(field) {
+ return this.field = field;
+ };
- ContributorsStatGraph.prototype.change_date_header = function() {
- var print, print_date_format, x_domain;
- x_domain = ContributorsGraph.prototype.x_domain;
- print_date_format = d3.time.format("%B %e %Y");
- print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
- return $("#date_header").text(print);
- };
+ ContributorsStatGraph.prototype.change_date_header = function() {
+ var print, print_date_format, x_domain;
+ x_domain = ContributorsGraph.prototype.x_domain;
+ print_date_format = d3.time.format("%B %e %Y");
+ print = print_date_format(x_domain[0]) + " - " + print_date_format(x_domain[1]);
+ return $("#date_header").text(print);
+ };
- ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
- var author_commit_info, author_list_item;
- author_list_item = $(this.authors[author.author_name].list_item);
- author_commit_info = this.format_author_commit_info(author);
- return author_list_item.find("span").html(author_commit_info);
- };
+ ContributorsStatGraph.prototype.redraw_author_commit_info = function(author) {
+ var author_commit_info, author_list_item;
+ author_list_item = $(this.authors[author.author_name].list_item);
+ author_commit_info = this.format_author_commit_info(author);
+ return author_list_item.find("span").html(author_commit_info);
+ };
- return ContributorsStatGraph;
- })();
-}).call(window);
+ return ContributorsStatGraph;
+})();
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index 228771da4ee..521bc77db66 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -1,276 +1,272 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return */
-/* global d3 */
-/* global ContributorsGraph */
-
-window.d3 = require('d3');
-
-(function() {
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
- this.ContributorsGraph = (function() {
- function ContributorsGraph() {}
-
- ContributorsGraph.prototype.MARGIN = {
- top: 20,
- right: 20,
- bottom: 30,
- left: 50
- };
-
- ContributorsGraph.prototype.x_domain = null;
-
- ContributorsGraph.prototype.y_domain = null;
-
- ContributorsGraph.prototype.dates = [];
-
- ContributorsGraph.set_x_domain = function(data) {
- return ContributorsGraph.prototype.x_domain = data;
- };
-
- ContributorsGraph.set_y_domain = function(data) {
- return ContributorsGraph.prototype.y_domain = [
- 0, d3.max(data, function(d) {
- return d.commits = d.commits || d.additions || d.deletions;
- })
- ];
- };
-
- ContributorsGraph.init_x_domain = function(data) {
- return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
- return d.date;
- });
- };
-
- ContributorsGraph.init_y_domain = function(data) {
- return ContributorsGraph.prototype.y_domain = [
- 0, d3.max(data, function(d) {
- return d.commits = d.commits || d.additions || d.deletions;
- })
- ];
- };
-
- ContributorsGraph.init_domain = function(data) {
- ContributorsGraph.init_x_domain(data);
- return ContributorsGraph.init_y_domain(data);
- };
-
- ContributorsGraph.set_dates = function(data) {
- return ContributorsGraph.prototype.dates = data;
- };
-
- ContributorsGraph.prototype.set_x_domain = function() {
- return this.x.domain(this.x_domain);
- };
-
- ContributorsGraph.prototype.set_y_domain = function() {
- return this.y.domain(this.y_domain);
- };
-
- ContributorsGraph.prototype.set_domain = function() {
- this.set_x_domain();
- return this.set_y_domain();
- };
-
- ContributorsGraph.prototype.create_scale = function(width, height) {
- this.x = d3.time.scale().range([0, width]).clamp(true);
- return this.y = d3.scale.linear().range([height, 0]).nice();
- };
-
- ContributorsGraph.prototype.draw_x_axis = function() {
- return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
- };
-
- ContributorsGraph.prototype.draw_y_axis = function() {
- return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
- };
-
- ContributorsGraph.prototype.set_data = function(data) {
- return this.data = data;
- };
-
- return ContributorsGraph;
- })();
-
- this.ContributorsMasterGraph = (function(superClass) {
- extend(ContributorsMasterGraph, superClass);
-
- function ContributorsMasterGraph(data1) {
- this.data = data1;
- this.update_content = bind(this.update_content, this);
- this.width = $('.content').width() - 70;
- this.height = 200;
- this.x = null;
- this.y = null;
- this.x_axis = null;
- this.y_axis = null;
- this.area = null;
- this.svg = null;
- this.brush = null;
- this.x_max_domain = null;
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
+
+import d3 from 'd3';
+
+const bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+const hasProp = {}.hasOwnProperty;
+
+export const ContributorsGraph = (function() {
+ function ContributorsGraph() {}
+
+ ContributorsGraph.prototype.MARGIN = {
+ top: 20,
+ right: 20,
+ bottom: 30,
+ left: 50
+ };
+
+ ContributorsGraph.prototype.x_domain = null;
+
+ ContributorsGraph.prototype.y_domain = null;
+
+ ContributorsGraph.prototype.dates = [];
+
+ ContributorsGraph.set_x_domain = function(data) {
+ return ContributorsGraph.prototype.x_domain = data;
+ };
+
+ ContributorsGraph.set_y_domain = function(data) {
+ return ContributorsGraph.prototype.y_domain = [
+ 0, d3.max(data, function(d) {
+ return d.commits = d.commits || d.additions || d.deletions;
+ })
+ ];
+ };
+
+ ContributorsGraph.init_x_domain = function(data) {
+ return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
+ return d.date;
+ });
+ };
+
+ ContributorsGraph.init_y_domain = function(data) {
+ return ContributorsGraph.prototype.y_domain = [
+ 0, d3.max(data, function(d) {
+ return d.commits = d.commits || d.additions || d.deletions;
+ })
+ ];
+ };
+
+ ContributorsGraph.init_domain = function(data) {
+ ContributorsGraph.init_x_domain(data);
+ return ContributorsGraph.init_y_domain(data);
+ };
+
+ ContributorsGraph.set_dates = function(data) {
+ return ContributorsGraph.prototype.dates = data;
+ };
+
+ ContributorsGraph.prototype.set_x_domain = function() {
+ return this.x.domain(this.x_domain);
+ };
+
+ ContributorsGraph.prototype.set_y_domain = function() {
+ return this.y.domain(this.y_domain);
+ };
+
+ ContributorsGraph.prototype.set_domain = function() {
+ this.set_x_domain();
+ return this.set_y_domain();
+ };
+
+ ContributorsGraph.prototype.create_scale = function(width, height) {
+ this.x = d3.time.scale().range([0, width]).clamp(true);
+ return this.y = d3.scale.linear().range([height, 0]).nice();
+ };
+
+ ContributorsGraph.prototype.draw_x_axis = function() {
+ return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
+ };
+
+ ContributorsGraph.prototype.draw_y_axis = function() {
+ return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
+ };
+
+ ContributorsGraph.prototype.set_data = function(data) {
+ return this.data = data;
+ };
+
+ return ContributorsGraph;
+})();
+
+export const ContributorsMasterGraph = (function(superClass) {
+ extend(ContributorsMasterGraph, superClass);
+
+ function ContributorsMasterGraph(data1) {
+ this.data = data1;
+ this.update_content = bind(this.update_content, this);
+ this.width = $('.content').width() - 70;
+ this.height = 200;
+ this.x = null;
+ this.y = null;
+ this.x_axis = null;
+ this.y_axis = null;
+ this.area = null;
+ this.svg = null;
+ this.brush = null;
+ this.x_max_domain = null;
+ }
+
+ ContributorsMasterGraph.prototype.process_dates = function(data) {
+ var dates;
+ dates = this.get_dates(data);
+ this.parse_dates(data);
+ return ContributorsGraph.set_dates(dates);
+ };
+
+ ContributorsMasterGraph.prototype.get_dates = function(data) {
+ return _.pluck(data, 'date');
+ };
+
+ ContributorsMasterGraph.prototype.parse_dates = function(data) {
+ var parseDate;
+ parseDate = d3.time.format("%Y-%m-%d").parse;
+ return data.forEach(function(d) {
+ return d.date = parseDate(d.date);
+ });
+ };
+
+ ContributorsMasterGraph.prototype.create_scale = function() {
+ return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
+ };
+
+ ContributorsMasterGraph.prototype.create_axes = function() {
+ this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
+ return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+ };
+
+ ContributorsMasterGraph.prototype.create_svg = function() {
+ return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ };
+
+ ContributorsMasterGraph.prototype.create_area = function(x, y) {
+ return this.area = d3.svg.area().x(function(d) {
+ return x(d.date);
+ }).y0(this.height).y1(function(d) {
+ d.commits = d.commits || d.additions || d.deletions;
+ return y(d.commits);
+ }).interpolate("basis");
+ };
+
+ ContributorsMasterGraph.prototype.create_brush = function() {
+ return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
+ };
+
+ ContributorsMasterGraph.prototype.draw_path = function(data) {
+ return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
+ };
+
+ ContributorsMasterGraph.prototype.add_brush = function() {
+ return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
+ };
+
+ ContributorsMasterGraph.prototype.update_content = function() {
+ ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
+ return $("#brush_change").trigger('change');
+ };
+
+ ContributorsMasterGraph.prototype.draw = function() {
+ this.process_dates(this.data);
+ this.create_scale();
+ this.create_axes();
+ ContributorsGraph.init_domain(this.data);
+ this.x_max_domain = this.x_domain;
+ this.set_domain();
+ this.create_area(this.x, this.y);
+ this.create_svg();
+ this.create_brush();
+ this.draw_path(this.data);
+ this.draw_x_axis();
+ this.draw_y_axis();
+ return this.add_brush();
+ };
+
+ ContributorsMasterGraph.prototype.redraw = function() {
+ this.process_dates(this.data);
+ ContributorsGraph.set_y_domain(this.data);
+ this.set_y_domain();
+ this.svg.select("path").datum(this.data);
+ this.svg.select("path").attr("d", this.area);
+ return this.svg.select(".y.axis").call(this.y_axis);
+ };
+
+ return ContributorsMasterGraph;
+})(ContributorsGraph);
+
+export const ContributorsAuthorGraph = (function(superClass) {
+ extend(ContributorsAuthorGraph, superClass);
+
+ function ContributorsAuthorGraph(data1) {
+ this.data = data1;
+ // Don't split graph size in half for mobile devices.
+ if ($(window).width() < 768) {
+ this.width = $('.content').width() - 80;
+ } else {
+ this.width = ($('.content').width() / 2) - 100;
}
-
- ContributorsMasterGraph.prototype.process_dates = function(data) {
- var dates;
- dates = this.get_dates(data);
- this.parse_dates(data);
- return ContributorsGraph.set_dates(dates);
- };
-
- ContributorsMasterGraph.prototype.get_dates = function(data) {
- return _.pluck(data, 'date');
- };
-
- ContributorsMasterGraph.prototype.parse_dates = function(data) {
+ this.height = 200;
+ this.x = null;
+ this.y = null;
+ this.x_axis = null;
+ this.y_axis = null;
+ this.area = null;
+ this.svg = null;
+ this.list_item = null;
+ }
+
+ ContributorsAuthorGraph.prototype.create_scale = function() {
+ return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
+ };
+
+ ContributorsAuthorGraph.prototype.create_axes = function() {
+ this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
+ return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
+ };
+
+ ContributorsAuthorGraph.prototype.create_area = function(x, y) {
+ return this.area = d3.svg.area().x(function(d) {
var parseDate;
parseDate = d3.time.format("%Y-%m-%d").parse;
- return data.forEach(function(d) {
- return d.date = parseDate(d.date);
- });
- };
-
- ContributorsMasterGraph.prototype.create_scale = function() {
- return ContributorsMasterGraph.__super__.create_scale.call(this, this.width, this.height);
- };
-
- ContributorsMasterGraph.prototype.create_axes = function() {
- this.x_axis = d3.svg.axis().scale(this.x).orient("bottom");
- return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
- };
-
- ContributorsMasterGraph.prototype.create_svg = function() {
- return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
- };
-
- ContributorsMasterGraph.prototype.create_area = function(x, y) {
- return this.area = d3.svg.area().x(function(d) {
- return x(d.date);
- }).y0(this.height).y1(function(d) {
- d.commits = d.commits || d.additions || d.deletions;
- return y(d.commits);
- }).interpolate("basis");
- };
-
- ContributorsMasterGraph.prototype.create_brush = function() {
- return this.brush = d3.svg.brush().x(this.x).on("brushend", this.update_content);
- };
-
- ContributorsMasterGraph.prototype.draw_path = function(data) {
- return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
- };
-
- ContributorsMasterGraph.prototype.add_brush = function() {
- return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
- };
-
- ContributorsMasterGraph.prototype.update_content = function() {
- ContributorsGraph.set_x_domain(this.brush.empty() ? this.x_max_domain : this.brush.extent());
- return $("#brush_change").trigger('change');
- };
-
- ContributorsMasterGraph.prototype.draw = function() {
- this.process_dates(this.data);
- this.create_scale();
- this.create_axes();
- ContributorsGraph.init_domain(this.data);
- this.x_max_domain = this.x_domain;
- this.set_domain();
- this.create_area(this.x, this.y);
- this.create_svg();
- this.create_brush();
- this.draw_path(this.data);
- this.draw_x_axis();
- this.draw_y_axis();
- return this.add_brush();
- };
-
- ContributorsMasterGraph.prototype.redraw = function() {
- this.process_dates(this.data);
- ContributorsGraph.set_y_domain(this.data);
- this.set_y_domain();
- this.svg.select("path").datum(this.data);
- this.svg.select("path").attr("d", this.area);
- return this.svg.select(".y.axis").call(this.y_axis);
- };
-
- return ContributorsMasterGraph;
- })(ContributorsGraph);
-
- this.ContributorsAuthorGraph = (function(superClass) {
- extend(ContributorsAuthorGraph, superClass);
-
- function ContributorsAuthorGraph(data1) {
- this.data = data1;
- // Don't split graph size in half for mobile devices.
- if ($(window).width() < 768) {
- this.width = $('.content').width() - 80;
- } else {
- this.width = ($('.content').width() / 2) - 100;
- }
- this.height = 200;
- this.x = null;
- this.y = null;
- this.x_axis = null;
- this.y_axis = null;
- this.area = null;
- this.svg = null;
- this.list_item = null;
- }
-
- ContributorsAuthorGraph.prototype.create_scale = function() {
- return ContributorsAuthorGraph.__super__.create_scale.call(this, this.width, this.height);
- };
-
- ContributorsAuthorGraph.prototype.create_axes = function() {
- this.x_axis = d3.svg.axis().scale(this.x).orient("bottom").ticks(8);
- return this.y_axis = d3.svg.axis().scale(this.y).orient("left").ticks(5);
- };
-
- ContributorsAuthorGraph.prototype.create_area = function(x, y) {
- return this.area = d3.svg.area().x(function(d) {
- var parseDate;
- parseDate = d3.time.format("%Y-%m-%d").parse;
- return x(parseDate(d));
- }).y0(this.height).y1((function(_this) {
- return function(d) {
- if (_this.data[d] != null) {
- return y(_this.data[d]);
- } else {
- return y(0);
- }
- };
- })(this)).interpolate("basis");
- };
-
- ContributorsAuthorGraph.prototype.create_svg = function() {
- this.list_item = d3.selectAll(".person")[0].pop();
- return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
- };
-
- ContributorsAuthorGraph.prototype.draw_path = function(data) {
- return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
- };
-
- ContributorsAuthorGraph.prototype.draw = function() {
- this.create_scale();
- this.create_axes();
- this.set_domain();
- this.create_area(this.x, this.y);
- this.create_svg();
- this.draw_path(this.dates);
- this.draw_x_axis();
- return this.draw_y_axis();
- };
-
- ContributorsAuthorGraph.prototype.redraw = function() {
- this.set_domain();
- this.svg.select("path").datum(this.dates);
- this.svg.select("path").attr("d", this.area);
- this.svg.select(".x.axis").call(this.x_axis);
- return this.svg.select(".y.axis").call(this.y_axis);
- };
-
- return ContributorsAuthorGraph;
- })(ContributorsGraph);
-}).call(window);
+ return x(parseDate(d));
+ }).y0(this.height).y1((function(_this) {
+ return function(d) {
+ if (_this.data[d] != null) {
+ return y(_this.data[d]);
+ } else {
+ return y(0);
+ }
+ };
+ })(this)).interpolate("basis");
+ };
+
+ ContributorsAuthorGraph.prototype.create_svg = function() {
+ this.list_item = d3.selectAll(".person")[0].pop();
+ return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ };
+
+ ContributorsAuthorGraph.prototype.draw_path = function(data) {
+ return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
+ };
+
+ ContributorsAuthorGraph.prototype.draw = function() {
+ this.create_scale();
+ this.create_axes();
+ this.set_domain();
+ this.create_area(this.x, this.y);
+ this.create_svg();
+ this.draw_path(this.dates);
+ this.draw_x_axis();
+ return this.draw_y_axis();
+ };
+
+ ContributorsAuthorGraph.prototype.redraw = function() {
+ this.set_domain();
+ this.svg.select("path").datum(this.dates);
+ this.svg.select("path").attr("d", this.area);
+ this.svg.select(".x.axis").call(this.x_axis);
+ return this.svg.select(".y.axis").call(this.y_axis);
+ };
+
+ return ContributorsAuthorGraph;
+})(ContributorsGraph);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
index 7954c583598..c583757f3f2 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -1,138 +1,137 @@
/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */
-(function() {
- window.ContributorsStatGraphUtil = {
- parse_log: function(log) {
- var by_author, by_email, data, entry, i, len, total, normalized_email;
- total = {};
- by_author = {};
- by_email = {};
- for (i = 0, len = log.length; i < len; i += 1) {
- entry = log[i];
- if (total[entry.date] == null) {
- this.add_date(entry.date, total);
- }
- normalized_email = entry.author_email.toLowerCase();
- data = by_author[entry.author_name] || by_email[normalized_email];
- if (data == null) {
- data = this.add_author(entry, by_author, by_email);
- }
- if (!data[entry.date]) {
- this.add_date(entry.date, data);
- }
- this.store_data(entry, total[entry.date], data[entry.date]);
- }
- total = _.toArray(total);
- by_author = _.toArray(by_author);
- return {
- total: total,
- by_author: by_author
- };
- },
- add_date: function(date, collection) {
- collection[date] = {};
- return collection[date].date = date;
- },
- add_author: function(author, by_author, by_email) {
- var data, normalized_email;
- data = {};
- data.author_name = author.author_name;
- data.author_email = author.author_email;
- normalized_email = author.author_email.toLowerCase();
- by_author[author.author_name] = data;
- by_email[normalized_email] = data;
- return data;
- },
- store_data: function(entry, total, by_author) {
- this.store_commits(total, by_author);
- this.store_additions(entry, total, by_author);
- return this.store_deletions(entry, total, by_author);
- },
- store_commits: function(total, by_author) {
- this.add(total, "commits", 1);
- return this.add(by_author, "commits", 1);
- },
- add: function(collection, field, value) {
- if (collection[field] == null) {
- collection[field] = 0;
- }
- return collection[field] += value;
- },
- store_additions: function(entry, total, by_author) {
- if (entry.additions == null) {
- entry.additions = 0;
+
+export default {
+ parse_log: function(log) {
+ var by_author, by_email, data, entry, i, len, total, normalized_email;
+ total = {};
+ by_author = {};
+ by_email = {};
+ for (i = 0, len = log.length; i < len; i += 1) {
+ entry = log[i];
+ if (total[entry.date] == null) {
+ this.add_date(entry.date, total);
}
- this.add(total, "additions", entry.additions);
- return this.add(by_author, "additions", entry.additions);
- },
- store_deletions: function(entry, total, by_author) {
- if (entry.deletions == null) {
- entry.deletions = 0;
+ normalized_email = entry.author_email.toLowerCase();
+ data = by_author[entry.author_name] || by_email[normalized_email];
+ if (data == null) {
+ data = this.add_author(entry, by_author, by_email);
}
- this.add(total, "deletions", entry.deletions);
- return this.add(by_author, "deletions", entry.deletions);
- },
- get_total_data: function(parsed_log, field) {
- var log, total_data;
- log = parsed_log.total;
- total_data = this.pick_field(log, field);
- return _.sortBy(total_data, function(d) {
- return d.date;
- });
- },
- pick_field: function(log, field) {
- var total_data;
- total_data = [];
- _.each(log, function(d) {
- return total_data.push(_.pick(d, [field, 'date']));
- });
- return total_data;
- },
- get_author_data: function(parsed_log, field, date_range) {
- var author_data, log;
- if (date_range == null) {
- date_range = null;
- }
- log = parsed_log.by_author;
- author_data = [];
- _.each(log, (function(_this) {
- return function(log_entry) {
- var parsed_log_entry;
- parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
- if (!_.isEmpty(parsed_log_entry.dates)) {
- return author_data.push(parsed_log_entry);
- }
- };
- })(this));
- return _.sortBy(author_data, function(d) {
- return d[field];
- }).reverse();
- },
- parse_log_entry: function(log_entry, field, date_range) {
- var parsed_entry;
- parsed_entry = {};
- parsed_entry.author_name = log_entry.author_name;
- parsed_entry.author_email = log_entry.author_email;
- parsed_entry.dates = {};
- parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
- _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
- return function(value, key) {
- if (_this.in_range(value.date, date_range)) {
- parsed_entry.dates[value.date] = value[field];
- parsed_entry.commits += value.commits;
- parsed_entry.additions += value.additions;
- return parsed_entry.deletions += value.deletions;
- }
- };
- })(this));
- return parsed_entry;
- },
- in_range: function(date, date_range) {
- var ref;
- if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
- return true;
- } else {
- return false;
+ if (!data[entry.date]) {
+ this.add_date(entry.date, data);
}
+ this.store_data(entry, total[entry.date], data[entry.date]);
+ }
+ total = _.toArray(total);
+ by_author = _.toArray(by_author);
+ return {
+ total: total,
+ by_author: by_author
+ };
+ },
+ add_date: function(date, collection) {
+ collection[date] = {};
+ return collection[date].date = date;
+ },
+ add_author: function(author, by_author, by_email) {
+ var data, normalized_email;
+ data = {};
+ data.author_name = author.author_name;
+ data.author_email = author.author_email;
+ normalized_email = author.author_email.toLowerCase();
+ by_author[author.author_name] = data;
+ by_email[normalized_email] = data;
+ return data;
+ },
+ store_data: function(entry, total, by_author) {
+ this.store_commits(total, by_author);
+ this.store_additions(entry, total, by_author);
+ return this.store_deletions(entry, total, by_author);
+ },
+ store_commits: function(total, by_author) {
+ this.add(total, "commits", 1);
+ return this.add(by_author, "commits", 1);
+ },
+ add: function(collection, field, value) {
+ if (collection[field] == null) {
+ collection[field] = 0;
+ }
+ return collection[field] += value;
+ },
+ store_additions: function(entry, total, by_author) {
+ if (entry.additions == null) {
+ entry.additions = 0;
+ }
+ this.add(total, "additions", entry.additions);
+ return this.add(by_author, "additions", entry.additions);
+ },
+ store_deletions: function(entry, total, by_author) {
+ if (entry.deletions == null) {
+ entry.deletions = 0;
+ }
+ this.add(total, "deletions", entry.deletions);
+ return this.add(by_author, "deletions", entry.deletions);
+ },
+ get_total_data: function(parsed_log, field) {
+ var log, total_data;
+ log = parsed_log.total;
+ total_data = this.pick_field(log, field);
+ return _.sortBy(total_data, function(d) {
+ return d.date;
+ });
+ },
+ pick_field: function(log, field) {
+ var total_data;
+ total_data = [];
+ _.each(log, function(d) {
+ return total_data.push(_.pick(d, [field, 'date']));
+ });
+ return total_data;
+ },
+ get_author_data: function(parsed_log, field, date_range) {
+ var author_data, log;
+ if (date_range == null) {
+ date_range = null;
+ }
+ log = parsed_log.by_author;
+ author_data = [];
+ _.each(log, (function(_this) {
+ return function(log_entry) {
+ var parsed_log_entry;
+ parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
+ if (!_.isEmpty(parsed_log_entry.dates)) {
+ return author_data.push(parsed_log_entry);
+ }
+ };
+ })(this));
+ return _.sortBy(author_data, function(d) {
+ return d[field];
+ }).reverse();
+ },
+ parse_log_entry: function(log_entry, field, date_range) {
+ var parsed_entry;
+ parsed_entry = {};
+ parsed_entry.author_name = log_entry.author_name;
+ parsed_entry.author_email = log_entry.author_email;
+ parsed_entry.dates = {};
+ parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0;
+ _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
+ return function(value, key) {
+ if (_this.in_range(value.date, date_range)) {
+ parsed_entry.dates[value.date] = value[field];
+ parsed_entry.commits += value.commits;
+ parsed_entry.additions += value.additions;
+ return parsed_entry.deletions += value.deletions;
+ }
+ };
+ })(this));
+ return parsed_entry;
+ },
+ in_range: function(date, date_range) {
+ var ref;
+ if (date_range === null || (date_range[0] <= (ref = new Date(date)) && ref <= date_range[1])) {
+ return true;
+ } else {
+ return false;
}
- };
-}).call(window);
+ }
+};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 45a1d90a9d9..dbf40ec7fcf 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -296,5 +296,57 @@
* @returns {Boolean}
*/
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
+
+ /**
+ * Back Off exponential algorithm
+ * backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
+ *
+ * @param {Function<next, stop>} fn function to be called
+ * @param {Number} timeout
+ * @return {Promise<Any, Error>}
+ * @example
+ * ```
+ * backOff(function (next, stop) {
+ * // Let's perform this function repeatedly for 60s or for the timeout provided.
+ *
+ * ourFunction()
+ * .then(function (result) {
+ * // continue if result is not what we need
+ * next();
+ *
+ * // when result is what we need let's stop with the repetions and jump out of the cycle
+ * stop(result);
+ * })
+ * .catch(function (error) {
+ * // if there is an error, we need to stop this with an error.
+ * stop(error);
+ * })
+ * }, 60000)
+ * .then(function (result) {})
+ * .catch(function (error) {
+ * // deal with errors passed to stop()
+ * })
+ * ```
+ */
+ w.gl.utils.backOff = (fn, timeout = 60000) => {
+ let nextInterval = 2000;
+
+ const startTime = (+new Date());
+
+ return new Promise((resolve, reject) => {
+ const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
+
+ const next = () => {
+ if (new Date().getTime() - startTime < timeout) {
+ setTimeout(fn.bind(null, next, stop), nextInterval);
+ nextInterval *= 2;
+ } else {
+ reject(new Error('BACKOFF_TIMEOUT'));
+ }
+ };
+
+ fn(next, stop);
+ });
+ };
})(window);
}).call(window);
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 7fbaeec7882..38c673e8907 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -78,7 +78,6 @@
} else {
$(element).find('.assignee-icon').empty();
}
- return $(element).effect('highlight');
};
function Milestone() {
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 8df1c8e7f94..51fa5c828b3 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -39,7 +39,7 @@
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL) {
- milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
+ milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
}
@@ -181,8 +181,7 @@
$selectbox.hide();
$value.css('display', '');
if (data.milestone != null) {
- data.milestone.namespace = _this.currentProject.namespace;
- data.milestone.path = _this.currentProject.path;
+ data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index cb24f212c66..3f678b93f73 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
@@ -20,15 +20,35 @@
};
NewBranchForm.prototype.init = function() {
- if (this.name.val().length > 0) {
+ if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
}
};
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
- return this.ref.autocomplete({
- source: availableRefs,
- minLength: 1
+ var $branchSelect = $('.js-branch-select');
+
+ $branchSelect.glDropdown({
+ data: availableRefs,
+ filterable: true,
+ filterByText: true,
+ remote: false,
+ fieldName: $branchSelect.data('field-name'),
+ selectable: true,
+ isSelectable: function(branch, $el) {
+ return !$el.hasClass('is-active');
+ },
+ text: function(branch) {
+ return branch;
+ },
+ id: function(branch) {
+ return branch;
+ },
+ toggleLabel: function(branch) {
+ if (branch) {
+ return branch;
+ }
+ }
});
};
diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js
index d7f3c9fd37e..15d32825583 100644
--- a/app/assets/javascripts/profile/profile_bundle.js
+++ b/app/assets/javascripts/profile/profile_bundle.js
@@ -1,3 +1,2 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
+require('./gl_crop');
+require('./profile');
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
index 149e511451e..6ef59e94384 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
@@ -36,6 +36,9 @@
// Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
+ this.$allowedToMergeDropdown.disable();
+ this.$allowedToPushDropdown.disable();
+
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
@@ -53,13 +56,13 @@
}]
}
},
- success: () => {
- this.$wrap.effect('highlight');
- },
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
+ }).always(() => {
+ this.$allowedToMergeDropdown.enable();
+ this.$allowedToPushDropdown.enable();
});
}
};
diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js
index ffb66caf5f4..849c1e31623 100644
--- a/app/assets/javascripts/protected_branches/protected_branches_bundle.js
+++ b/app/assets/javascripts/protected_branches/protected_branches_bundle.js
@@ -1,3 +1,5 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/));
+require('./protected_branch_access_dropdown');
+require('./protected_branch_create');
+require('./protected_branch_dropdown');
+require('./protected_branch_edit');
+require('./protected_branch_edit_list');
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index 89822246bb8..a98403f4cf2 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -1,10 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
-
(function() {
$(function() {
var editor = ace.edit("editor");
diff --git a/app/assets/javascripts/user_callout.js b/app/assets/javascripts/user_callout.js
new file mode 100644
index 00000000000..74b869502a4
--- /dev/null
+++ b/app/assets/javascripts/user_callout.js
@@ -0,0 +1,58 @@
+/* global Cookies */
+
+const userCalloutElementName = '.user-callout';
+const closeButton = '.close-user-callout';
+const userCalloutBtn = '.user-callout-btn';
+const userCalloutSvgAttrName = 'callout-svg';
+
+const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
+
+const USER_CALLOUT_TEMPLATE = `
+ <div class="bordered-box landing content-block">
+ <button class="btn btn-default close close-user-callout" type="button">
+ <i class="fa fa-times dismiss-icon"></i>
+ </button>
+ <div class="row">
+ <div class="col-sm-3 col-xs-12 svg-container">
+ </div>
+ <div class="col-sm-8 col-xs-12 inner-content">
+ <h4>
+ Customize your experience
+ </h4>
+ <p>
+ Change syntax themes, default project pages, and more in preferences.
+ </p>
+ <a class="btn user-callout-btn" href="/profile/preferences">Check it out</a>
+ </div>
+ </div>
+</div>`;
+
+class UserCallout {
+ constructor() {
+ this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
+ this.userCalloutBody = $(userCalloutElementName);
+ this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName);
+ $(userCalloutElementName).removeAttr(userCalloutSvgAttrName);
+ this.init();
+ }
+
+ init() {
+ const $template = $(USER_CALLOUT_TEMPLATE);
+ if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
+ $template.find('.svg-container').append(this.userCalloutSvg);
+ this.userCalloutBody.append($template);
+ $template.find(closeButton).on('click', e => this.dismissCallout(e));
+ $template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
+ }
+ }
+
+ dismissCallout(e) {
+ Cookies.set(USER_CALLOUT_COOKIE, 'true');
+ const $currentTarget = $(e.currentTarget);
+ if ($currentTarget.hasClass('close-user-callout')) {
+ this.userCalloutBody.empty();
+ }
+ }
+}
+
+module.exports = UserCallout;
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
index 4cad60a59b1..580e2d84be5 100644
--- a/app/assets/javascripts/users/users_bundle.js
+++ b/app/assets/javascripts/users/users_bundle.js
@@ -1,3 +1 @@
-// require everything else in this directory
-function requireAll(context) { return context.keys().map(context); }
-requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
+require('./calendar');
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
index 04029c47668..e20085d1fd2 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
@@ -1,5 +1,5 @@
/* global Vue, Flash, gl */
-/* eslint-disable no-param-reassign */
+/* eslint-disable no-param-reassign, no-alert */
((gl) => {
gl.VuePipelineActions = Vue.extend({
@@ -16,6 +16,20 @@
download(name) {
return `Download ${name} artifacts`;
},
+
+ /**
+ * Shows a dialog when the user clicks in the cancel button.
+ * We need to prevent the default behavior and stop propagation because the
+ * link relies on UJS.
+ *
+ * @param {Event} event
+ */
+ confirmAction(event) {
+ if (!confirm('Are you sure you want to cancel this pipeline?')) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
},
template: `
<td class="pipeline-actions hidden-xs">
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
index 0265c00a414..9d66d28cc62 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
@@ -23,7 +23,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s
apiScope: 'all',
pageInfo: {},
pagenum: 1,
- count: { all: 0, running_or_pending: 0 },
+ count: {},
pageRequest: false,
};
},
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
index 8cc417a9966..67fdd729e41 100644
--- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
@@ -23,6 +23,13 @@
required: true,
},
},
+
+ updated() {
+ if (this.builds) {
+ this.stopDropdownClickPropagation();
+ }
+ },
+
methods: {
fetchBuilds(e) {
const areaExpanded = e.currentTarget.attributes['aria-expanded'];
@@ -37,17 +44,19 @@
return flash;
});
},
- keepGraph(e) {
- const { target } = e;
-
- if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
-
- if (
- target.parentElement &&
- (target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
- ) return null;
- return e.stopPropagation();
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
+ e.stopPropagation();
+ });
},
},
computed: {
@@ -76,13 +85,13 @@
template: `
<div>
<button
- @click='fetchBuilds($event)'
+ @click="fetchBuilds($event)"
:class="triggerButtonClass"
- :title='stage.title'
+ :title="stage.title"
data-placement="top"
data-toggle="dropdown"
type="button"
- :aria-label='stage.title'
+ :aria-label="stage.title"
>
<span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
@@ -90,7 +99,6 @@
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div>
<div
- @click='keepGraph($event)'
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner"
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 1dcd1f8a6fc..83a8eeaafde 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
*= require_self
diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss
index d335fedefe2..300ba4f2de6 100644
--- a/app/assets/stylesheets/framework/jquery.scss
+++ b/app/assets/stylesheets/framework/jquery.scss
@@ -2,17 +2,6 @@
font-family: $regular_font;
font-size: $font-size-base;
- &.ui-autocomplete {
- border-color: $jq-ui-border;
- padding: 0;
- margin-top: 2px;
- z-index: 1001;
-
- .ui-menu-item a {
- padding: 4px 10px;
- }
- }
-
.ui-state-default {
border: 1px solid $white-light;
background: $white-light;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 040a7ce0c16..8978e284f55 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -29,16 +29,14 @@
}
}
-@media (min-width: $screen-sm-min) {
- .content-wrapper {
- padding-right: $gutter_collapsed_width;
- }
-}
-
.right-sidebar-collapsed {
padding-right: 0;
@media (min-width: $screen-sm-min) {
+ .content-wrapper {
+ padding-right: $gutter_collapsed_width;
+ }
+
.merge-request-tabs-holder.affix {
right: $gutter_collapsed_width;
}
@@ -56,6 +54,12 @@
.right-sidebar-expanded {
padding-right: 0;
+ @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+ .content-wrapper {
+ padding-right: $gutter_collapsed_width;
+ }
+ }
+
@media (min-width: $screen-md-min) {
.content-wrapper {
padding-right: $gutter_width;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8031c4467a4..aad1a8986b0 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px;
}
}
+
+.user-callout {
+ margin: 24px auto 0;
+
+ .bordered-box {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+ }
+
+ .landing {
+ margin-bottom: $gl-padding;
+
+ .close {
+ margin-right: 20px;
+ }
+
+ .dismiss-icon {
+ float: right;
+ cursor: pointer;
+ color: $cycle-analytics-dismiss-icon-color;
+ }
+
+ .svg-container {
+ text-align: center;
+
+ svg {
+ width: 136px;
+ height: 136px;
+ }
+ }
+ }
+
+ @media(max-width: $screen-xs-max) {
+ .inner-content {
+ padding-left: 30px;
+ }
+ }
+}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b0f5d4a9933..d807e6263ee 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -83,6 +83,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key,
:akismet_enabled,
:container_registry_token_expire_delay,
+ :default_artifacts_expire_in,
:default_branch_protection,
:default_group_visibility,
:default_project_visibility,
diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb
index 1330399a836..99039724521 100644
--- a/app/controllers/admin/system_info_controller.rb
+++ b/app/controllers/admin/system_info_controller.rb
@@ -3,7 +3,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'nobrowse',
'read-only',
'ro'
- ]
+ ].freeze
EXCLUDED_MOUNT_TYPES = [
'autofs',
@@ -27,7 +27,7 @@ class Admin::SystemInfoController < Admin::ApplicationController
'tmpfs',
'tracefs',
'vfat'
- ]
+ ].freeze
def show
@cpus = Vmstat.cpu rescue nil
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5e7af3bff0d..e42e48f87d2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -181,7 +181,7 @@ class ApplicationController < ActionController::Base
end
def gitlab_ldap_access(&block)
- Gitlab::LDAP::Access.open { |access| block.call(access) }
+ Gitlab::LDAP::Access.open { |access| yield(access) }
end
# JSON for infinite scroll via Pager object
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 88d180fcc2e..2fe03020d2d 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -101,13 +101,14 @@ module CreatesCommit
# TODO: We should really clean this up
def set_commit_variables
- if can?(current_user, :push_code, @project)
- # Edit file in this project
- @mr_source_project = @project
- else
- # Merge request from fork to this project
- @mr_source_project = current_user.fork_of(@project)
- end
+ @mr_source_project =
+ if can?(current_user, :push_code, @project)
+ # Edit file in this project
+ @project
+ else
+ # Merge request from fork to this project
+ current_user.fork_of(@project)
+ end
# Merge request to this project
@mr_target_project = @project
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index d7f5a4e4682..e610ccaec96 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -59,10 +59,10 @@ module ServiceParams
:user_key,
:username,
:webhook
- ]
+ ].freeze
# Parameters to ignore if no value is specified
- FILTER_BLANK_PARAMS = [:password]
+ FILTER_BLANK_PARAMS = [:password].freeze
def service_params
dynamic_params = @service.event_channel_names + @service.event_names
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index da225d8f1c7..d0a692070d9 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -27,7 +27,7 @@ module SpammableActions
render :verify
else
- fallback.call
+ yield
end
end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index c2e4d62b50b..3109439b2ff 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -5,7 +5,7 @@ class JwtController < ApplicationController
SERVICES = {
Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService,
- }
+ }.freeze
def auth
service = SERVICES[params[:service]]
@@ -39,7 +39,8 @@ class JwtController < ApplicationController
message: "HTTP Basic: Access denied\n" \
"You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}" }
- ] }, status: 401
+ ]
+ }, status: 401
end
def render_unauthorized
@@ -47,7 +48,8 @@ class JwtController < ApplicationController
errors: [
{ code: 'UNAUTHORIZED',
message: 'HTTP Basic: Access denied' }
- ] }, status: 401
+ ]
+ }, status: 401
end
def auth_params
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 18044ca78e2..26e7e93533e 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -80,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def build_qr_code
uri = current_user.otp_provisioning_uri(account_string, issuer: issuer_host)
- RQRCode::render_qrcode(uri, :svg, level: :m, unit: 3)
+ RQRCode.render_qrcode(uri, :svg, level: :m, unit: 3)
end
def account_string
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 89d84809e3a..a01c0caa959 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,8 +1,9 @@
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include SortingHelper
+
# Authorize
- before_action :require_non_empty_project
+ before_action :require_non_empty_project, except: :create
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
@@ -32,6 +33,8 @@ class Projects::BranchesController < Projects::ApplicationController
branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
+ redirect_to_autodeploy = project.empty_repo? && project.deployment_services.present?
+
result = CreateBranchService.new(project, current_user).
execute(branch_name, ref)
@@ -42,8 +45,15 @@ class Projects::BranchesController < Projects::ApplicationController
if result[:status] == :success
@branch = result[:branch]
- redirect_to namespace_project_tree_path(@project.namespace, @project,
- @branch.name)
+
+ if redirect_to_autodeploy
+ redirect_to(
+ url_to_autodeploy_setup(project, branch_name),
+ notice: view_context.autodeploy_flash_notice(branch_name))
+ else
+ redirect_to namespace_project_tree_path(@project.namespace, @project,
+ @branch.name)
+ end
else
@error = result[:message]
render action: 'new'
@@ -76,7 +86,19 @@ class Projects::BranchesController < Projects::ApplicationController
ref_escaped = sanitize(strip_tags(params[:ref]))
Addressable::URI.unescape(ref_escaped)
else
- @project.default_branch
+ @project.default_branch || 'master'
end
end
+
+ def url_to_autodeploy_setup(project, branch_name)
+ namespace_project_new_blob_path(
+ project.namespace,
+ project,
+ branch_name,
+ file_name: '.gitlab-ci.yml',
+ commit_message: 'Set up auto deploy',
+ target_branch: branch_name,
+ context: 'autodeploy'
+ )
+ end
end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 216c158e41e..9a1bf037a95 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -76,11 +76,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController
return @project if defined?(@project)
project_id, _ = project_id_with_suffix
- if project_id.blank?
- @project = nil
- else
- @project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
- end
+ @project =
+ if project_id.blank?
+ nil
+ else
+ Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
+ end
end
# This method returns two values so that we can parse
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 365c49a20d4..d122c7fdcb2 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -381,14 +381,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_widget_refresh
- if merge_request.merge_when_build_succeeds
- @status = :merge_when_build_succeeds
- else
- # Only MRs that can be merged end in this action
- # MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
- # in last case it does not have any special status. Possible error is handled inside widget js function
- @status = :success
- end
+ @status =
+ if merge_request.merge_when_build_succeeds
+ :merge_when_build_succeeds
+ else
+ # Only MRs that can be merged end in this action
+ # MR can be already picked up for merge / merged already or can be waiting for worker to be picked up
+ # in last case it does not have any special status. Possible error is handled inside widget js function
+ :success
+ end
render 'merge'
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 84451257b98..8657bc4dfdc 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -13,9 +13,15 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page])
.per(30)
- @running_or_pending_count = PipelinesFinder
+ @running_count = PipelinesFinder
.new(project).execute(scope: 'running').count
+ @pending_count = PipelinesFinder
+ .new(project).execute(scope: 'pending').count
+
+ @finished_count = PipelinesFinder
+ .new(project).execute(scope: 'finished').count
+
@pipelines_count = PipelinesFinder
.new(project).execute.count
@@ -29,7 +35,9 @@ class Projects::PipelinesController < Projects::ApplicationController
.represent(@pipelines),
count: {
all: @pipelines_count,
- running_or_pending: @running_or_pending_count
+ running: @running_count,
+ pending: @pending_count,
+ finished: @finished_count,
}
}
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 93a180b9036..7d81c96262f 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -15,11 +15,12 @@ class SessionsController < Devise::SessionsController
def new
set_minimum_password_length
- if Gitlab.config.ldap.enabled
- @ldap_servers = Gitlab::LDAP::Config.servers
- else
- @ldap_servers = []
- end
+ @ldap_servers =
+ if Gitlab.config.ldap.enabled
+ Gitlab::LDAP::Config.servers
+ else
+ []
+ end
super
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 2d26718873f..f3fd3da8b20 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -28,8 +28,9 @@ class SnippetsController < ApplicationController
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
- scope: params[:scope] }).
- page(params[:page])
+ scope: params[:scope]
+ })
+ .page(params[:page])
render 'index'
else
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 206c92fe82a..f49301e2631 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -19,7 +19,7 @@
# iids: integer[]
#
class IssuableFinder
- NONE = '0'
+ NONE = '0'.freeze
attr_accessor :current_user, :params
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 4bd8c83081a..6630c6384f2 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -28,11 +28,12 @@ class NotesFinder
private
def init_collection
- if @params[:target_id]
- @notes = on_target(@params[:target_type], @params[:target_id])
- else
- @notes = notes_of_any_type
- end
+ @notes =
+ if @params[:target_id]
+ on_target(@params[:target_type], @params[:target_id])
+ else
+ notes_of_any_type
+ end
end
def notes_of_any_type
diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb
index 32aea75486d..a9172f6767f 100644
--- a/app/finders/pipelines_finder.rb
+++ b/app/finders/pipelines_finder.rb
@@ -10,7 +10,11 @@ class PipelinesFinder
scoped_pipelines =
case scope
when 'running'
- pipelines.running_or_pending
+ pipelines.running
+ when 'pending'
+ pipelines.pending
+ when 'finished'
+ pipelines.finished
when 'branches'
from_ids(ids_for_ref(branches))
when 'tags'
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index a93a63bdb9b..b7f091f334d 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -13,7 +13,7 @@
#
class TodosFinder
- NONE = '0'
+ NONE = '0'.freeze
attr_accessor :current_user, :params
@@ -99,7 +99,7 @@ class TodosFinder
end
def type?
- type.present? && ['Issue', 'MergeRequest'].include?(type)
+ type.present? && %w(Issue MergeRequest).include?(type)
end
def type
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 6db813d4a02..70419eb4bde 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -69,11 +69,12 @@ module ApplicationHelper
end
def avatar_icon(user_or_email = nil, size = nil, scale = 2)
- if user_or_email.is_a?(User)
- user = user_or_email
- else
- user = User.find_by_any_email(user_or_email.try(:downcase))
- end
+ user =
+ if user_or_email.is_a?(User)
+ user_or_email
+ else
+ User.find_by_any_email(user_or_email.try(:downcase))
+ end
if user
user.avatar_url(size) || default_avatar
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 60485160495..4b025669f69 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -1,28 +1,15 @@
module ApplicationSettingsHelper
- def gravatar_enabled?
- current_application_settings.gravatar_enabled?
- end
-
- def signup_enabled?
- current_application_settings.signup_enabled?
- end
-
- def signin_enabled?
- current_application_settings.signin_enabled?
- end
+ delegate :gravatar_enabled?,
+ :signup_enabled?,
+ :signin_enabled?,
+ :akismet_enabled?,
+ :koding_enabled?,
+ to: :current_application_settings
def user_oauth_applications?
current_application_settings.user_oauth_applications
end
- def askimet_enabled?
- current_application_settings.akismet_enabled?
- end
-
- def koding_enabled?
- current_application_settings.koding_enabled?
- end
-
def allowed_protocols_present?
current_application_settings.enabled_git_access_protocol.present?
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 311a70725ab..7f32c1b5300 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -153,16 +153,17 @@ module BlobHelper
# Because we are opionated we set the cache headers ourselves.
response.cache_control[:public] = @project.public?
- if @ref && @commit && @ref == @commit.id
- # This is a link to a commit by its commit SHA. That means that the blob
- # is immutable. The only reason to invalidate the cache is if the commit
- # was deleted or if the user lost access to the repository.
- response.cache_control[:max_age] = Blob::CACHE_TIME_IMMUTABLE
- else
- # A branch or tag points at this blob. That means that the expected blob
- # value may change over time.
- response.cache_control[:max_age] = Blob::CACHE_TIME
- end
+ response.cache_control[:max_age] =
+ if @ref && @commit && @ref == @commit.id
+ # This is a link to a commit by its commit SHA. That means that the blob
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ Blob::CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this blob. That means that the expected blob
+ # value may change over time.
+ Blob::CACHE_TIME
+ end
response.etag = @blob.id
!stale
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 4c7c16d694c..195094730aa 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -34,7 +34,7 @@ module ButtonHelper
content_tag (append_link ? :a : :span), protocol,
class: klass,
- href: (project.http_url_to_repo if append_link),
+ href: (project.http_url_to_repo(current_user) if append_link),
data: {
html: true,
placement: placement,
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index a6d9e37ac76..f927cfc998f 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -24,7 +24,7 @@ module EmailsHelper
def action_title(url)
return unless url
- ["merge_requests", "issues", "commit"].each do |action|
+ %w(merge_requests issues commit).each do |action|
if url.split("/").include?(action)
return "View #{action.humanize.singularize}"
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 03354c235eb..860a665ae26 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -23,7 +23,7 @@ module IssuablesHelper
def issuable_json_path(issuable)
project = issuable.project
- if issuable.kind_of?(MergeRequest)
+ if issuable.is_a?(MergeRequest)
namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
else
namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
@@ -52,7 +52,7 @@ module IssuablesHelper
field_name: 'issuable_template',
selected: selected_template(issuable),
project_path: ref_project.path,
- namespace_path: ref_project.namespace.path
+ namespace_path: ref_project.namespace.full_path
}
}
@@ -198,7 +198,7 @@ module IssuablesHelper
@counts[issuable_type][state]
end
- IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page]
+ IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
def issuables_state_counter_cache_key(issuable_type, state)
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index 320dd89c9d3..68c09c922a6 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -2,6 +2,7 @@ module JavascriptHelper
def page_specific_javascript_tag(js)
javascript_include_tag asset_path(js)
end
+
def page_specific_javascript_bundle_tag(js)
javascript_include_tag(*webpack_asset_paths(js))
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index dc5ae8edbb2..2e3a15bc1b9 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -33,7 +33,7 @@ module NamespacesHelper
end
def namespace_icon(namespace, size = 40)
- if namespace.kind_of?(Group)
+ if namespace.is_a?(Group)
group_icon(namespace)
else
avatar_icon(namespace.owner.email, size)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index eb98204285d..4befeacc135 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -150,6 +150,15 @@ module ProjectsHelper
).html_safe
end
+ def link_to_autodeploy_doc
+ link_to 'About auto deploy', help_page_path('ci/autodeploy/index'), target: '_blank'
+ end
+
+ def autodeploy_flash_notice(branch_name)
+ "Branch <strong>#{truncate(sanitize(branch_name))}</strong> was created. To set up auto deploy, \
+ choose a GitLab CI Yaml template and commit your changes. #{link_to_autodeploy_doc}".html_safe
+ end
+
private
def repo_children_classes(field)
@@ -232,7 +241,7 @@ module ProjectsHelper
when 'ssh'
project.ssh_url_to_repo
else
- project.http_url_to_repo
+ project.http_url_to_repo(current_user)
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index ff787fb4131..8ad3851fb9a 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -30,7 +30,7 @@ module SortingHelper
}
if current_controller?('admin/projects')
- options.merge!(sort_value_largest_repo => sort_title_largest_repo)
+ options[sort_value_largest_repo] = sort_title_largest_repo
end
options
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 9a748aaaf33..fb95f2b565e 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -37,8 +37,8 @@ module SubmoduleHelper
end
def self_url?(url, namespace, project)
- return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/',
- project, '.git' ].join('')
+ return true if url == [Gitlab.config.gitlab.url, '/', namespace, '/',
+ project, '.git'].join('')
url == gitlab_shell.url_to_repo([namespace, '/', project].join(''))
end
@@ -48,8 +48,8 @@ module SubmoduleHelper
end
def standard_links(host, namespace, project, commit)
- base = [ 'https://', host, '/', namespace, '/', project ].join('')
- [base, [ base, '/tree/', commit ].join('')]
+ base = ['https://', host, '/', namespace, '/', project].join('')
+ [base, [base, '/tree/', commit].join('')]
end
def relative_self_links(url, commit)
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 547f6258909..1a55ee05996 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -99,7 +99,7 @@ module TabHelper
return 'active'
end
- if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
+ if %w(services hooks deploy_keys protected_branches).include? controller.controller_name
"active"
end
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index c52afd6db1c..7f8efb0a4ac 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -150,6 +150,6 @@ module TodosHelper
private
def show_todo_state?(todo)
- (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && ['closed', 'merged'].include?(todo.target.state)
+ (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state)
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index fc93acfe63e..169cedeb796 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -89,13 +89,9 @@ module VisibilityLevelHelper
current_application_settings.restricted_visibility_levels || []
end
- def default_project_visibility
- current_application_settings.default_project_visibility
- end
-
- def default_group_visibility
- current_application_settings.default_group_visibility
- end
+ delegate :default_project_visibility,
+ :default_group_visibility,
+ to: :current_application_settings
def skip_level?(form_model, level)
form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level)
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 21db2fe04a0..22a9f5da646 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,10 +1,11 @@
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
- if failed_count == 1
- @message = "One project failed its last repository check"
- else
- @message = "#{failed_count} projects failed their last repository check"
- end
+ @message =
+ if failed_count == 1
+ "One project failed its last repository check"
+ else
+ "#{failed_count} projects failed their last repository check"
+ end
mail(
to: User.admins.pluck(:email),
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 74b358d8c40..dc36c754438 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -5,7 +5,7 @@ class ApplicationSetting < ActiveRecord::Base
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
- CACHE_KEY = 'application_setting.last'
+ CACHE_KEY = 'application_setting.last'.freeze
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or
\s # any whitespace character
@@ -76,6 +76,12 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :max_artifacts_size,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
+ validates :default_artifacts_expire_in, presence: true, duration: true
+
validates :container_registry_token_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
@@ -168,6 +174,7 @@ class ApplicationSetting < ActiveRecord::Base
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
+ default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
@@ -201,9 +208,9 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
+ terminal_max_session_time: 0,
two_factor_grace_period: 48,
- user_default_external: false,
- terminal_max_session_time: 0
+ user_default_external: false
}
end
@@ -215,6 +222,14 @@ class ApplicationSetting < ActiveRecord::Base
create(defaults)
end
+ def self.human_attribute_name(attr, _options = {})
+ if attr == :default_artifacts_expire_in
+ 'Default artifacts expiration'
+ else
+ super
+ end
+ end
+
def home_page_url_column_exist
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e018f8e7c4e..77aba91f65c 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -22,8 +22,10 @@ module Ci
serialize :options
serialize :yaml_variables, Gitlab::Serializer::Ci::Variables
+ delegate :name, to: :project, prefix: true
+
validates :coverage, numericality: true, allow_blank: true
- validates_presence_of :ref
+ validates :ref, presence: true
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
@@ -233,10 +235,6 @@ module Ci
gl_project_id
end
- def project_name
- project.name
- end
-
def repo_url
auth = "gitlab-ci-token:#{ensure_token!}@"
project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
@@ -257,7 +255,7 @@ module Ci
return unless regex
matches = text.scan(Regexp.new(regex)).last
- matches = matches.last if matches.kind_of?(Array)
+ matches = matches.last if matches.is_a?(Array)
coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present?
@@ -486,7 +484,7 @@ module Ci
def artifacts_expire_in=(value)
self.artifacts_expire_at =
if value
- Time.now + ChronicDuration.parse(value)
+ ChronicDuration.parse(value)&.seconds&.from_now
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index dc4590a9923..80e11a5b58f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -14,9 +14,11 @@ module Ci
has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
- validates_presence_of :sha, unless: :importing?
- validates_presence_of :ref, unless: :importing?
- validates_presence_of :status, unless: :importing?
+ delegate :id, to: :project, prefix: true
+
+ validates :sha, presence: { unless: :importing? }
+ validates :ref, presence: { unless: :importing? }
+ validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing?
@@ -93,8 +95,11 @@ module Ci
.select("max(#{quoted_table_name}.id)")
.group(:ref, :sha)
- relation = ref ? where(ref: ref) : self
- relation.where(id: max_id)
+ if ref
+ where(ref: ref, id: max_id.where(ref: ref))
+ else
+ where(id: max_id)
+ end
end
def self.latest_status(ref = nil)
@@ -150,10 +155,6 @@ module Ci
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
- def project_id
- project.id
- end
-
# For now the only user who participates is the user who triggered
def participants(_current_user = nil)
Array(user)
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 07a086b0aca..4863c34a6a6 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -4,8 +4,8 @@ module Ci
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
LAST_CONTACT_TIME = 1.hour.ago
- AVAILABLE_SCOPES = %w[specific shared active paused online]
- FORM_EDITABLE = %i[description tag_list active run_untagged locked]
+ AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
+ FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
has_many :builds
has_many :runner_projects, dependent: :destroy
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 1f9baeca5b1..234376a7e4c 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -5,6 +5,6 @@ module Ci
belongs_to :runner
belongs_to :project, foreign_key: :gl_project_id
- validates_uniqueness_of :runner_id, scope: :gl_project_id
+ validates :runner_id, uniqueness: { scope: :gl_project_id }
end
end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 62889fe80d8..39a1dd86241 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -7,8 +7,8 @@ module Ci
belongs_to :project, foreign_key: :gl_project_id
has_many :trigger_requests, dependent: :destroy
- validates_presence_of :token
- validates_uniqueness_of :token
+ validates :token, presence: true
+ validates :token, uniqueness: true
before_validation :set_default_values
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 46f06733da1..0a18986ef26 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -22,12 +22,12 @@ class Commit
DIFF_HARD_LIMIT_LINES = 50000
# The SHA can be between 7 and 40 hex characters.
- COMMIT_SHA_PATTERN = '\h{7,40}'
+ COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
class << self
def decorate(commits, project)
commits.map do |commit|
- if commit.kind_of?(Commit)
+ if commit.is_a?(Commit)
commit
else
self.new(commit, project)
@@ -105,7 +105,7 @@ class Commit
end
def diff_line_count
- @diff_line_count ||= Commit::diff_line_count(raw_diffs)
+ @diff_line_count ||= Commit.diff_line_count(raw_diffs)
@diff_line_count
end
@@ -122,11 +122,12 @@ class Commit
def full_title
return @full_title if @full_title
- if safe_message.blank?
- @full_title = no_commit_message
- else
- @full_title = safe_message.split("\n", 2).first
- end
+ @full_title =
+ if safe_message.blank?
+ no_commit_message
+ else
+ safe_message.split("\n", 2).first
+ end
end
# Returns the commits description
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 99a6326309d..fc750a3e5e9 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -10,10 +10,11 @@ class CommitStatus < ActiveRecord::Base
belongs_to :user
delegate :commit, to: :pipeline
+ delegate :sha, :short_sha, to: :pipeline
validates :pipeline, presence: true, unless: :importing?
- validates_presence_of :name
+ validates :name, presence: true
alias_attribute :author, :user
@@ -102,8 +103,6 @@ class CommitStatus < ActiveRecord::Base
end
end
- delegate :sha, :short_sha, to: :pipeline
-
def before_sha
pipeline.before_sha || Gitlab::Git::BLANK_SHA
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index a600f9c14c5..8ea95beed79 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,14 +11,15 @@ module CacheMarkdownField
# Knows about the relationship between markdown and html field names, and
# stores the rendering contexts for the latter
class FieldData
- extend Forwardable
-
def initialize
@data = {}
end
- def_delegators :@data, :[], :[]=
- def_delegator :@data, :keys, :markdown_fields
+ delegate :[], :[]=, to: :@data
+
+ def markdown_fields
+ @data.keys
+ end
def html_field(markdown_field)
"#{markdown_field}_html"
@@ -45,7 +46,7 @@ module CacheMarkdownField
Project
Release
Snippet
- ]
+ ].freeze
def self.caching_classes
CACHING_CLASSES.map(&:constantize)
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index fe0cea8465f..034e9f40ff0 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -13,11 +13,12 @@ module CaseSensitivity
params.each do |key, value|
column = ActiveRecord::Base.connection.quote_table_name(key)
- if cast_lower
- condition = "LOWER(#{column}) = LOWER(:value)"
- else
- condition = "#{column} = :value"
- end
+ condition =
+ if cast_lower
+ "LOWER(#{column}) = LOWER(:value)"
+ else
+ "#{column} = :value"
+ end
criteria = criteria.where(condition, value: value)
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 431c0354969..aea359e70bb 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -1,12 +1,12 @@
module HasStatus
extend ActiveSupport::Concern
- DEFAULT_STATUS = 'created'
- AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
- STARTED_STATUSES = %w[running success failed skipped]
- ACTIVE_STATUSES = %w[pending running]
- COMPLETED_STATUSES = %w[success failed canceled skipped]
- ORDERED_STATUSES = %w[failed pending running canceled success skipped]
+ DEFAULT_STATUS = 'created'.freeze
+ AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped].freeze
+ STARTED_STATUSES = %w[running success failed skipped].freeze
+ ACTIVE_STATUSES = %w[pending running].freeze
+ COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
+ ORDERED_STATUSES = %w[failed pending running canceled success skipped].freeze
class_methods do
def status_sql
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index c9c6bd24d75..37c727b5d9f 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -46,6 +46,17 @@ module Issuable
has_one :metrics
+ delegate :name,
+ :email,
+ to: :author,
+ prefix: true
+
+ delegate :name,
+ :email,
+ to: :assignee,
+ allow_nil: true,
+ prefix: true
+
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
@@ -68,21 +79,10 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
- scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
+ scope :inc_notes_with_associations, -> { includes(notes: [:project, :author, :award_emoji]) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
- delegate :name,
- :email,
- to: :author,
- prefix: true
-
- delegate :name,
- :email,
- to: :assignee,
- allow_nil: true,
- prefix: true
-
attr_mentionable :title, pipeline: :single_line
attr_mentionable :description
@@ -182,7 +182,7 @@ module Issuable
def grouping_columns(sort)
grouping_columns = [arel_table[:id]]
- if ["milestone_due_desc", "milestone_due_asc"].include?(sort)
+ if %w(milestone_due_desc milestone_due_asc).include?(sort)
milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date]
@@ -235,7 +235,7 @@ module Issuable
# DEPRECATED
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
}
- hook_data.merge!(assignee: assignee.hook_attrs) if assignee
+ hook_data[:assignee] = assignee.hook_attrs if assignee
hook_data
end
diff --git a/app/models/concerns/reactive_service.rb b/app/models/concerns/reactive_service.rb
index e1f868a299b..713246039c1 100644
--- a/app/models/concerns/reactive_service.rb
+++ b/app/models/concerns/reactive_service.rb
@@ -5,6 +5,6 @@ module ReactiveService
include ReactiveCaching
# Default cache key: class name + project_id
- self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
end
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 7edb0acd56c..b9a2d812edd 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -46,11 +46,12 @@ module Sortable
where("label_links.target_id = #{target_column}").
reorder(nil)
- if target_type_column
- query = query.where("label_links.target_type = #{target_type_column}")
- else
- query = query.where(label_links: { target_type: target_type })
- end
+ query =
+ if target_type_column
+ query.where("label_links.target_type = #{target_type_column}")
+ else
+ query.where(label_links: { target_type: target_type })
+ end
query = query.where.not(title: excluded_labels) if excluded_labels.present?
diff --git a/app/models/concerns/uniquify.rb b/app/models/concerns/uniquify.rb
new file mode 100644
index 00000000000..a7fe5951b6e
--- /dev/null
+++ b/app/models/concerns/uniquify.rb
@@ -0,0 +1,30 @@
+class Uniquify
+ # Return a version of the given 'base' string that is unique
+ # by appending a counter to it. Uniqueness is determined by
+ # repeated calls to the passed block.
+ #
+ # If `base` is a function/proc, we expect that calling it with a
+ # candidate counter returns a string to test/return.
+ def string(base)
+ @base = base
+ @counter = nil
+
+ increment_counter! while yield(base_string)
+ base_string
+ end
+
+ private
+
+ def base_string
+ if @base.respond_to?(:call)
+ @base.call(@counter)
+ else
+ "#{@base}#{@counter}"
+ end
+ end
+
+ def increment_counter!
+ @counter ||= 0
+ @counter += 1
+ end
+end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 559b3075905..895a91139c9 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -8,7 +8,7 @@ class DiffNote < Note
validates :position, presence: true
validates :diff_line, presence: true
validates :line_code, presence: true, line_code: true
- validates :noteable_type, inclusion: { in: ['Commit', 'MergeRequest'] }
+ validates :noteable_type, inclusion: { in: %w(Commit MergeRequest) }
validates :resolved_by, presence: true, if: :resolved?
validate :positions_complete
validate :verify_supported
diff --git a/app/models/event.rb b/app/models/event.rb
index e5027df3f8a..d7ca8e3c599 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
- where(project_id: projects).recent
+ where(project_id: projects.pluck(:id)).recent
end
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
@@ -47,7 +47,7 @@ class Event < ActiveRecord::Base
def contributions
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
Event::PUSHED,
- ["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED],
+ %w(MergeRequest Issue), [Event::CREATED, Event::CLOSED, Event::MERGED],
"Note", Event::COMMENTED)
end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 26712c19b5a..b973bbcd8da 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -43,7 +43,7 @@ class ExternalIssue
end
def reference_link_text(from_project = nil)
- return "##{id}" if /^\d+$/.match(id)
+ return "##{id}" if id =~ /^\d+$/
id
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d8826b65fcc..de90f19f854 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -15,8 +15,6 @@ class Issue < ActiveRecord::Base
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
- ActsAsTaggableOn.strict_case_match = true
-
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
diff --git a/app/models/label.rb b/app/models/label.rb
index 5b6b9a7a736..f68a8c9cff2 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -11,7 +11,7 @@ class Label < ActiveRecord::Base
cache_markdown_field :description, pipeline: :single_line
- DEFAULT_COLOR = '#428BCA'
+ DEFAULT_COLOR = '#428BCA'.freeze
default_value_for :color, DEFAULT_COLOR
diff --git a/app/models/member.rb b/app/models/member.rb
index d07f270b757..0545bd4eedf 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -10,6 +10,8 @@ class Member < ActiveRecord::Base
belongs_to :user
belongs_to :source, polymorphic: true
+ delegate :name, :username, :email, to: :user, prefix: true
+
validates :user, presence: true, unless: :invite?
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
@@ -73,8 +75,6 @@ class Member < ActiveRecord::Base
after_destroy :post_destroy_hook, unless: :pending?
after_commit :refresh_member_authorized_projects
- delegate :name, :username, :email, to: :user, prefix: true
-
default_value_for :notification_level, NotificationSetting.levels[:global]
class << self
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 204f34f0269..446f9f8f8a7 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,11 +1,11 @@
class GroupMember < Member
- SOURCE_TYPE = 'Namespace'
+ SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id'
# Make sure group member points only to group as it source
default_value_for :source_type, SOURCE_TYPE
- validates_format_of :source_type, with: /\ANamespace\z/
+ validates :source_type, format: { with: /\ANamespace\z/ }
default_scope { where(source_type: SOURCE_TYPE) }
def self.access_level_roles
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 008fff0857c..912820b51ac 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,5 +1,5 @@
class ProjectMember < Member
- SOURCE_TYPE = 'Project'
+ SOURCE_TYPE = 'Project'.freeze
include Gitlab::ShellAdapter
@@ -7,7 +7,7 @@ class ProjectMember < Member
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
- validates_format_of :source_type, with: /\AProject\z/
+ validates :source_type, format: { with: /\AProject\z/ }
validates :access_level, inclusion: { in: Gitlab::Access.values }
default_scope { where(source_type: SOURCE_TYPE) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 204d2b153ad..7eb875f1ef5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -203,7 +203,11 @@ class MergeRequest < ActiveRecord::Base
end
def diff_size
- opts = diff_options || {}
+ # The `#diffs` method ends up at an instance of a class inheriting from
+ # `Gitlab::Diff::FileCollection::Base`, so use those options as defaults
+ # here too, to get the same diff size without performing highlighting.
+ #
+ opts = Gitlab::Diff::FileCollection::Base.default_options.merge(diff_options || {})
raw_diffs(opts).size
end
@@ -527,7 +531,7 @@ class MergeRequest < ActiveRecord::Base
}
if diff_head_commit
- attrs.merge!(last_commit: diff_head_commit.hook_attrs)
+ attrs[:last_commit] = diff_head_commit.hook_attrs
end
attributes.merge!(attrs)
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 70bad2a4396..baee00b8fcd 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -7,7 +7,7 @@ class MergeRequestDiff < ActiveRecord::Base
COMMITS_SAFE_SIZE = 100
# Valid types of serialized diffs allowed by Gitlab::Git::Diff
- VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta]
+ VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta].freeze
belongs_to :merge_request
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index bd0336c984a..3137dd32f93 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -98,14 +98,8 @@ class Namespace < ActiveRecord::Base
# Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank?
- counter = 0
- base = path
- while Namespace.find_by_path_or_name(path)
- counter += 1
- path = "#{base}#{counter}"
- end
-
- path
+ uniquify = Uniquify.new
+ uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end
end
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index b524ca50ee8..0bbc9451ffd 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -188,11 +188,12 @@ module Network
end
# and mark it as reserved
- if parent_time.nil?
- min_time = leaves.first.time
- else
- min_time = parent_time + 1
- end
+ min_time =
+ if parent_time.nil?
+ leaves.first.time
+ else
+ parent_time + 1
+ end
max_time = leaves.last.time
leaves.last.parents(@map).each do |parent|
diff --git a/app/models/note.rb b/app/models/note.rb
index 029fe667a45..d6d5396afa5 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -72,7 +72,7 @@ class Note < ActiveRecord::Base
scope :inc_author, ->{ includes(:author) }
scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) }
- scope :diff_notes, ->{ where(type: ['LegacyDiffNote', 'DiffNote']) }
+ scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
scope :with_associations, -> do
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 58f6214bea7..52577bd52ea 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -35,11 +35,11 @@ class NotificationSetting < ActiveRecord::Base
:merge_merge_request,
:failed_pipeline,
:success_pipeline
- ]
+ ].freeze
EXCLUDED_WATCHER_EVENTS = [
:success_pipeline
- ]
+ ].freeze
store :events, accessors: EMAIL_EVENTS, coder: JSON
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 0b9ebf1ffe2..f2f2fc1e32a 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -2,7 +2,7 @@ class PagesDomain < ActiveRecord::Base
belongs_to :project
validates :domain, hostname: true
- validates_uniqueness_of :domain, case_sensitive: false
+ validates :domain, uniqueness: { case_sensitive: false }
validates :certificate, certificate: true, allow_nil: true, allow_blank: true
validates :key, certificate_key: true, allow_nil: true, allow_blank: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 411299eef63..e06fc30dc8a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -22,7 +22,7 @@ class Project < ActiveRecord::Base
class BoardLimitExceeded < StandardError; end
NUMBER_OF_PERMITTED_BOARDS = 1
- UNKNOWN_IMPORT_URL = 'http://unknown.git'
+ UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
cache_markdown_field :description, pipeline: :description
@@ -70,8 +70,7 @@ class Project < ActiveRecord::Base
after_validation :check_pending_delete
- ActsAsTaggableOn.strict_case_match = true
- acts_as_taggable_on :tags
+ acts_as_taggable
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
@@ -172,9 +171,11 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :project_feature
delegate :name, to: :owner, allow_nil: true, prefix: true
+ delegate :count, to: :forks, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
+ delegate :empty_repo?, to: :repository
# Validations
validates :creator, presence: true, on: :create
@@ -191,8 +192,8 @@ class Project < ActiveRecord::Base
format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message }
validates :namespace, presence: true
- validates_uniqueness_of :name, scope: :namespace_id
- validates_uniqueness_of :path, scope: :namespace_id
+ validates :name, uniqueness: { scope: :namespace_id }
+ validates :path, uniqueness: { scope: :namespace_id }
validates :import_url, addressable_url: true, if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
@@ -358,7 +359,7 @@ class Project < ActiveRecord::Base
end
def reference_pattern
- name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
+ name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
%r{
((?<namespace>#{name_pattern})\/)?
@@ -453,13 +454,14 @@ class Project < ActiveRecord::Base
end
def add_import_job
- if forked?
- job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
- forked_from_project.path_with_namespace,
- self.namespace.full_path)
- else
- job_id = RepositoryImportWorker.perform_async(self.id)
- end
+ job_id =
+ if forked?
+ RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
+ forked_from_project.path_with_namespace,
+ self.namespace.full_path)
+ else
+ RepositoryImportWorker.perform_async(self.id)
+ end
if job_id
Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}"
@@ -837,10 +839,6 @@ class Project < ActiveRecord::Base
false
end
- def empty_repo?
- repository.empty_repo?
- end
-
def repo
repository.raw
end
@@ -849,10 +847,6 @@ class Project < ActiveRecord::Base
gitlab_shell.url_to_repo(path_with_namespace)
end
- def namespace_dir
- namespace.try(:path) || ''
- end
-
def repo_exists?
@repo_exists ||= repository.exists?
rescue
@@ -875,8 +869,14 @@ class Project < ActiveRecord::Base
url_to_repo
end
- def http_url_to_repo
- "#{web_url}.git"
+ def http_url_to_repo(user = nil)
+ url = web_url
+
+ if user
+ url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" }
+ end
+
+ "#{url}.git"
end
# Check if current branch name is marked as protected in the system
@@ -901,8 +901,8 @@ class Project < ActiveRecord::Base
def rename_repo
path_was = previous_changes['path'].first
- old_path_with_namespace = File.join(namespace_dir, path_was)
- new_path_with_namespace = File.join(namespace_dir, path)
+ old_path_with_namespace = File.join(namespace.full_path, path_was)
+ new_path_with_namespace = File.join(namespace.full_path, path)
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
@@ -1028,10 +1028,6 @@ class Project < ActiveRecord::Base
forked? && project == forked_from_project
end
- def forks_count
- forks.count
- end
-
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 03194fc2141..e3ef4919b28 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -18,7 +18,7 @@ class ProjectFeature < ActiveRecord::Base
PRIVATE = 10
ENABLED = 20
- FEATURES = %i(issues merge_requests wiki snippets builds repository)
+ FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze
class << self
def access_level_attribute(feature)
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 0956c4a4ede..5fb95050b83 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -3,7 +3,7 @@ require "addressable/uri"
class BuildkiteService < CiService
include ReactiveService
- ENDPOINT = "https://buildkite.com"
+ ENDPOINT = "https://buildkite.com".freeze
prop_accessor :project_url, :token
boolean_accessor :enable_ssl_verification
diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb
index b96aca47e65..791e5b0cec7 100644
--- a/app/models/project_services/chat_message/issue_message.rb
+++ b/app/models/project_services/chat_message/issue_message.rb
@@ -51,7 +51,8 @@ module ChatMessage
title: issue_title,
title_link: issue_url,
text: format(description),
- color: "#C95823" }]
+ color: "#C95823"
+ }]
end
def project_link
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 1ad9efac196..2717c240f05 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -39,7 +39,7 @@ class DroneCiService < CiService
def commit_status_path(sha, ref)
url = [drone_url,
"gitlab/#{project.full_path}/commits/#{sha}",
- "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
+ "?branch=#{URI.encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
end
@@ -74,7 +74,7 @@ class DroneCiService < CiService
def build_page(sha, ref)
url = [drone_url,
"gitlab/#{project.full_path}/redirect/commits/#{sha}",
- "?branch=#{URI::encode(ref.to_s)}"]
+ "?branch=#{URI.encode(ref.to_s)}"]
URI.join(*url).to_s
end
@@ -114,7 +114,7 @@ class DroneCiService < CiService
end
def merge_request_valid?(data)
- ['opened', 'reopened'].include?(data[:object_attributes][:state]) &&
+ %w(opened reopened).include?(data[:object_attributes][:state]) &&
data[:object_attributes][:merge_status] == 'unchecked'
end
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 72da219df28..c4142c38b2f 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -6,7 +6,7 @@ class HipchatService < Service
a b i strong em br img pre code
table th tr td caption colgroup col thead tbody tfoot
ul ol li dl dt dd
- ]
+ ].freeze
prop_accessor :token, :room, :server, :color, :api_version
boolean_accessor :notify_only_broken_builds, :notify
@@ -36,7 +36,7 @@ class HipchatService < Service
{ type: 'text', name: 'token', placeholder: 'Room token' },
{ type: 'text', name: 'room', placeholder: 'Room name or ID' },
{ type: 'checkbox', name: 'notify' },
- { type: 'select', name: 'color', choices: ['yellow', 'red', 'green', 'purple', 'gray', 'random'] },
+ { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) },
{ type: 'text', name: 'api_version',
placeholder: 'Leave blank for default (v2)' },
{ type: 'text', name: 'server',
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 5d6862d9faa..c62bb4fa120 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -33,7 +33,8 @@ class IrkerService < Service
end
def settings
- { server_host: server_host.present? ? server_host : 'localhost',
+ {
+ server_host: server_host.present? ? server_host : 'localhost',
server_port: server_port.present? ? server_port : 6659
}
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index f2f019c43bb..9819e723fe8 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -3,7 +3,7 @@ class KubernetesService < DeploymentService
include Gitlab::Kubernetes
include ReactiveCaching
- self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
# Namespace defaults to the project path, but can be overridden in case that
# is an invalid or inappropriate name
@@ -62,23 +62,19 @@ class KubernetesService < DeploymentService
{ type: 'text',
name: 'namespace',
title: 'Kubernetes namespace',
- placeholder: 'Kubernetes namespace',
- },
+ placeholder: 'Kubernetes namespace' },
{ type: 'text',
name: 'api_url',
title: 'API URL',
- placeholder: 'Kubernetes API URL, like https://kube.example.com/',
- },
+ placeholder: 'Kubernetes API URL, like https://kube.example.com/' },
{ type: 'text',
name: 'token',
title: 'Service token',
- placeholder: 'Service token',
- },
+ placeholder: 'Service token' },
{ type: 'textarea',
name: 'ca_pem',
title: 'Custom CA bundle',
- placeholder: 'Certificate Authority bundle (PEM format)',
- },
+ placeholder: 'Certificate Authority bundle (PEM format)' },
]
end
@@ -167,7 +163,7 @@ class KubernetesService < DeploymentService
url = URI.parse(api_url)
prefix = url.path.sub(%r{/+\z}, '')
- url.path = [ prefix, *parts ].join("/")
+ url.path = [prefix, *parts].join("/")
url.to_s
end
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 4ebc5318da1..c13538e9fea 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -15,10 +15,10 @@ class MattermostService < ChatNotificationService
'This service sends notifications about projects events to Mattermost channels.<br />
To set up this service:
<ol>
- <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation. </li>
- <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event. </li>
- <li>Paste the webhook <strong>URL</strong> into the field bellow. </li>
- <li>Select events below to enable notifications. The channel and username are optional. </li>
+ <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation.</li>
+ <li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event.</li>
+ <li>Paste the webhook <strong>URL</strong> into the field below.</li>
+ <li>Select events below to enable notifications. The <strong>Channel handle</strong> and <strong>Username</strong> fields are optional.</li>
</ol>'
end
@@ -28,14 +28,14 @@ class MattermostService < ChatNotificationService
def default_fields
[
- { type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' },
- { type: 'text', name: 'username', placeholder: 'username' },
+ { type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel_placeholder
- "town-square"
+ "Channel handle (e.g. town-square)"
end
end
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 9cc642591f4..d86f4f6f448 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,7 +1,7 @@
class PivotaltrackerService < Service
include HTTParty
- API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
+ API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index a963d27a376..3e618a8dbf1 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -29,25 +29,24 @@ class PushoverService < Service
['Normal Priority', 0],
['High Priority', 1]
],
- default_choice: 0
- },
+ default_choice: 0 },
{ type: 'select', name: 'sound', choices:
[
['Device default sound', nil],
['Pushover (default)', 'pushover'],
- ['Bike', 'bike'],
- ['Bugle', 'bugle'],
+ %w(Bike bike),
+ %w(Bugle bugle),
['Cash Register', 'cashregister'],
- ['Classical', 'classical'],
- ['Cosmic', 'cosmic'],
- ['Falling', 'falling'],
- ['Gamelan', 'gamelan'],
- ['Incoming', 'incoming'],
- ['Intermission', 'intermission'],
- ['Magic', 'magic'],
- ['Mechanical', 'mechanical'],
+ %w(Classical classical),
+ %w(Cosmic cosmic),
+ %w(Falling falling),
+ %w(Gamelan gamelan),
+ %w(Incoming incoming),
+ %w(Intermission intermission),
+ %w(Magic magic),
+ %w(Mechanical mechanical),
['Piano Bar', 'pianobar'],
- ['Siren', 'siren'],
+ %w(Siren siren),
['Space Alarm', 'spacealarm'],
['Tug Boat', 'tugboat'],
['Alien Alarm (long)', 'alien'],
@@ -56,8 +55,7 @@ class PushoverService < Service
['Pushover Echo (long)', 'echo'],
['Up Down (long)', 'updown'],
['None (silent)', 'none']
- ]
- },
+ ] },
]
end
@@ -72,13 +70,14 @@ class PushoverService < Service
before = data[:before]
after = data[:after]
- if Gitlab::Git.blank_ref?(before)
- message = "#{data[:user_name]} pushed new branch \"#{ref}\"."
- elsif Gitlab::Git.blank_ref?(after)
- message = "#{data[:user_name]} deleted branch \"#{ref}\"."
- else
- message = "#{data[:user_name]} push to branch \"#{ref}\"."
- end
+ message =
+ if Gitlab::Git.blank_ref?(before)
+ "#{data[:user_name]} pushed new branch \"#{ref}\"."
+ elsif Gitlab::Git.blank_ref?(after)
+ "#{data[:user_name]} deleted branch \"#{ref}\"."
+ else
+ "#{data[:user_name]} push to branch \"#{ref}\"."
+ end
if data[:total_commits_count] > 0
message << "\nTotal commits count: #{data[:total_commits_count]}"
@@ -97,7 +96,7 @@ class PushoverService < Service
# Sound parameter MUST NOT be sent to API if not selected
if sound
- pushover_data.merge!(sound: sound)
+ pushover_data[:sound] = sound
end
PushoverService.post('/messages.json', body: pushover_data)
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index f77d2d7c60b..da7496573ef 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -13,11 +13,11 @@ class SlackService < ChatNotificationService
def help
'This service sends notifications about projects events to Slack channels.<br />
- To setup this service:
+ To set up this service:
<ol>
- <li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event. </li>
- <li>Paste the <strong>Webhook URL</strong> into the field below. </li>
- <li>Select events below to enable notifications. The channel and username are optional. </li>
+ <li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event.</li>
+ <li>Paste the <strong>Webhook URL</strong> into the field below.</li>
+ <li>Select events below to enable notifications. The <strong>Channel name</strong> and <strong>Username</strong> fields are optional.</li>
</ol>'
end
@@ -27,14 +27,14 @@ class SlackService < ChatNotificationService
def default_fields
[
- { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
- { type: 'text', name: 'username', placeholder: 'username' },
+ { type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel_placeholder
- "#general"
+ "Channel name (e.g. general)"
end
end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 06abd406523..aeaf63abab9 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -4,7 +4,7 @@ class ProjectStatistics < ActiveRecord::Base
before_save :update_storage_size
- STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size]
+ STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size].freeze
STATISTICS_COLUMNS = [:commit_count] + STORAGE_COLUMNS
def total_repository_size
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index d0b991db112..9891f5edf41 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -5,7 +5,7 @@ class ProjectWiki
'Markdown' => :markdown,
'RDoc' => :rdoc,
'AsciiDoc' => :asciidoc
- } unless defined?(MARKUPS)
+ }.freeze unless defined?(MARKUPS)
class CouldNotCreateWikiError < StandardError; end
@@ -19,6 +19,9 @@ class ProjectWiki
@user = user
end
+ delegate :empty?, to: :pages
+ delegate :repository_storage_path, to: :project
+
def path
@project.path + '.wiki'
end
@@ -60,10 +63,6 @@ class ProjectWiki
!!repository.exists?
end
- def empty?
- pages.empty?
- end
-
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages
@@ -160,10 +159,6 @@ class ProjectWiki
}
end
- def repository_storage_path
- project.repository_storage_path
- end
-
private
def init_repo(path_with_namespace)
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 6240912a6e1..39e979ef15b 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -8,8 +8,8 @@ class ProtectedBranch < ActiveRecord::Base
has_many :merge_access_levels, dependent: :destroy
has_many :push_access_levels, dependent: :destroy
- validates_length_of :merge_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
- validates_length_of :push_access_levels, is: 1, message: "are restricted to a single instance per protected branch."
+ validates :merge_access_levels, length: { is: 1, message: "are restricted to a single instance per protected branch." }
+ validates :push_access_levels, length: { is: 1, message: "are restricted to a single instance per protected branch." }
accepts_nested_attributes_for :push_access_levels
accepts_nested_attributes_for :merge_access_levels
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 56c582cd9be..0dbf246c3a4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -18,7 +18,7 @@ class Repository
CACHED_METHODS = %i(size commit_count readme version contribution_guide
changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count
- tag_count avatar exists? empty? root_ref)
+ tag_count avatar exists? empty? root_ref).freeze
# Certain method caches should be refreshed when certain types of files are
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
@@ -33,7 +33,7 @@ class Repository
koding: :koding_yml,
gitlab_ci: :gitlab_ci_yml,
avatar: :avatar
- }
+ }.freeze
# Wraps around the given method and caches its output in Redis and an instance
# variable.
@@ -109,9 +109,7 @@ class Repository
offset: offset,
after: after,
before: before,
- # --follow doesn't play well with --skip. See:
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
- follow: false,
+ follow: path.present?,
skip_merges: skip_merges
}
@@ -487,9 +485,7 @@ class Repository
end
cache_method :exists?
- def empty?
- raw_repository.empty?
- end
+ delegate :empty?, to: :raw_repository
cache_method :empty?
# The size of this repository in megabytes.
@@ -508,9 +504,7 @@ class Repository
end
cache_method :branch_names, fallback: []
- def tag_names
- raw_repository.tag_names
- end
+ delegate :tag_names, to: :raw_repository
cache_method :tag_names, fallback: []
def branch_count
@@ -750,136 +744,63 @@ class Repository
@tags ||= raw_repository.tags
end
- # rubocop:disable Metrics/ParameterLists
- def commit_dir(
- user, path,
- message:, branch_name:,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- check_tree_entry_for_dir(branch_name, path)
-
- if start_branch_name
- start_project.repository.
- check_tree_entry_for_dir(start_branch_name, path)
- end
+ def create_dir(user, path, **options)
+ options[:user] = user
+ options[:actions] = [{ action: :create_dir, file_path: path }]
- commit_file(
- user,
- "#{path}/.gitkeep",
- '',
- message: message,
- branch_name: branch_name,
- update: false,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project)
+ multi_action(**options)
end
- # rubocop:enable Metrics/ParameterLists
- # rubocop:disable Metrics/ParameterLists
- def commit_file(
- user, path, content,
- message:, branch_name:, update: true,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- unless update
- error_message = "Filename already exists; update not allowed"
+ def create_file(user, path, content, **options)
+ options[:user] = user
+ options[:actions] = [{ action: :create, file_path: path, content: content }]
- if tree_entry_at(branch_name, path)
- raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
- end
+ multi_action(**options)
+ end
- if start_branch_name &&
- start_project.repository.tree_entry_at(start_branch_name, path)
- raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
- end
- end
+ def update_file(user, path, content, **options)
+ previous_path = options.delete(:previous_path)
+ action = previous_path && previous_path != path ? :move : :update
- multi_action(
- user: user,
- message: message,
- branch_name: branch_name,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project,
- actions: [{ action: :create,
- file_path: path,
- content: content }])
- end
- # rubocop:enable Metrics/ParameterLists
+ options[:user] = user
+ options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
- # rubocop:disable Metrics/ParameterLists
- def update_file(
- user, path, content,
- message:, branch_name:, previous_path:,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- action = if previous_path && previous_path != path
- :move
- else
- :update
- end
-
- multi_action(
- user: user,
- message: message,
- branch_name: branch_name,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project,
- actions: [{ action: action,
- file_path: path,
- content: content,
- previous_path: previous_path }])
+ multi_action(**options)
end
- # rubocop:enable Metrics/ParameterLists
- # rubocop:disable Metrics/ParameterLists
- def remove_file(
- user, path,
- message:, branch_name:,
- author_email: nil, author_name: nil,
- start_branch_name: nil, start_project: project)
- multi_action(
- user: user,
- message: message,
- branch_name: branch_name,
- author_email: author_email,
- author_name: author_name,
- start_branch_name: start_branch_name,
- start_project: start_project,
- actions: [{ action: :delete,
- file_path: path }])
+ def delete_file(user, path, **options)
+ options[:user] = user
+ options[:actions] = [{ action: :delete, file_path: path }]
+
+ multi_action(**options)
end
- # rubocop:enable Metrics/ParameterLists
# rubocop:disable Metrics/ParameterLists
def multi_action(
user:, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
start_branch_name: nil, start_project: project)
+
GitOperationService.new(user, self).with_branch(
branch_name,
start_branch_name: start_branch_name,
start_project: start_project) do |start_commit|
- index = rugged.index
- parents = if start_commit
- index.read_tree(start_commit.raw_commit.tree)
- [start_commit.sha]
- else
- []
- end
+ index = Gitlab::Git::Index.new(raw_repository)
- actions.each do |act|
- git_action(index, act)
+ if start_commit
+ index.read_tree(start_commit.raw_commit.tree)
+ parents = [start_commit.sha]
+ else
+ parents = []
+ end
+
+ actions.each do |options|
+ index.public_send(options.delete(:action), options)
end
options = {
- tree: index.write_tree(rugged),
+ tree: index.write_tree,
message: message,
parents: parents
}
@@ -892,7 +813,7 @@ class Repository
def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
- author = Gitlab::Git::committer_hash(email: email, name: name) || committer
+ author = Gitlab::Git.committer_hash(email: email, name: name) || committer
{
author: author,
@@ -1170,30 +1091,6 @@ class Repository
blob_data_at(sha, '.gitlab-ci.yml')
end
- protected
-
- def tree_entry_at(branch_name, path)
- branch_exists?(branch_name) &&
- # tree_entry is private
- raw_repository.send(:tree_entry, commit(branch_name), path)
- end
-
- def check_tree_entry_for_dir(branch_name, path)
- return unless branch_exists?(branch_name)
-
- entry = tree_entry_at(branch_name, path)
-
- return unless entry
-
- if entry[:type] == :blob
- raise Gitlab::Git::Repository::InvalidBlobName.new(
- "Directory already exists as a file")
- else
- raise Gitlab::Git::Repository::InvalidBlobName.new(
- "Directory already exists")
- end
- end
-
private
def blob_data_at(sha, path)
@@ -1204,58 +1101,6 @@ class Repository
blob.data
end
- def git_action(index, action)
- path = normalize_path(action[:file_path])
-
- if action[:action] == :move
- previous_path = normalize_path(action[:previous_path])
- end
-
- case action[:action]
- when :create, :update, :move
- mode =
- case action[:action]
- when :update
- index.get(path)[:mode]
- when :move
- index.get(previous_path)[:mode]
- end
- mode ||= 0o100644
-
- index.remove(previous_path) if action[:action] == :move
-
- content = if action[:encoding] == 'base64'
- Base64.decode64(action[:content])
- else
- action[:content]
- end
-
- detect = CharlockHolmes::EncodingDetector.new.detect(content) if content
-
- unless detect && detect[:type] == :binary
- # When writing to the repo directly as we are doing here,
- # the `core.autocrlf` config isn't taken into account.
- content.gsub!("\r\n", "\n") if self.autocrlf
- end
-
- oid = rugged.write(content, :blob)
-
- index.add(path: path, oid: oid, mode: mode)
- when :delete
- index.remove(path)
- end
- end
-
- def normalize_path(path)
- pathname = Gitlab::Git::PathHelper.normalize_path(path)
-
- if pathname.each_filename.include?('..')
- raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
- end
-
- pathname.to_s
- end
-
def refs_directory_exists?
return false unless path_with_namespace
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 3dda7948d0b..47789a21133 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -17,7 +17,7 @@ class Todo < ActiveRecord::Base
APPROVAL_REQUIRED => :approval_required,
UNMERGEABLE => :unmergeable,
DIRECTLY_ADDRESSED => :directly_addressed
- }
+ }.freeze
belongs_to :author, class_name: "User"
belongs_to :note
diff --git a/app/models/user.rb b/app/models/user.rb
index f614eb66e1f..40264401b53 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -81,7 +81,6 @@ class User < ActiveRecord::Base
has_many :authorized_projects, through: :project_authorizations, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id
- has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id
@@ -99,12 +98,17 @@ class User < ActiveRecord::Base
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
+ # Issues that a user owns are expected to be moved to the "ghost" user before
+ # the user is destroyed. If the user owns any issues during deletion, this
+ # should be treated as an exceptional condition.
+ has_many :issues, dependent: :restrict_with_exception, foreign_key: :author_id
+
#
# Validations
#
# Note: devise :validatable above adds validations for :email and :password
validates :name, presence: true
- validates_confirmation_of :email
+ validates :email, confirmation: true
validates :notification_email, presence: true
validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
@@ -120,6 +124,7 @@ class User < ActiveRecord::Base
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
+ validate :ghost_users_must_be_blocked
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
@@ -334,9 +339,15 @@ class User < ActiveRecord::Base
def reference_pattern
%r{
#{Regexp.escape(reference_prefix)}
- (?<user>#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR})
+ (?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR})
}x
end
+
+ # 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
+ end
end
#
@@ -435,6 +446,12 @@ class User < ActiveRecord::Base
errors.add(:public_email, "is not an email you own") unless all_emails.include?(public_email)
end
+ def ghost_users_must_be_blocked
+ if ghost? && !blocked?
+ errors.add(:ghost, 'cannot be enabled for a user who is not blocked.')
+ end
+ end
+
def update_emails_with_primary_email
primary_email_record = emails.find_by(email: email)
if primary_email_record
@@ -580,8 +597,8 @@ class User < ActiveRecord::Base
if project.repository.branch_exists?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
- where(source_project_id: project.id,
- source_branch: event.branch_name)
+ where(source_project_id: project.id,
+ source_branch: event.branch_name)
merge_requests.empty?
end
end
@@ -999,4 +1016,40 @@ class User < ActiveRecord::Base
super
end
end
+
+ def self.create_ghost_user
+ # Since we only want a single ghost user in an instance, we use an
+ # exclusive lease to ensure than this block is never run concurrently.
+ lease_key = "ghost_user_creation"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)
+
+ until uuid = lease.try_obtain
+ # Keep trying until we obtain the lease. To prevent hammering Redis too
+ # much we'll wait for a bit between retries.
+ sleep(1)
+ end
+
+ # Recheck if a ghost 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?
+
+ uniquify = Uniquify.new
+
+ username = uniquify.string("ghost") { |s| User.find_by_username(s) }
+
+ email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) 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
+ )
+ ensure
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ end
end
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index b9f1c29c32e..e07b144355a 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -6,9 +6,7 @@ class BasePolicy
@cannot_set = cannot_set
end
- def size
- to_set.size
- end
+ delegate :size, to: :to_set
def self.empty
new(Set.new, Set.new)
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 03a2499e263..229846e368c 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -3,6 +3,14 @@ class UserPolicy < BasePolicy
def rules
can! :read_user if @user || !restricted_public_level?
+
+ if @user
+ if @user.admin? || @subject == @user
+ can! :destroy_user
+ end
+
+ cannot! :destroy_user if @subject.ghost?
+ end
end
def restricted_public_level?
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 5cb7a86a5ee..db82b8f6c30 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -2,7 +2,7 @@ module Auth
class ContainerRegistryAuthenticationService < BaseService
include Gitlab::CurrentSettings
- AUDIENCE = 'container_registry'
+ AUDIENCE = 'container_registry'.freeze
def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index fa45506317e..745c2c4b681 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -28,9 +28,7 @@ class BaseService
SystemHooksService.new
end
- def repository
- project.repository
- end
+ delegate :repository, to: :project
# Add an error to the specified model for restricted visibility levels
def deny_visibility_level(model, denied_visibility_level = nil)
diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb
index b7da3f8e7eb..70fb2c5e38f 100644
--- a/app/services/ci/create_pipeline_builds_service.rb
+++ b/app/services/ci/create_pipeline_builds_service.rb
@@ -10,9 +10,7 @@ module Ci
end
end
- def project
- pipeline.project
- end
+ delegate :project, to: :pipeline
private
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 4b47ee489cf..38ef323f6e5 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -1,16 +1,17 @@
module Ci
class RetryBuildService < ::BaseService
- CLONE_ATTRIBUTES = %i[pipeline ref tag options commands tag_list name
+ CLONE_ATTRIBUTES = %i[pipeline project ref tag options commands name
allow_failure stage stage_idx trigger_request
yaml_variables when environment coverage_regex]
.freeze
REJECT_ATTRIBUTES = %i[id status user token coverage trace runner
- artifacts_file artifacts_metadata artifacts_size
+ artifacts_expire_at artifacts_file
+ artifacts_metadata artifacts_size
created_at updated_at started_at finished_at
queued_at erased_by erased_at].freeze
- IGNORE_ATTRIBUTES = %i[trace type lock_version project target_url
+ IGNORE_ATTRIBUTES = %i[type lock_version gl_project_id target_url
deploy job_id description].freeze
def execute(build)
diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb
index 2c5e130e5aa..574561adc4c 100644
--- a/app/services/ci/retry_pipeline_service.rb
+++ b/app/services/ci/retry_pipeline_service.rb
@@ -1,5 +1,7 @@
module Ci
class RetryPipelineService < ::BaseService
+ include Gitlab::OptimisticLocking
+
def execute(pipeline)
unless can?(current_user, :update_pipeline, pipeline)
raise Gitlab::Access::AccessDeniedError
@@ -12,6 +14,10 @@ module Ci
.reprocess(build)
end
+ pipeline.builds.skipped.find_each do |skipped|
+ retry_optimistic_lock(skipped) { |build| build.process }
+ end
+
MergeRequests::AddTodoWhenBuildFailsService
.new(project, current_user)
.close_all(pipeline)
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 77459d8779d..b07338d500a 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,5 +1,7 @@
class CreateBranchService < BaseService
def execute(branch_name, ref)
+ create_master_branch if project.empty_repo?
+
result = ValidateNewBranchService.new(project, current_user)
.execute(branch_name)
@@ -19,4 +21,16 @@ class CreateBranchService < BaseService
def success(branch)
super().merge(branch: branch)
end
+
+ private
+
+ def create_master_branch
+ project.repository.commit_file(
+ current_user,
+ '/README.md',
+ '',
+ message: 'Add README.md',
+ branch_name: 'master',
+ update: false)
+ end
end
diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
index 858de5f0538..083ffdc634c 100644
--- a/app/services/files/create_dir_service.rb
+++ b/app/services/files/create_dir_service.rb
@@ -1,7 +1,7 @@
module Files
class CreateDirService < Files::BaseService
def commit
- repository.commit_dir(
+ repository.create_dir(
current_user,
@file_path,
message: @commit_message,
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 88dd7bbaedb..65b5537fb68 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,13 +1,12 @@
module Files
class CreateService < Files::BaseService
def commit
- repository.commit_file(
+ repository.create_file(
current_user,
@file_path,
@file_content,
message: @commit_message,
branch_name: @target_branch,
- update: false,
author_email: @author_email,
author_name: @author_name,
start_project: @start_project,
@@ -17,6 +16,10 @@ module Files
def validate
super
+ if @file_content.nil?
+ raise_error("You must provide content.")
+ end
+
if @file_path =~ Gitlab::Regex.directory_traversal_regex
raise_error(
'Your changes could not be committed, because the file name ' +
diff --git a/app/services/files/destroy_service.rb b/app/services/files/destroy_service.rb
index c3be806a42d..e294659bc98 100644
--- a/app/services/files/destroy_service.rb
+++ b/app/services/files/destroy_service.rb
@@ -1,7 +1,7 @@
module Files
class DestroyService < Files::BaseService
def commit
- repository.remove_file(
+ repository.delete_file(
current_user,
@file_path,
message: @commit_message,
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index af6da5b9d56..0609c6219e7 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -2,6 +2,8 @@ module Files
class MultiService < Files::BaseService
class FileChangedError < StandardError; end
+ ACTIONS = %w[create update delete move].freeze
+
def commit
repository.multi_action(
user: current_user,
@@ -21,10 +23,19 @@ module Files
super
params[:actions].each_with_index do |action, index|
+ if ACTIONS.include?(action[:action].to_s)
+ action[:action] = action[:action].to_sym
+ else
+ raise_error("Unknown action type `#{action[:action]}`.")
+ end
+
unless action[:file_path].present?
raise_error("You must specify a file_path.")
end
+ action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
+ action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')
+
regex_check(action[:file_path])
regex_check(action[:previous_path]) if action[:previous_path]
@@ -43,8 +54,6 @@ module Files
validate_delete(action)
when :move
validate_move(action, index)
- else
- raise_error("Unknown action type `#{action[:action]}`.")
end
end
end
@@ -92,6 +101,20 @@ module Files
if repository.blob_at_branch(params[:branch], action[:file_path])
raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.")
end
+
+ if action[:content].nil?
+ raise_error("You must provide content.")
+ end
+ end
+
+ def validate_update(action)
+ if action[:content].nil?
+ raise_error("You must provide content.")
+ end
+
+ if file_has_changed?
+ raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
+ end
end
def validate_delete(action)
@@ -114,11 +137,5 @@ module Files
params[:actions][index][:content] = blob.data
end
end
-
- def validate_update(action)
- if file_has_changed?
- raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
- end
- end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index a71fe61a4b6..54e1aaf3f67 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -18,6 +18,10 @@ module Files
def validate
super
+ if @file_content.nil?
+ raise_error("You must provide content.")
+ end
+
if file_has_changed?
raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.")
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 431da8372c9..2e089149ca8 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -4,7 +4,7 @@ module Members
attr_accessor :source
- ALLOWED_SCOPES = %i[members requesters all]
+ ALLOWED_SCOPES = %i[members requesters all].freeze
def initialize(source, current_user, params = {})
@source = source
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb
index 56913568cae..ad1e6f6774a 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/slash_commands_service.rb
@@ -3,7 +3,7 @@ module Notes
UPDATE_SERVICES = {
'Issue' => Issues::UpdateService,
'MergeRequest' => MergeRequests::UpdateService
- }
+ }.freeze
def self.noteable_update_service(note)
UPDATE_SERVICES[note.noteable_type]
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 9716a1780a9..2e06826c311 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -4,7 +4,7 @@ module Projects
class DestroyError < StandardError; end
- DELETED_FLAG = '+deleted'
+ DELETED_FLAG = '+deleted'.freeze
def async_execute
project.transaction do
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
index f06a3d44c17..604747e39d0 100644
--- a/app/services/projects/download_service.rb
+++ b/app/services/projects/download_service.rb
@@ -2,7 +2,7 @@ module Projects
class DownloadService < BaseService
WHITELIST = [
/^[^.]+\.fogbugz.com$/
- ]
+ ].freeze
def initialize(project, url)
@project, @url = project, url
@@ -25,7 +25,7 @@ module Projects
end
def http?(url)
- url =~ /\A#{URI::regexp(['http', 'https'])}\z/
+ url =~ /\A#{URI.regexp(%w(http https))}\z/
end
def valid_domain?(url)
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index f5f9ee88912..2d42c4fc04a 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -2,7 +2,7 @@ module Projects
class UpdatePagesService < BaseService
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
- SITE_PATH = 'public/'
+ SITE_PATH = 'public/'.freeze
attr_reader :build
diff --git a/app/services/protected_branches/api_update_service.rb b/app/services/protected_branches/api_update_service.rb
index 050cb3b738b..bdb0e0cc8bf 100644
--- a/app/services/protected_branches/api_update_service.rb
+++ b/app/services/protected_branches/api_update_service.rb
@@ -15,16 +15,16 @@ module ProtectedBranches
case @developers_can_push
when true
- params.merge!(push_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }])
+ params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }]
when false
- params.merge!(push_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }])
+ params[:push_access_levels_attributes] = [{ access_level: Gitlab::Access::MASTER }]
end
case @developers_can_merge
when true
- params.merge!(merge_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }])
+ params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::DEVELOPER }]
when false
- params.merge!(merge_access_levels_attributes: [{ access_level: Gitlab::Access::MASTER }])
+ params[:merge_access_levels_attributes] = [{ access_level: Gitlab::Access::MASTER }]
end
service = ProtectedBranches::UpdateService.new(@project, @current_user, @params)
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index a2bfa422c9d..9b6dd013e3a 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -33,9 +33,7 @@ class SystemHooksService
data.merge!(project_data(model))
if event == :rename || event == :transfer
- data.merge!({
- old_path_with_namespace: model.old_path_with_namespace
- })
+ data[:old_path_with_namespace] = model.old_path_with_namespace
end
data
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 87ba72cf991..55b548a12f9 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -356,10 +356,10 @@ module SystemNoteService
note: cross_reference_note_content(gfm_reference)
}
- if noteable.kind_of?(Commit)
+ if noteable.is_a?(Commit)
note_options.merge!(noteable_type: 'Commit', commit_id: noteable.id)
else
- note_options.merge!(noteable: noteable)
+ note_options[:noteable] = noteable
end
if noteable.is_a?(ExternalIssue)
@@ -408,12 +408,13 @@ module SystemNoteService
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
- if noteable.is_a?(Commit)
- # Commits have non-integer IDs, so they're stored in `commit_id`
- notes = notes.where(commit_id: noteable.id)
- else
- notes = notes.where(noteable_id: noteable.id)
- end
+ notes =
+ if noteable.is_a?(Commit)
+ # Commits have non-integer IDs, so they're stored in `commit_id`
+ notes.where(commit_id: noteable.id)
+ else
+ notes.where(noteable_id: noteable.id)
+ end
notes_for_mentioner(mentioner, noteable, notes).exists?
end
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index bc0653cb634..833da5bc5d1 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -7,7 +7,7 @@ module Users
end
def execute(user, options = {})
- unless current_user.admin? || current_user == user
+ unless Ability.allowed?(current_user, :destroy_user, user)
raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!"
end
@@ -26,6 +26,8 @@ module Users
::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
end
+ move_issues_to_ghost_user(user)
+
# Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
namespace = user.namespace
user_data = user.destroy
@@ -33,5 +35,22 @@ module Users
user_data
end
+
+ private
+
+ def move_issues_to_ghost_user(user)
+ # Block the user before moving issues to prevent a data race.
+ # If the user creates an issue after `move_issues_to_ghost_user`
+ # runs and before the user is destroyed, the destroy will fail with
+ # an exception. We block the user so that issues can't be created
+ # after `move_issues_to_ghost_user` runs and before the destroy happens.
+ user.block
+
+ ghost_user = User.ghost
+
+ user.issues.update_all(author_id: ghost_user.id)
+
+ user.reload
+ end
end
end
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index 35fd1ed23f8..bee311583ea 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -1,15 +1,15 @@
# Extra methods for uploader
module UploaderHelper
- IMAGE_EXT = %w[png jpg jpeg gif bmp tiff]
+ IMAGE_EXT = %w[png jpg jpeg gif bmp tiff].freeze
# We recommend using the .mp4 format over .mov. Videos in .mov format can
# still be used but you really need to make sure they are served with the
# proper MIME type video/mp4 and not video/quicktime or your videos won't play
# on IE >= 9.
# http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
- VIDEO_EXT = %w[mp4 m4v mov webm ogv]
+ VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
# These extension types can contain dangerous code and should only be embedded inline with
# proper filtering. They should always be tagged as "Content-Disposition: attachment", not "inline".
- DANGEROUS_EXT = %w[svg]
+ DANGEROUS_EXT = %w[svg].freeze
def image?
extension_match?(IMAGE_EXT)
diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
index 09bfa613cbe..94542125d43 100644
--- a/app/validators/addressable_url_validator.rb
+++ b/app/validators/addressable_url_validator.rb
@@ -18,7 +18,7 @@
# end
#
class AddressableUrlValidator < ActiveModel::EachValidator
- DEFAULT_OPTIONS = { protocols: %w(http https ssh git) }
+ DEFAULT_OPTIONS = { protocols: %w(http https ssh git) }.freeze
def validate_each(record, attribute, value)
unless valid_url?(value)
diff --git a/app/validators/duration_validator.rb b/app/validators/duration_validator.rb
new file mode 100644
index 00000000000..10ff44031c6
--- /dev/null
+++ b/app/validators/duration_validator.rb
@@ -0,0 +1,17 @@
+# DurationValidator
+#
+# Validate the format conforms with ChronicDuration
+#
+# Example:
+#
+# class ApplicationSetting < ActiveRecord::Base
+# validates :default_artifacts_expire_in, presence: true, duration: true
+# end
+#
+class DurationValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ ChronicDuration.parse(value)
+ rescue ChronicDuration::DurationParseError
+ record.errors.add(attribute, "is not a correct duration")
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 749c74b8110..057b584e1bc 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -212,8 +212,16 @@
.col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control'
.help-block
- Set the maximum file size each jobs's artifacts can have
- = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
+ Set the maximum file size for each job's artifacts
+ = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
+ .form-group
+ = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :default_artifacts_expire_in, class: 'form-control'
+ .help-block
+ Set the default expiration time for each job's artifacts.
+ 0 for unlimited.
+ = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
- if Gitlab.config.registry.enabled
%fieldset
diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml
index 3b5c713ac2d..a756cb7243a 100644
--- a/app/views/admin/users/_user.html.haml
+++ b/app/views/admin/users/_user.html.haml
@@ -34,7 +34,7 @@
- if user.access_locked?
%li
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
- - if user.can_be_removed?
+ - if user.can_be_removed? && can?(current_user, :destroy_user, @user)
%li.divider
%li
= link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 76b1291fe10..840d843f069 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -173,7 +173,7 @@
.panel-heading
Remove user
.panel-body
- - if @user.can_be_removed?
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p Deleting a user has the following effects:
%ul
%li All user content like authored issues, snippets, comments will be removed
@@ -189,3 +189,6 @@
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete this user.
+ - else
+ %p
+ You don't have access to delete this user.
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 4f36a4a1c73..b82b933c3ad 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -5,6 +5,8 @@
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
+.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
+
- if @projects.any? || params[:filter_projects]
= render 'dashboard/projects_head'
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 5a44ec45b7b..30e63d991bb 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
= f.label :username
- = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 19a947af4ca..d068c895fa3 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -33,7 +33,7 @@
Abuse Reports
%span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- - if askimet_enabled?
+ - if akismet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
%span
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index a4f4079d556..02fb47ec981 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -115,7 +115,7 @@
%h4.prepend-top-0.danger-title
Remove account
.col-lg-9
- - if @user.can_be_removed?
+ - if @user.can_be_removed? && can?(current_user, :destroy_user, @user)
%p
Deleting an account has the following effects:
%ul
@@ -131,4 +131,7 @@
%strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete your account.
+ - else
+ %p
+ You don't have access to delete this user.
.append-bottom-default
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index b10f5fc08e2..903b957c26b 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -101,5 +101,3 @@
$("#created-personal-access-token").click(function() {
this.select();
});
-
- $("#created-personal-access-token").effect('highlight', { color: '#ffff99' }, 2000);
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index d1f7f65bf53..c48d9dd982c 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -19,10 +19,10 @@
= line_content
- when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_old), "##{line_old}"
+ %a{ href: "##{line_old}", data: { linenumber: line_old } }
= line_content
%td.new_line.diff-line-num{ data: { linenumber: line_new } }
- = link_to raw(line_new), "##{line_new}"
+ %a{ href: "##{line_new}", data: { linenumber: line_new } }
= line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index f5ca9607823..b3bc6010efb 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -8,7 +8,6 @@
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
- %script#js-board-list-card{ type: "text/x-template" }= render "projects/boards/components/card"
= render "projects/issues/head"
diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml
deleted file mode 100644
index 891c2c46251..00000000000
--- a/app/views/projects/boards/components/_card.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
- ":index" => "index",
- ":data-issue-id" => "issue.id",
- "@mousedown" => "mouseDown",
- "@mousemove" => "mouseMove",
- "@mouseup" => "showIssue($event)" }
- %issue-card-inner{ ":list" => "list",
- ":issue" => "issue",
- ":issue-link-base" => "issueLinkBase",
- ":root-path" => "rootPath" }
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 19ffe73a08d..ae63f8184df 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -31,7 +31,7 @@
- if can?(current_user, :push_code, @project)
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
- class: "btn btn-remove remove-row #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}",
+ class: "btn btn-remove remove-row js-ajax-loading-spinner #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}",
method: :delete,
data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" },
remote: true,
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index e63bdb38bd8..d3c3e40d518 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -12,12 +12,16 @@
.form-group
= label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name'
+ = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name'
.help-block.text-danger.js-branch-name-error
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
- = text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
+ = hidden_field_tag :ref, params[:ref] || @project.default_branch
+ = dropdown_tag(params[:ref] || @project.default_branch,
+ options: { toggle_class: 'js-branch-select wide',
+ filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
+ data: { selected: params[:ref] || @project.default_branch, field_name: 'ref' } })
.help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 1dbfe830d52..f809c52c367 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -10,10 +10,10 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- %strong.file-title-name.has-tooltip{ data: { title: old_path, container: 'body' } }
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.old_path, container: 'body' } }
= old_path
&rarr;
- %strong.file-title-name.has-tooltip{ data: { title: new_path, container: 'body' } }
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= new_path
- else
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index b9300efd04f..83ae9fd10ec 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -120,7 +120,7 @@
.form-group
- if @project.avatar?
.avatar-container.s160
- = project_icon("#{@project.namespace.to_param}/#{@project.to_param}", alt: '', class: 'avatar project-avatar s160')
+ = project_icon(@project.full_path, alt: '', class: 'avatar project-avatar s160')
%p.light
- if @project.avatar_in_git
Project avatar in repository: #{ @project.avatar_in_git }
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 6e0428e2a31..4147a617d95 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -5,23 +5,35 @@
%div{ class: container_class }
.top-area
%ul.nav-links
- %li{ class: active_when(@scope.nil?) }>
+ %li.js-pipelines-tab-all{ class: active_when(@scope.nil?) }>
= link_to project_pipelines_path(@project) do
All
%span.badge.js-totalbuilds-count
= number_with_delimiter(@pipelines_count)
- %li{ class: active_when(@scope == 'running') }>
+ %li.js-pipelines-tab-pending{ class: active_when(@scope == 'pending') }>
+ = link_to project_pipelines_path(@project, scope: :pending) do
+ Pending
+ %span.badge
+ = number_with_delimiter(@pending_count)
+
+ %li.js-pipelines-tab-running{ class: active_when(@scope == 'running') }>
= link_to project_pipelines_path(@project, scope: :running) do
Running
%span.badge.js-running-count
- = number_with_delimiter(@running_or_pending_count)
+ = number_with_delimiter(@running_count)
+
+ %li.js-pipelines-tab-finished{ class: active_when(@scope == 'finished') }>
+ = link_to project_pipelines_path(@project, scope: :finished) do
+ Finished
+ %span.badge
+ = number_with_delimiter(@finished_count)
- %li{ class: active_when(@scope == 'branches') }>
+ %li.js-pipelines-tab-branches{ class: active_when(@scope == 'branches') }>
= link_to project_pipelines_path(@project, scope: :branches) do
Branches
- %li{ class: active_when(@scope == 'tags') }>
+ %li.js-pipelines-tab-tags{ class: active_when(@scope == 'tags') }>
= link_to project_pipelines_path(@project, scope: :tags) do
Tags
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 55202725b9e..14a270a3039 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -9,7 +9,11 @@
.form-group
= f.label :ref, 'Create for', class: 'control-label'
.col-sm-10
- = f.text_field :ref, required: true, tabindex: 2, class: 'form-control js-branch-name ui-autocomplete-input', autocomplete: :false, id: :ref
+ = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
+ = dropdown_tag(params[:ref] || @project.default_branch,
+ options: { toggle_class: 'js-branch-select wide',
+ filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
+ data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
.help-block Existing branch name, tag
.form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index efb207b9916..02b7b2447ed 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -17,7 +17,7 @@
%strong= parent.full_path + '/'
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
- pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE,
+ pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
title: 'Please choose a group name with no special characters.'
- if parent
= f.hidden_field :parent_id, value: parent.id
diff --git a/app/views/shared/icons/_icon_customization.svg b/app/views/shared/icons/_icon_customization.svg
new file mode 100644
index 00000000000..eb1f8ba129b
--- /dev/null
+++ b/app/views/shared/icons/_icon_customization.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112 90" xmlns:xlink="http://www.w3.org/1999/xlink"><g fill="none" fill-rule="evenodd"><rect width="112" height="90" fill="#fff" rx="6"/><path fill="#eee" fill-rule="nonzero" d="m4 6.01v77.98c0 1.11.899 2.01 2 2.01h100c1.105 0 2-.898 2-2.01v-77.98c0-1.11-.899-2.01-2-2.01h-100c-1.105 0-2 .898-2 2.01m-4 0c0-3.319 2.686-6.01 6-6.01h100c3.315 0 6 2.694 6 6.01v77.98c0 3.319-2.686 6.01-6 6.01h-100c-3.315 0-6-2.694-6-6.01v-77.98"/><g transform="translate(26 35)"><rect width="4" height="39" x="5" fill="#eee" rx="2" id="0"/><rect width="4" height="21" x="5" y="18" fill="#fef0ea" rx="2"/><circle cx="7" cy="13" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 20c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(49 35)"><use xlink:href="#0"/><rect width="4" height="21" x="5" y="18" fill="#b5a7dd" rx="2"/><circle cx="7" cy="25" r="5" fill="#fff"/><path fill="#6b4fbb" fill-rule="nonzero" d="m7 32c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g transform="translate(72 33)"><rect width="4" height="39" x="5" y="2" fill="#eee" rx="2"/><rect width="4" height="34" x="5" y="7" fill="#fef0ea" rx="2"/><circle cx="7" cy="7" r="5" fill="#fff"/><path fill="#fb722e" fill-rule="nonzero" d="m7 14c-3.866 0-7-3.134-7-7 0-3.866 3.134-7 7-7 3.866 0 7 3.134 7 7 0 3.866-3.134 7-7 7m0-4c1.657 0 3-1.343 3-3 0-1.657-1.343-3-3-3-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3"/></g><g fill="#6b4fbb"><circle cx="13.5" cy="11.5" r="2.5"/><circle cx="23.5" cy="11.5" r="2.5" opacity=".5"/><circle cx="33.5" cy="11.5" r="2.5" opacity=".5"/></g><path fill="#eee" d="m0 19h111v4h-111z"/></g></svg>
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 3f7f1a86b9f..6c730e16f67 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -173,7 +173,7 @@
:javascript
gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(issuable))}");
- new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
+ new MilestoneSelect('{"full_path":"#{@project.full_path}"}');
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
gl.Subscription.bindAll('.subscription');
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index dc2fea450bd..c130f3d9e17 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -98,6 +98,7 @@
Snippets
%div{ class: container_class }
+ .user-callout{ 'callout-svg' => custom_icon('icon_customization') }
.tab-content
#activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 7e44b241743..c9658b3fe17 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -120,8 +120,8 @@ class IrkerWorker
end
def compare_url(data, repo_path)
- sha1 = Commit::truncate_sha(data['before'])
- sha2 = Commit::truncate_sha(data['after'])
+ sha1 = Commit.truncate_sha(data['before'])
+ sha2 = Commit.truncate_sha(data['after'])
compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare"
compare_url += "/#{sha1}...#{sha2}"
colorize_url compare_url
@@ -129,7 +129,7 @@ class IrkerWorker
def send_one_commit(project, hook_attrs, repo_name, branch)
commit = commit_from_id project, hook_attrs['id']
- sha = colorize_sha Commit::truncate_sha(hook_attrs['id'])
+ sha = colorize_sha Commit.truncate_sha(hook_attrs['id'])
author = hook_attrs['author']['name']
files = colorize_nb_files(files_count commit)
title = commit.title