diff options
366 files changed, 4817 insertions, 2753 deletions
diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 3f187db0c07..00000000000 --- a/.eslintrc +++ /dev/null @@ -1,56 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "extends": [ - "airbnb-base", - "plugin:vue/recommended" - ], - "globals": { - "__webpack_public_path__": true, - "gl": false, - "gon": false, - "localStorage": false - }, - "parserOptions": { - "parser": "babel-eslint" - }, - "plugins": [ - "filenames", - "import", - "html", - "promise" - ], - "settings": { - "html/html-extensions": [".html", ".html.raw"], - "import/resolver": { - "webpack": { - "config": "./config/webpack.config.js" - } - } - }, - "rules": { - "filenames/match-regex": [2, "^[a-z0-9_]+$"], - "import/no-commonjs": "error", - "no-multiple-empty-lines": ["error", { "max": 1 }], - "promise/catch-or-return": "error", - "no-underscore-dangle": ["error", { "allow": ["__", "_links"] }], - "no-mixed-operators": 0, - "space-before-function-paren": 0, - "curly": 0, - "arrow-parens": 0, - "vue/html-self-closing": [ - "error", - { - "html": { - "void": "always", - "normal": "never", - "component": "always" - }, - "svg": "always", - "math": "always" - } - ] - } -} diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 00000000000..f851e3b67e6 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,77 @@ +--- +env: + browser: true + es6: true +extends: + - airbnb-base + - plugin:vue/recommended +globals: + __webpack_public_path__: true + gl: false + gon: false + localStorage: false +parserOptions: + parser: babel-eslint +plugins: + - filenames + - import + - html + - promise +settings: + html/html-extensions: + - ".html" + - ".html.raw" + import/resolver: + webpack: + config: "./config/webpack.config.js" +rules: + filenames/match-regex: + - error + - "^[a-z0-9_]+$" + import/no-commonjs: error + no-multiple-empty-lines: + - error + - max: 1 + promise/catch-or-return: error + no-underscore-dangle: + - error + - allow: + - __ + - _links + no-mixed-operators: off + vue/html-self-closing: + - error + - html: + void: always + normal: never + component: always + svg: always + math: always + ## Conflicting rules with prettier: + space-before-function-paren: off + curly: off + arrow-parens: off + function-paren-newline: off + object-curly-newline: off + padded-blocks: off + # Disabled for now, to make the eslint 3 -> eslint 4 update smoother + ## Indent rule. We are using the old for now: https://eslint.org/docs/user-guide/migrating-to-4.0.0#indent-rewrite + indent: off + indent-legacy: + - error + - 2 + - SwitchCase: 1 + VariableDeclarator: 1 + outerIIFEBody: 1 + FunctionDeclaration: + parameters: 1 + body: 1 + FunctionExpression: + parameters: 1 + body: 1 + ## Destructuring: https://eslint.org/docs/rules/prefer-destructuring + prefer-destructuring: off + ## no-restricted-globals: https://eslint.org/docs/rules/no-restricted-globals + no-restricted-globals: off + ## no-multi-assign: https://eslint.org/docs/rules/no-multi-assign + no-multi-assign: off diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ef263a3f106..d3daab78940 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git - gitlab-org .default-cache: &default-cache - key: "ruby-2.3.7-with-yarn" + key: "ruby-2.3.7-debian-stretch-with-yarn" paths: - vendor/ruby - .yarn-cache/ @@ -550,7 +550,7 @@ static-analysis: script: - scripts/static-analysis cache: - key: "ruby-2.3.7-with-yarn-and-rubocop" + key: "ruby-2.3.7-debian-stretch-with-yarn-and-rubocop" paths: - vendor/ruby - .yarn-cache/ @@ -591,7 +591,7 @@ ee_compat_check: except: - master - tags - - /^[\d-]+-stable(-ee)?/ + - /[\d-]+-stable(-ee)?/ - /^security-/ - branches@gitlab-org/gitlab-ee - branches@gitlab/gitlab-ee diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cf96035d9..ec92829f7d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.8.3 (2018-05-30) + +### Fixed (4 changes) + +- Replace Gitlab::REVISION with Gitlab.revision and handle installations without a .git directory. !19125 +- Fix encoding of branch names on compare and new merge request page. !19143 +- Fix remote mirror database inconsistencies when upgrading from EE to CE. !19196 +- Fix local storage not being cleared after creating a new issue. + +### Performance (1 change) + +- Memoize Gitlab::Database.version. + + +## 10.8.2 (2018-05-28) + +### Security (3 changes) + +- Prevent user passwords from being changed without providing the previous password. +- Fix API to remove deploy key from project instead of deleting it entirely. +- Fixed bug that allowed importing arbitrary project attributes. + + ## 10.8.1 (2018-05-23) ### Fixed (9 changes) @@ -193,6 +216,15 @@ entry. - Gitaly handles repository forks by default. +## 10.7.5 (2018-05-28) + +### Security (3 changes) + +- Prevent user passwords from being changed without providing the previous password. +- Fix API to remove deploy key from project instead of deleting it entirely. +- Fixed bug that allowed importing arbitrary project attributes. + + ## 10.7.4 (2018-05-21) ### Fixed (1 change) @@ -457,6 +489,16 @@ entry. - Upgrade Gitaly to upgrade its charlock_holmes. +## 10.6.6 (2018-05-28) + +### Security (4 changes) + +- Do not allow non-members to create MRs via forked projects when MRs are private. +- Prevent user passwords from being changed without providing the previous password. +- Fix API to remove deploy key from project instead of deleting it entirely. +- Fixed bug that allowed importing arbitrary project attributes. + + ## 10.6.5 (2018-04-24) ### Security (1 change) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 383d13656e2..64470a1f087 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -181,7 +181,7 @@ Team labels specify what team is responsible for this issue. Assigning a team label makes sure issues get the attention of the appropriate people. -The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Quality, +The current team labels are ~Distribution, ~"CI/CD", ~Discussion, ~Documentation, ~Quality, ~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX". The descriptions on the [labels page][labels-page] explain what falls under the diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 7bb21aff834..89eba2c5b85 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.102.0 +0.103.0 @@ -28,7 +28,7 @@ gem 'mysql2', '~> 0.4.10', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres gem 'rugged', '~> 0.27' -gem 'grape-route-helpers', '~> 2.1.0' +gem 'grape-path-helpers', '~> 1.0' gem 'faraday', '~> 0.12' @@ -133,7 +133,7 @@ gem 'gitlab-markup', '~> 1.6.2' gem 'redcarpet', '~> 3.4' gem 'commonmarker', '~> 0.17' gem 'RedCloth', '~> 4.3.2' -gem 'rdoc', '~> 4.2' +gem 'rdoc', '~> 6.0' gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' @@ -162,7 +162,7 @@ gem 'acts-as-taggable-on', '~> 5.0' # Background jobs gem 'sidekiq', '~> 5.1' gem 'sidekiq-cron', '~> 0.6.0' -gem 'redis-namespace', '~> 1.5.2' +gem 'redis-namespace', '~> 1.6.0' gem 'sidekiq-limit_fetch', '~> 3.4', require: false # Cron Parser @@ -219,7 +219,7 @@ gem 'asana', '~> 0.6.0' gem 'ruby-fogbugz', '~> 0.2.1' # Kubernetes integration -gem 'kubeclient', '~> 3.0' +gem 'kubeclient', '~> 3.1.0' # Sanitize user input gem 'sanitize', '~> 2.0' @@ -320,7 +320,7 @@ group :development, :test do gem 'pry-byebug', '~> 3.4.1', platform: :mri gem 'pry-rails', '~> 0.3.4' - gem 'awesome_print', '~> 1.2.0', require: false + gem 'awesome_print', require: false gem 'fuubar', '~> 2.2.0' gem 'database_cleaner', '~> 1.5.0' @@ -412,7 +412,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.99.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.100.0', require: 'gitaly' gem 'grpc', '~> 1.11.0' # Locked until https://github.com/google/protobuf/issues/4210 is closed diff --git a/Gemfile.lock b/Gemfile.lock index 7332b55c175..fe20570ae89 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,7 +69,7 @@ GEM attr_encrypted (3.1.0) encryptor (~> 3.0.0) attr_required (1.0.0) - awesome_print (1.2.0) + awesome_print (1.8.0) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) @@ -168,7 +168,7 @@ GEM diff-lcs (1.3) diffy (3.1.0) docile (1.1.5) - domain_name (0.5.20170404) + domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) doorkeeper (4.3.2) railties (>= 4.2) @@ -281,7 +281,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (0.99.0) + gitaly-proto (0.100.0) google-protobuf (~> 3.1) grpc (~> 1.10) github-linguist (5.3.3) @@ -348,7 +348,7 @@ GEM signet (~> 0.7) gpgme (2.0.13) mini_portile2 (~> 2.1) - grape (1.0.2) + grape (1.0.3) activesupport builder mustermann-grape (~> 1.0.0) @@ -358,10 +358,10 @@ GEM grape-entity (0.7.1) activesupport (>= 4.0) multi_json (>= 1.3.2) - grape-route-helpers (2.1.0) - activesupport - grape (>= 0.16.0) - rake + grape-path-helpers (1.0.1) + activesupport (~> 4) + grape (~> 1.0) + rake (~> 12) grape_logging (1.7.0) grape grpc (1.11.0) @@ -446,9 +446,9 @@ GEM knapsack (1.16.0) rake timecop (>= 0.1.0) - kubeclient (3.0.0) + kubeclient (3.1.0) http (~> 2.2.2) - recursive-open-struct (~> 1.0.4) + recursive-open-struct (~> 1.0, >= 1.0.4) rest-client (~> 2.0) launchy (2.4.3) addressable (~> 2.3) @@ -694,12 +694,11 @@ GEM ffi rbnacl-libsodium (1.0.11) rbnacl (>= 3.0.1) - rdoc (4.2.2) - json (~> 1.4) + rdoc (6.0.4) re2 (1.1.1) recaptcha (3.0.0) json - recursive-open-struct (1.0.5) + recursive-open-struct (1.1.0) redcarpet (3.4.0) redis (3.3.5) redis-actionpack (5.0.2) @@ -709,8 +708,8 @@ GEM redis-activesupport (5.0.4) activesupport (>= 3, < 6) redis-store (>= 1.3, < 2) - redis-namespace (1.5.2) - redis (~> 3.0, >= 3.0.4) + redis-namespace (1.6.0) + redis (>= 3.0.4) redis-rack (2.0.4) rack (>= 1.5, < 3) redis-store (>= 1.2, < 2) @@ -801,7 +800,7 @@ GEM rubyzip (1.2.1) rufus-scheduler (3.4.0) et-orbi (~> 1.0) - rugged (0.27.0) + rugged (0.27.1) safe_yaml (1.0.4) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -918,7 +917,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.3.0) + unicode-display_width (1.3.2) unicorn (5.1.0) kgio (~> 2.6) raindrops (~> 0.7) @@ -978,7 +977,7 @@ DEPENDENCIES asciidoctor-plantuml (= 0.0.8) asset_sync (~> 2.4) attr_encrypted (~> 3.1.0) - awesome_print (~> 1.2.0) + awesome_print babosa (~> 1.0.2) base32 (~> 0.3.0) batch-loader (~> 1.2.1) @@ -1036,7 +1035,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 0.99.0) + gitaly-proto (~> 0.100.0) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) @@ -1050,7 +1049,7 @@ DEPENDENCIES gpgme grape (~> 1.0) grape-entity (~> 0.7.1) - grape-route-helpers (~> 2.1.0) + grape-path-helpers (~> 1.0) grape_logging (~> 1.7) grpc (~> 1.11.0) haml_lint (~> 0.26.0) @@ -1068,7 +1067,7 @@ DEPENDENCIES jwt (~> 1.5.6) kaminari (~> 1.0) knapsack (~> 1.16) - kubeclient (~> 3.0) + kubeclient (~> 3.1.0) letter_opener_web (~> 1.3.0) license_finder (~> 3.1) licensee (~> 8.9) @@ -1124,12 +1123,12 @@ DEPENDENCIES rblineprof (~> 0.3.6) rbnacl (~> 4.0) rbnacl-libsodium - rdoc (~> 4.2) + rdoc (~> 6.0) re2 (~> 1.1.1) recaptcha (~> 3.0) redcarpet (~> 3.4) redis (~> 3.2) - redis-namespace (~> 1.5.2) + redis-namespace (~> 1.6.0) redis-rails (~> 5.0.2) request_store (~> 1.3) responders (~> 2.0) diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index ae942b2c1a7..5975cb9669e 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -160,7 +160,7 @@ export default { @input="debouncedPreview" /> <span - class="help-block" + class="form-text text-muted" v-html="helpText" ></span> </div> @@ -176,7 +176,7 @@ export default { @input="debouncedPreview" /> <span - class="help-block" + class="form-text text-muted" v-html="helpText" ></span> </div> diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js index eb8a66975ee..1e5f2383223 100644 --- a/app/assets/javascripts/boards/components/modal/empty_state.js +++ b/app/assets/javascripts/boards/components/modal/empty_state.js @@ -41,10 +41,10 @@ gl.issueBoards.ModalEmptyState = Vue.extend({ template: ` <section class="empty-state"> <div class="row"> - <div class="col-xs-12 col-sm-6 order-sm-last"> + <div class="col-12 col-md-6 order-md-last"> <aside class="svg-content"><img :src="emptyStateSvg"/></aside> </div> - <div class="col-xs-12 col-sm-6 order-sm-first"> + <div class="col-12 col-md-6 order-md-first"> <div class="text-content"> <h4>{{ contents.title }}</h4> <p v-html="contents.content"></p> diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js index 6c662432037..f86896d2178 100644 --- a/app/assets/javascripts/boards/components/modal/list.js +++ b/app/assets/javascripts/boards/components/modal/list.js @@ -1,5 +1,3 @@ -/* global ListIssue */ - import Vue from 'vue'; import bp from '../../../breakpoints'; import ModalStore from '../../stores/modal_store'; @@ -56,8 +54,11 @@ gl.issueBoards.ModalList = Vue.extend({ scrollHandler() { const currentPage = Math.floor(this.issues.length / this.perPage); - if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage - && currentPage === this.page) { + if ( + this.scrollTop() > this.scrollHeight() - 100 && + !this.loadingNewPage && + currentPage === this.page + ) { this.loadingNewPage = true; this.page += 1; } diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 371774098b9..eb335f352d3 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -1,71 +1,69 @@ <script> - /* global ListIssue */ +import $ from 'jquery'; +import _ from 'underscore'; +import eventHub from '../eventhub'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import Api from '../../api'; - import $ from 'jquery'; - import _ from 'underscore'; - import eventHub from '../eventhub'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - import Api from '../../api'; - - export default { - name: 'BoardProjectSelect', - components: { - loadingIcon, - }, - props: { - groupId: { - type: Number, - required: true, - default: 0, - }, +export default { + name: 'BoardProjectSelect', + components: { + loadingIcon, + }, + props: { + groupId: { + type: Number, + required: true, + default: 0, }, - data() { - return { - loading: true, - selectedProject: {}, - }; + }, + data() { + return { + loading: true, + selectedProject: {}, + }; + }, + computed: { + selectedProjectName() { + return this.selectedProject.name || 'Select a project'; }, - computed: { - selectedProjectName() { - return this.selectedProject.name || 'Select a project'; + }, + mounted() { + $(this.$refs.projectsDropdown).glDropdown({ + filterable: true, + filterRemote: true, + search: { + fields: ['name_with_namespace'], }, - }, - mounted() { - $(this.$refs.projectsDropdown).glDropdown({ - filterable: true, - filterRemote: true, - search: { - fields: ['name_with_namespace'], - }, - clicked: ({ $el, e }) => { - e.preventDefault(); - this.selectedProject = { - id: $el.data('project-id'), - name: $el.data('project-name'), - }; - eventHub.$emit('setSelectedProject', this.selectedProject); - }, - selectable: true, - data: (term, callback) => { - this.loading = true; - return Api.groupProjects(this.groupId, term, (projects) => { - this.loading = false; - callback(projects); - }); - }, - renderRow(project) { - return ` + clicked: ({ $el, e }) => { + e.preventDefault(); + this.selectedProject = { + id: $el.data('project-id'), + name: $el.data('project-name'), + }; + eventHub.$emit('setSelectedProject', this.selectedProject); + }, + selectable: true, + data: (term, callback) => { + this.loading = true; + return Api.groupProjects(this.groupId, term, projects => { + this.loading = false; + callback(projects); + }); + }, + renderRow(project) { + return ` <li> <a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}"> ${_.escape(project.name)} </a> </li> `; - }, - text: project => project.name, - }); - }, - }; + }, + text: project => project.name, + }); + }, +}; </script> <template> diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue index 1cec84706fc..a4e06bbbe3c 100644 --- a/app/assets/javascripts/ide/components/changed_file_icon.vue +++ b/app/assets/javascripts/ide/components/changed_file_icon.vue @@ -43,7 +43,7 @@ export default { return `${this.changedIcon}-solid`; }, changedIconClass() { - return `multi-${this.changedIcon} pull-left`; + return `multi-${this.changedIcon} float-left`; }, tooltipTitle() { if (!this.showTooltip) return undefined; diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue index 81961fe3c57..705953c86e3 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -144,14 +144,14 @@ export default { <loading-button :loading="submitCommitLoading" :disabled="commitButtonDisabled" - container-class="btn btn-success btn-sm pull-left" + container-class="btn btn-success btn-sm float-left" :label="__('Commit')" @click="commitChanges" /> <button v-if="!discardDraftButtonDisabled" type="button" - class="btn btn-default btn-sm pull-right" + class="btn btn-default btn-sm float-right" @click="discardDraft" > {{ __('Discard draft') }} @@ -159,7 +159,7 @@ export default { <button v-else type="button" - class="btn btn-default btn-sm pull-right" + class="btn btn-default btn-sm float-right" @click="toggleIsSmall" > {{ __('Collapse') }} diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue index c3ac18bfb83..1325fc993b2 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -120,7 +120,7 @@ export default { </ul> <p v-else - class="multi-file-commit-list help-block" + class="multi-file-commit-list form-text text-muted" > {{ __('No changes') }} </p> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue index dcd934f76b7..f14fcdc88ed 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue @@ -80,7 +80,7 @@ export default { {{ __('Commit Message') }} <span v-popover="$options.popoverOptions" - class="help-block prepend-left-10" + class="form-text text-muted prepend-left-10" > <icon name="question" diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index d83a90f71e1..dd2800179ff 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -72,21 +72,19 @@ export default { <form slot="body" @submit.prevent="createEntryInStore" - class="form-group row append-bottom-0" + class="form-group row" > - <fieldset class="form-group append-bottom-0"> - <label class="label-light col-form-label col-sm-3 ide-new-modal-label"> - {{ __('Name') }} - </label> - <div class="col-sm-9"> - <input - type="text" - class="form-control" - v-model="entryName" - ref="fieldName" - /> - </div> - </fieldset> + <label class="label-light col-form-label col-sm-3"> + {{ __('Name') }} + </label> + <div class="col-sm-9"> + <input + type="text" + class="form-control" + v-model="entryName" + ref="fieldName" + /> + </div> </form> </deprecated-modal> </template> diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 442697e1c80..f56aeced806 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -169,7 +169,7 @@ export default { :show-tooltip="true" :show-staged-icon="true" :force-modified-icon="true" - class="pull-right" + class="float-right" /> </span> <new-dropdown diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js index b1e43a1e38c..e5149b1f3ad 100644 --- a/app/assets/javascripts/ide/lib/common/model.js +++ b/app/assets/javascripts/ide/lib/common/model.js @@ -1,4 +1,3 @@ -/* global monaco */ import Disposable from './disposable'; import eventHub from '../../eventhub'; diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index 13aea91d8ba..74f9c112f5a 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -84,11 +84,11 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive }); }; -export const setFileMrChange = ({ state, commit }, { file, mrChange }) => { +export const setFileMrChange = ({ commit }, { file, mrChange }) => { commit(types.SET_FILE_MERGE_REQUEST_CHANGE, { file, mrChange }); }; -export const getRawFileData = ({ state, commit, dispatch }, { path, baseSha }) => { +export const getRawFileData = ({ state, commit }, { path, baseSha }) => { const file = state.entries[path]; return new Promise((resolve, reject) => { service @@ -156,7 +156,7 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn } }; -export const setFileViewMode = ({ state, commit }, { file, viewMode }) => { +export const setFileViewMode = ({ commit }, { file, viewMode }) => { commit(types.SET_FILE_VIEWMODE, { file, viewMode }); }; diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js index da73034fd7d..5ec9bd661bb 100644 --- a/app/assets/javascripts/ide/stores/actions/merge_request.js +++ b/app/assets/javascripts/ide/stores/actions/merge_request.js @@ -3,7 +3,7 @@ import service from '../../services'; import * as types from '../mutation_types'; export const getMergeRequestData = ( - { commit, state, dispatch }, + { commit, state }, { projectId, mergeRequestId, force = false } = {}, ) => new Promise((resolve, reject) => { @@ -32,7 +32,7 @@ export const getMergeRequestData = ( }); export const getMergeRequestChanges = ( - { commit, state, dispatch }, + { commit, state }, { projectId, mergeRequestId, force = false } = {}, ) => new Promise((resolve, reject) => { @@ -58,7 +58,7 @@ export const getMergeRequestChanges = ( }); export const getMergeRequestVersions = ( - { commit, state, dispatch }, + { commit, state }, { projectId, mergeRequestId, force = false } = {}, ) => new Promise((resolve, reject) => { diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index cece9154c82..75cfd9946d7 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -7,10 +7,7 @@ import Poll from '../../../lib/utils/poll'; let eTagPoll; -export const getProjectData = ( - { commit, state, dispatch }, - { namespace, projectId, force = false } = {}, -) => +export const getProjectData = ({ commit, state }, { namespace, projectId, force = false } = {}) => new Promise((resolve, reject) => { if (!state.projects[`${namespace}/${projectId}`] || force) { commit(types.TOGGLE_LOADING, { entry: state }); @@ -40,10 +37,7 @@ export const getProjectData = ( } }); -export const getBranchData = ( - { commit, state, dispatch }, - { projectId, branchId, force = false } = {}, -) => +export const getBranchData = ({ commit, state }, { projectId, branchId, force = false } = {}) => new Promise((resolve, reject) => { if ( typeof state.projects[`${projectId}`] === 'undefined' || @@ -78,7 +72,7 @@ export const getBranchData = ( } }); -export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId, branchId } = {}) => +export const refreshLastCommitData = ({ commit }, { projectId, branchId } = {}) => service .getBranchData(projectId, branchId) .then(({ data }) => { @@ -92,7 +86,7 @@ export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId, flash(__('Error loading last commit.'), 'alert', document, null, false, true); }); -export const pollSuccessCallBack = ({ commit, state, dispatch }, { data }) => { +export const pollSuccessCallBack = ({ commit, state }, { data }) => { if (data.pipelines && data.pipelines.length) { const lastCommitHash = state.projects[state.currentProjectId].branches[state.currentBranchId].commit.id; diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js index 6536be04f0a..cc5116413f7 100644 --- a/app/assets/javascripts/ide/stores/actions/tree.js +++ b/app/assets/javascripts/ide/stores/actions/tree.js @@ -5,7 +5,7 @@ import * as types from '../mutation_types'; import { findEntry } from '../utils'; import FilesDecoratorWorker from '../workers/files_decorator_worker'; -export const toggleTreeOpen = ({ commit, dispatch }, path) => { +export const toggleTreeOpen = ({ commit }, path) => { commit(types.TOGGLE_TREE_OPEN, path); }; @@ -23,7 +23,7 @@ export const handleTreeEntryAction = ({ commit, dispatch }, row) => { } }; -export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => { +export const getLastCommitData = ({ state, commit, dispatch }, tree = state) => { if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return; service @@ -49,7 +49,7 @@ export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = s .catch(() => flash('Error fetching log data.', 'alert', document, null, false, true)); }; -export const getFiles = ({ state, commit, dispatch }, { projectId, branchId } = {}) => +export const getFiles = ({ state, commit }, { projectId, branchId } = {}) => new Promise((resolve, reject) => { if (!state.trees[`${projectId}/${branchId}`]) { const selectedProject = state.projects[projectId]; diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index cd25c3060f2..0a0db4033c8 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -31,9 +31,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => { const currentProject = rootState.projects[rootState.currentProjectId]; const commitStats = data.stats ? sprintf(__('with %{additions} additions, %{deletions} deletions.'), { - additions: data.stats.additions, // eslint-disable-line indent - deletions: data.stats.deletions, // eslint-disable-line indent - }) // eslint-disable-line indent + additions: data.stats.additions, // eslint-disable-line indent-legacy + deletions: data.stats.deletions, // eslint-disable-line indent-legacy + }) // eslint-disable-line indent-legacy : ''; const commitMsg = sprintf( __('Your changes have been committed. Commit %{commitId} %{commitStats}'), @@ -74,10 +74,7 @@ export const checkCommitStatus = ({ rootState }) => ), ); -export const updateFilesAfterCommit = ( - { commit, dispatch, state, rootState, rootGetters }, - { data }, -) => { +export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }) => { const selectedProject = rootState.projects[rootState.currentProjectId]; const lastCommit = { commit_path: `${selectedProject.web_url}/commit/${data.id}`, diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 90d4e19e90b..bb8b3d91e40 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -30,7 +30,7 @@ export default class IssuableForm { } this.initAutosave(); - this.form.on('submit:success', this.handleSubmit); + this.form.on('submit', this.handleSubmit); this.form.on('click', '.btn-cancel', this.resetAutosave); this.initWip(); diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index c67bd7fb0c6..611e8200b4d 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -84,7 +84,7 @@ export default class Job { If the browser does not support position sticky, it returns the position as static. If the browser does support sticky, then we allow the browser to handle it, if not then we use a polyfill - **/ + */ if (this.$topBar.css('position') !== 'static') return; StickyFill.add(this.$topBar); diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 82fec7e936b..8f3c66b0cbe 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -48,11 +48,10 @@ export default { return `${this.job.runner.description} (#${this.job.runner.id})`; }, retryButtonClass() { - let className = 'js-retry-button pull-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; + let className = + 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; className += - this.job.status && this.job.recoverable - ? ' btn-primary' - : ' btn-inverted-secondary'; + this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary'; return className; }, hasTimeout() { @@ -104,8 +103,7 @@ export default { <button type="button" :aria-label="__('Toggle Sidebar')" - class="btn btn-blank gutter-toggle pull-right - d-block d-sm-block d-md-none js-sidebar-build-toggle" + class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle" > <i aria-hidden="true" diff --git a/app/assets/javascripts/jobs/job_details_mediator.js b/app/assets/javascripts/jobs/job_details_mediator.js index 5a216f8fae2..89019da9d1e 100644 --- a/app/assets/javascripts/jobs/job_details_mediator.js +++ b/app/assets/javascripts/jobs/job_details_mediator.js @@ -1,5 +1,3 @@ -/* global Build */ - import Visibility from 'visibilityjs'; import Flash from '../flash'; import Poll from '../lib/utils/poll'; @@ -50,7 +48,8 @@ export default class JobMediator { } getJob() { - return this.service.getJob() + return this.service + .getJob() .then(response => this.successCallback(response)) .catch(() => this.errorCallback()); } diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js index 2f4328b56e1..2cc5fb10027 100644 --- a/app/assets/javascripts/locale/index.js +++ b/app/assets/javascripts/locale/index.js @@ -9,7 +9,7 @@ delete window.translations; Translates `text` @param text The text to be translated @returns {String} The translated text -**/ +*/ const gettext = locale.gettext.bind(locale); /** @@ -21,7 +21,7 @@ const gettext = locale.gettext.bind(locale); @param pluralText Plural text to translate (eg. '%d days') @param count Number to decide which translation to use (eg. 2) @returns {String} Translated text with the number replaced (eg. '2 days') -**/ +*/ const ngettext = (text, pluralText, count) => { const translated = locale.ngettext(text, pluralText, count).replace(/%d/g, count).split('|'); @@ -38,7 +38,7 @@ const ngettext = (text, pluralText, count) => { (eg. 'Context') @param key Is the dynamic variable you want to be translated @returns {String} Translated context based text -**/ +*/ const pgettext = (keyOrContext, key) => { const normalizedKey = key ? `${keyOrContext}|${key}` : keyOrContext; const translated = gettext(normalizedKey).split('|'); diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js index 5f4a053f98e..599104dcfa0 100644 --- a/app/assets/javascripts/locale/sprintf.js +++ b/app/assets/javascripts/locale/sprintf.js @@ -10,7 +10,7 @@ import _ from 'underscore'; @see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf @see https://gitlab.com/gitlab-org/gitlab-ce/issues/37992 -**/ +*/ export default (input, parameters, escapeParameters = true) => { let output = input; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 3548c07aea8..493c119dc6f 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -362,7 +362,7 @@ export default class MergeRequestTabs { // // status - Boolean, true to show, false to hide toggleLoading(status) { - $('.mr-loading-status .loading').toggleClass('hidden', status); + $('.mr-loading-status .loading').toggleClass('hidden', !status); } diffViewType() { @@ -427,7 +427,7 @@ export default class MergeRequestTabs { If the browser does not support position sticky, it returns the position as static. If the browser does support sticky, then we allow the browser to handle it, if not then we default back to Bootstraps affix - **/ + */ if ($tabs.css('position') !== 'static') return; const $diffTabs = $('#diff-notes-app'); diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 98ce070288e..b2222476924 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -12,20 +12,13 @@ import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; let eTagPoll; -export const setNotesData = ({ commit }, data) => - commit(types.SET_NOTES_DATA, data); -export const setNoteableData = ({ commit }, data) => - commit(types.SET_NOTEABLE_DATA, data); -export const setUserData = ({ commit }, data) => - commit(types.SET_USER_DATA, data); -export const setLastFetchedAt = ({ commit }, data) => - commit(types.SET_LAST_FETCHED_AT, data); -export const setInitialNotes = ({ commit }, data) => - commit(types.SET_INITIAL_NOTES, data); -export const setTargetNoteHash = ({ commit }, data) => - commit(types.SET_TARGET_NOTE_HASH, data); -export const toggleDiscussion = ({ commit }, data) => - commit(types.TOGGLE_DISCUSSION, data); +export const setNotesData = ({ commit }, data) => commit(types.SET_NOTES_DATA, data); +export const setNoteableData = ({ commit }, data) => commit(types.SET_NOTEABLE_DATA, data); +export const setUserData = ({ commit }, data) => commit(types.SET_USER_DATA, data); +export const setLastFetchedAt = ({ commit }, data) => commit(types.SET_LAST_FETCHED_AT, data); +export const setInitialNotes = ({ commit }, data) => commit(types.SET_INITIAL_NOTES, data); +export const setTargetNoteHash = ({ commit }, data) => commit(types.SET_TARGET_NOTE_HASH, data); +export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data); export const fetchNotes = ({ commit }, path) => service @@ -69,20 +62,14 @@ export const createNewNote = ({ commit }, { endpoint, data }) => return res; }); -export const removePlaceholderNotes = ({ commit }) => - commit(types.REMOVE_PLACEHOLDER_NOTES); +export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES); -export const toggleResolveNote = ( - { commit }, - { endpoint, isResolved, discussion }, -) => +export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) => service .toggleResolveNote(endpoint, isResolved) .then(res => res.json()) .then(res => { - const mutationType = discussion - ? types.UPDATE_DISCUSSION - : types.UPDATE_NOTE; + const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE; commit(mutationType, res); }); @@ -114,7 +101,7 @@ export const reopenIssue = ({ commit, dispatch, state }) => { export const toggleStateButtonLoading = ({ commit }, value) => commit(types.TOGGLE_STATE_BUTTON_LOADING, value); -export const emitStateChangedEvent = ({ commit, getters }, data) => { +export const emitStateChangedEvent = ({ getters }, data) => { const event = new CustomEvent('issuable_vue_app:change', { detail: { data, @@ -179,10 +166,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { loadAwardsHandler() .then(awardsHandler => { - awardsHandler.addAwardToEmojiBar( - votesBlock, - commandsChanges.emoji_award, - ); + awardsHandler.addAwardToEmojiBar(votesBlock, commandsChanges.emoji_award); awardsHandler.scrollToAwards(); }) .catch(() => { @@ -194,10 +178,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { }); } - if ( - commandsChanges.spend_time != null || - commandsChanges.time_estimate != null - ) { + if (commandsChanges.spend_time != null || commandsChanges.time_estimate != null) { sidebarTimeTrackingEventHub.$emit('timeTrackingUpdated', res); } } @@ -218,14 +199,8 @@ const pollSuccessCallBack = (resp, commit, state, getters) => { resp.notes.forEach(note => { if (notesById[note.id]) { commit(types.UPDATE_NOTE, note); - } else if ( - note.type === constants.DISCUSSION_NOTE || - note.type === constants.DIFF_NOTE - ) { - const discussion = utils.findNoteObjectById( - state.notes, - note.discussion_id, - ); + } else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) { + const discussion = utils.findNoteObjectById(state.notes, note.discussion_id); if (discussion) { commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); @@ -249,11 +224,8 @@ export const poll = ({ commit, state, getters }) => { method: 'poll', data: state, successCallback: resp => - resp - .json() - .then(data => pollSuccessCallBack(data, commit, state, getters)), - errorCallback: () => - Flash('Something went wrong while fetching latest comments.'), + resp.json().then(data => pollSuccessCallBack(data, commit, state, getters)), + errorCallback: () => Flash('Something went wrong while fetching latest comments.'), }); if (!Visibility.hidden()) { @@ -292,14 +264,11 @@ export const fetchData = ({ commit, state, getters }) => { .catch(() => Flash('Something went wrong while fetching latest comments.')); }; -export const toggleAward = ( - { commit, state, getters, dispatch }, - { awardName, noteId }, -) => { +export const toggleAward = ({ commit, getters }, { awardName, noteId }) => { commit(types.TOGGLE_AWARD, { awardName, note: getters.notesById[noteId] }); }; -export const toggleAwardRequest = ({ commit, getters, dispatch }, data) => { +export const toggleAwardRequest = ({ dispatch }, data) => { const { endpoint, awardName } = data; return service diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index bb91ac84ffb..8737f537296 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -1,9 +1,15 @@ import groupAvatar from '~/group_avatar'; import TransferDropdown from '~/groups/transfer_dropdown'; import initConfirmDangerModal from '~/confirm_danger_modal'; +import initSettingsPanels from '~/settings_panels'; document.addEventListener('DOMContentLoaded', () => { groupAvatar(); new TransferDropdown(); // eslint-disable-line no-new initConfirmDangerModal(); }); + +document.addEventListener('DOMContentLoaded', () => { + // Initialize expandable settings panels + initSettingsPanels(); +}); diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 755a34b7348..06b0ab184ed 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -213,7 +213,7 @@ </i> </div> </div> - <span class="help-block">{{ visibilityLevelDescription }}</span> + <span class="form-text text-muted">{{ visibilityLevelDescription }}</span> <label v-if="visibilityLevel !== visibilityOptions.PRIVATE" class="request-access" diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js index 34a12ef76a1..dcd0b9a76ce 100644 --- a/app/assets/javascripts/pages/projects/wikis/wikis.js +++ b/app/assets/javascripts/pages/projects/wikis/wikis.js @@ -1,5 +1,7 @@ import bp from '../../../breakpoints'; import { slugify } from '../../../lib/utils/text_utility'; +import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils'; +import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility'; export default class Wikis { constructor() { @@ -28,7 +30,12 @@ export default class Wikis { if (slug.length > 0) { const wikisPath = slugInput.getAttribute('data-wikis-path'); - window.location.href = `${wikisPath}/${slug}`; + + // If the wiki is empty, we need to merge the current URL params to keep the "create" view. + const params = parseQueryStringIntoObject(window.location.search.substr(1)); + const url = mergeUrlParams(params, `${wikisPath}/${slug}`); + redirectTo(url); + e.preventDefault(); } } diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js index 53030045292..18c7b21cf8c 100644 --- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js +++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js @@ -5,7 +5,7 @@ import $ from 'jquery'; * * Toggling this checkbox adds/removes a `remember_me` parameter to the * login buttons' href, which is passed on to the omniauth callback. - **/ + */ export default class OAuthRememberMe { constructor(opts = {}) { diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index ca375007ec5..9404b06615e 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -77,10 +77,9 @@ export default class UserTabs { this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); this.windowLocation = window.location; - this.$parentEl.find('.nav-links a') - .each((i, navLink) => { - this.loaded[$(navLink).attr('data-action')] = false; - }); + this.$parentEl.find('.nav-links a').each((i, navLink) => { + this.loaded[$(navLink).attr('data-action')] = false; + }); this.actions = Object.keys(this.loaded); this.bindEvents(); @@ -116,8 +115,7 @@ export default class UserTabs { } activateTab(action) { - return this.$parentEl.find(`.nav-links .js-${action}-tab a`) - .tab('show'); + return this.$parentEl.find(`.nav-links .js-${action}-tab a`).tab('show'); } setTab(action, endpoint) { @@ -137,7 +135,8 @@ export default class UserTabs { loadTab(action, endpoint) { this.toggleLoading(true); - return axios.get(endpoint) + return axios + .get(endpoint) .then(({ data }) => { const tabSelector = `div#${action}`; this.$parentEl.find(tabSelector).html(data.html); @@ -161,10 +160,11 @@ export default class UserTabs { const utcOffset = $calendarWrap.data('utcOffset'); let utcFormatted = 'UTC'; if (utcOffset !== 0) { - utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`; + utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${utcOffset / 3600}`; } - axios.get(calendarPath) + axios + .get(calendarPath) .then(({ data }) => { $calendarWrap.html(CALENDAR_TEMPLATE); $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); @@ -180,17 +180,20 @@ export default class UserTabs { } toggleLoading(status) { - return this.$parentEl.find('.loading-status .loading') - .toggleClass('hidden', status); + return this.$parentEl.find('.loading-status .loading').toggleClass('hidden', !status); } setCurrentAction(source) { let newState = source; newState = newState.replace(/\/+$/, ''); newState += this.windowLocation.search + this.windowLocation.hash; - history.replaceState({ - url: newState, - }, document.title, newState); + history.replaceState( + { + url: newState, + }, + document.title, + newState, + ); return newState; } diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue index a7a2a7235fd..b37febe523c 100644 --- a/app/assets/javascripts/profile/account/components/update_username.vue +++ b/app/assets/javascripts/profile/account/components/update_username.vue @@ -99,7 +99,7 @@ Please update your Git repository remotes as soon as possible.`), :disabled="isRequestPending" /> </div> - <p class="help-block"> + <p class="form-text text-muted"> {{ path }} </p> </div> diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue index 5cb1ae670dc..ab7d2d41ece 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue @@ -8,14 +8,24 @@ export default { name: 'GkeMachineTypeDropdown', mixins: [gkeDropdownMixin], computed: { - ...mapState(['projectHasBillingEnabled', 'selectedZone', 'selectedMachineType']), + ...mapState([ + 'isValidatingProjectBilling', + 'projectHasBillingEnabled', + 'selectedZone', + 'selectedMachineType', + ]), ...mapState({ items: 'machineTypes' }), ...mapGetters(['hasZone', 'hasMachineType']), allDropdownsSelected() { return this.projectHasBillingEnabled && this.hasZone && this.hasMachineType; }, isDisabled() { - return !this.projectHasBillingEnabled || !this.selectedZone; + return ( + this.isLoading || + this.isValidatingProjectBilling || + !this.projectHasBillingEnabled || + !this.hasZone + ); }, toggleText() { if (this.isLoading) { @@ -45,11 +55,15 @@ export default { }, watch: { selectedZone() { - this.isLoading = true; + this.hasErrors = false; + + if (this.hasZone) { + this.isLoading = true; - this.fetchMachineTypes() - .then(this.fetchSuccessHandler) - .catch(this.fetchFailureHandler); + this.fetchMachineTypes() + .then(this.fetchSuccessHandler) + .catch(this.fetchFailureHandler); + } }, selectedMachineType() { this.enableSubmit(); @@ -118,7 +132,7 @@ export default { </div> </div> <span - class="help-block" + class="form-text text-muted" :class="{ 'gl-field-error': hasErrors }" v-if="hasErrors" > diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue index 44ebdb12ada..25350ef0fa9 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue @@ -14,20 +14,17 @@ export default { required: true, }, }, - data() { - return { - isValidatingProjectBilling: false, - }; - }, computed: { - ...mapState(['selectedProject', 'projectHasBillingEnabled']), + ...mapState(['selectedProject', 'isValidatingProjectBilling', 'projectHasBillingEnabled']), ...mapState({ items: 'projects' }), ...mapGetters(['hasProject']), hasOneProject() { return this.items && this.items.length === 1; }, isDisabled() { - return this.items && this.items.length < 2; + return ( + this.isLoading || this.isValidatingProjectBilling || (this.items && this.items.length < 2) + ); }, toggleText() { if (this.isValidatingProjectBilling) { @@ -103,17 +100,12 @@ export default { }, watch: { selectedProject() { - this.isLoading = true; - this.isValidatingProjectBilling = true; + this.setIsValidatingProjectBilling(true); this.validateProjectBilling() .then(this.validateProjectBillingSuccessHandler) .catch(this.validateProjectBillingFailureHandler); }, - projectHasBillingEnabled(billingEnabled) { - this.hasErrors = !billingEnabled; - this.isValidatingProjectBilling = false; - }, }, created() { this.isLoading = true; @@ -123,7 +115,7 @@ export default { .catch(this.fetchFailureHandler); }, methods: { - ...mapActions(['fetchProjects', 'validateProjectBilling']), + ...mapActions(['fetchProjects', 'setIsValidatingProjectBilling', 'validateProjectBilling']), ...mapActions({ setItem: 'setProject' }), fetchSuccessHandler() { if (this.defaultValue) { @@ -140,10 +132,9 @@ export default { this.hasErrors = false; }, validateProjectBillingSuccessHandler() { - this.isLoading = false; + this.hasErrors = !this.projectHasBillingEnabled; }, validateProjectBillingFailureHandler(resp) { - this.isLoading = false; this.hasErrors = true; this.gapiError = resp.result ? resp.result.error.message : resp; @@ -202,7 +193,7 @@ export default { </div> </div> <span - class="help-block" + class="form-text text-muted" :class="{ 'gl-field-error': hasErrors }" v-html="helpText" ></span> diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue index 43531813407..8ee4eefcd91 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue @@ -8,10 +8,16 @@ export default { name: 'GkeZoneDropdown', mixins: [gkeDropdownMixin], computed: { - ...mapState(['selectedProject', 'selectedZone', 'projects', 'projectHasBillingEnabled']), + ...mapState([ + 'selectedProject', + 'selectedZone', + 'projects', + 'isValidatingProjectBilling', + 'projectHasBillingEnabled', + ]), ...mapState({ items: 'zones' }), isDisabled() { - return !this.projectHasBillingEnabled; + return this.isLoading || this.isValidatingProjectBilling || !this.projectHasBillingEnabled; }, toggleText() { if (this.isLoading) { @@ -34,13 +40,16 @@ export default { }, }, watch: { - projectHasBillingEnabled(billingEnabled) { - if (!billingEnabled) return false; - this.isLoading = true; + isValidatingProjectBilling(isValidating) { + this.hasErrors = false; - return this.fetchZones() - .then(this.fetchSuccessHandler) - .catch(this.fetchFailureHandler); + if (!isValidating && this.projectHasBillingEnabled) { + this.isLoading = true; + + this.fetchZones() + .then(this.fetchSuccessHandler) + .catch(this.fetchFailureHandler); + } }, }, methods: { @@ -97,7 +106,7 @@ export default { </div> </div> <span - class="help-block" + class="form-text text-muted" :class="{ 'gl-field-error': hasErrors }" v-if="hasErrors" > diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js index 409265175a4..4834a856271 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js @@ -31,6 +31,10 @@ export const setMachineType = ({ commit }, selectedMachineType) => { commit(types.SET_MACHINE_TYPE, selectedMachineType); }; +export const setIsValidatingProjectBilling = ({ commit }, isValidatingProjectBilling) => { + commit(types.SET_IS_VALIDATING_PROJECT_BILLING, isValidatingProjectBilling); +}; + export const fetchProjects = ({ commit }) => gapiResourceListRequest({ resource: gapi.client.cloudresourcemanager.projects, @@ -40,20 +44,25 @@ export const fetchProjects = ({ commit }) => payloadKey: 'projects', }); -export const validateProjectBilling = ({ commit, state }) => +export const validateProjectBilling = ({ dispatch, commit, state }) => new Promise((resolve, reject) => { const request = gapi.client.cloudbilling.projects.getBillingInfo({ name: `projects/${state.selectedProject.projectId}`, }); + commit(types.SET_ZONE, ''); + commit(types.SET_MACHINE_TYPE, ''); + return request.then( resp => { const { billingEnabled } = resp.result; commit(types.SET_PROJECT_BILLING_STATUS, !!billingEnabled); + dispatch('setIsValidatingProjectBilling', false); resolve(); }, resp => { + dispatch('setIsValidatingProjectBilling', false); reject(resp); }, ); diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js index 98574289bc4..45a91efc2d9 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js @@ -1,5 +1,6 @@ export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT_BILLING_STATUS = 'SET_PROJECT_BILLING_STATUS'; +export const SET_IS_VALIDATING_PROJECT_BILLING = 'SET_IS_VALIDATING_PROJECT_BILLING'; export const SET_ZONE = 'SET_ZONE'; export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE'; export const SET_PROJECTS = 'SET_PROJECTS'; diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js index a9ff3b503f4..88a2c1b630d 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js @@ -4,6 +4,9 @@ export default { [types.SET_PROJECT](state, selectedProject) { Object.assign(state, { selectedProject }); }, + [types.SET_IS_VALIDATING_PROJECT_BILLING](state, isValidatingProjectBilling) { + Object.assign(state, { isValidatingProjectBilling }); + }, [types.SET_PROJECT_BILLING_STATUS](state, projectHasBillingEnabled) { Object.assign(state, { projectHasBillingEnabled }); }, diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js index 4110377c0f4..9f3c473d4bc 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js @@ -5,6 +5,7 @@ export default () => ({ }, selectedZone: '', selectedMachineType: '', + isValidatingProjectBilling: null, projectHasBillingEnabled: null, projects: [], zones: [], diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js index ae54fa5f1a9..658caeecde1 100644 --- a/app/assets/javascripts/raven/raven_config.js +++ b/app/assets/javascripts/raven/raven_config.js @@ -37,7 +37,7 @@ const IGNORE_URLS = [ /extensions\//i, /^chrome:\/\//i, // Other plugins - /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb + /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb /webappstoolbarba\.texthelp\.com\//i, /metrics\.itunes\.apple\.com\.edgesuite\.net\//i, ]; diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js index 593a43c7cc1..c0de03373d8 100644 --- a/app/assets/javascripts/registry/stores/actions.js +++ b/app/assets/javascripts/registry/stores/actions.js @@ -7,9 +7,10 @@ Vue.use(VueResource); export const fetchRepos = ({ commit, state }) => { commit(types.TOGGLE_MAIN_LOADING); - return Vue.http.get(state.endpoint) + return Vue.http + .get(state.endpoint) .then(res => res.json()) - .then((response) => { + .then(response => { commit(types.TOGGLE_MAIN_LOADING); commit(types.SET_REPOS_LIST, response); }); @@ -18,19 +19,20 @@ export const fetchRepos = ({ commit, state }) => { export const fetchList = ({ commit }, { repo, page }) => { commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - return Vue.http.get(repo.tagsPath, { params: { page } }) - .then((response) => { - const headers = response.headers; + return Vue.http.get(repo.tagsPath, { params: { page } }).then(response => { + const headers = response.headers; - return response.json().then((resp) => { - commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - commit(types.SET_REGISTRY_LIST, { repo, resp, headers }); - }); + return response.json().then(resp => { + commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); + commit(types.SET_REGISTRY_LIST, { repo, resp, headers }); }); + }); }; +// eslint-disable-next-line no-unused-vars export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath); +// eslint-disable-next-line no-unused-vars export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue index 926a3172412..875c3323dbb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue @@ -1,15 +1,63 @@ -/* -The squash-before-merge button is EE only, but it's located right in the middle -of the readyToMerge state component template. - -If we didn't declare this component in CE, we'd need to maintain a separate copy -of the readyToMergeState template in EE, which is pretty big and likely to change. - -Instead, in CE, we declare the component, but it's hidden and is configured to do nothing. -In EE, the configuration extends this object to add a functioning squash-before-merge -button. -*/ - <script> - export default {}; +import Icon from '~/vue_shared/components/icon.vue'; +import eventHub from '~/vue_merge_request_widget/event_hub'; +import tooltip from '~/vue_shared/directives/tooltip'; + +export default { + components: { + Icon, + }, + directives: { + tooltip, + }, + props: { + mr: { + type: Object, + required: true, + }, + isMergeButtonDisabled: { + type: Boolean, + required: true, + }, + }, + data() { + return { + squashBeforeMerge: this.mr.squash, + }; + }, + methods: { + updateSquashModel() { + eventHub.$emit('MRWidgetUpdateSquash', this.squashBeforeMerge); + }, + }, +}; </script> + +<template> + <div class="accept-control inline"> + <label class="merge-param-checkbox"> + <input + type="checkbox" + name="squash" + class="qa-squash-checkbox" + :disabled="isMergeButtonDisabled" + v-model="squashBeforeMerge" + @change="updateSquashModel" + /> + {{ __('Squash commits') }} + </label> + <a + :href="mr.squashBeforeMergeHelpPath" + data-title="About this feature" + data-placement="bottom" + target="_blank" + rel="noopener noreferrer nofollow" + data-container="body" + v-tooltip + > + <icon + name="question-o" + /> + </a> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 1d1c8ebc179..3a194320bd8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -6,11 +6,13 @@ import MergeRequest from '../../../merge_request'; import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon.vue'; import eventHub from '../../event_hub'; +import SquashBeforeMerge from './mr_widget_squash_before_merge.vue'; export default { name: 'ReadyToMerge', components: { statusIcon, + 'squash-before-merge': SquashBeforeMerge, }, props: { mr: { type: Object, required: true }, @@ -101,6 +103,12 @@ export default { return enableSquashBeforeMerge && commitsCount > 1; }, }, + created() { + eventHub.$on('MRWidgetUpdateSquash', this.handleUpdateSquash); + }, + beforeDestroy() { + eventHub.$off('MRWidgetUpdateSquash', this.handleUpdateSquash); + }, methods: { shouldShowMergeControls() { return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText; @@ -128,13 +136,9 @@ export default { commit_message: this.commitMessage, merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds, should_remove_source_branch: this.removeSourceBranch === true, + squash: this.mr.squash, }; - // Only truthy in EE extension of this component - if (this.setAdditionalParams) { - this.setAdditionalParams(options); - } - this.isMakingRequest = true; this.service.merge(options) .then(res => res.data) @@ -154,6 +158,9 @@ export default { new Flash('Something went wrong. Please try again.'); // eslint-disable-line }); }, + handleUpdateSquash(val) { + this.mr.squash = val; + }, initiateMergePolling() { simplePoll((continuePolling, stopPolling) => { this.handleMergePolling(continuePolling, stopPolling); diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 83b7b054e6f..e5b7e1f1c68 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -15,6 +15,11 @@ export default class MergeRequestStore { const currentUser = data.current_user; const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; + this.squash = data.squash; + this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath || + data.squash_before_merge_help_path; + this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true; + this.title = data.title; this.targetBranch = data.target_branch; this.sourceBranch = data.source_branch; diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js index 2c7886ec308..48c63373b77 100644 --- a/app/assets/javascripts/vue_shared/translate.js +++ b/app/assets/javascripts/vue_shared/translate.js @@ -13,7 +13,7 @@ export default (Vue) => { @param text The text to be translated @returns {String} The translated text - **/ + */ __, /** Translate the text with a number @@ -24,7 +24,7 @@ export default (Vue) => { @param pluralText Plural text to translate (eg. '%d days') @param count Number to decide which translation to use (eg. 2) @returns {String} Translated text with the number replaced (eg. '2 days') - **/ + */ n__, /** Translate context based text @@ -36,7 +36,7 @@ export default (Vue) => { (eg. 'Context') @param key Is the dynamic variable you want to be translated @returns {String} Translated context based text - **/ + */ s__, sprintf, }, diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index 3b7ee5c73e6..e1a47f3d686 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -131,6 +131,10 @@ table { } .card { + .card-title { + margin-bottom: 0; + } + &.card-without-border { @extend .border-0; } @@ -147,3 +151,7 @@ table { .nav-tabs .nav-link { border: 0; } + +pre code { + white-space: pre-wrap; +} diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 17f4958d535..d54490c87c6 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -2,7 +2,7 @@ * Well styled list * */ -.card-body-list { +.hover-list { position: relative; margin: 0; padding: 0; diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 667661d8b5c..ed5a1c91d8f 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -74,12 +74,6 @@ body.modal-open { } } -@include media-breakpoint-up(lg) { - .modal-full { - width: 98%; - } -} - .modal { background-color: $black-transparent; z-index: 2100; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 0188a55cbf3..f85f66b9c0b 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -1,3 +1,30 @@ +@mixin flat-connector-before($length: 44px) { + &::before { + content: ''; + position: absolute; + top: 48%; + left: -$length; + border-top: 2px solid $border-color; + width: $length; + height: 1px; + } +} + +@mixin build-content($border-radius: 30px) { + display: inline-block; + padding: 8px 10px 9px; + width: 100%; + border: 1px solid $border-color; + border-radius: $border-radius; + background-color: $white-light; + + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $dropdown-toggle-active-border-color; + color: $gl-text-color; + } +} + .pipelines { .stage { max-width: 90px; @@ -357,14 +384,8 @@ &:not(:first-child) { margin-left: 44px; - .left-connector::before { - content: ''; - position: absolute; - top: 48%; - left: -44px; - border-top: 2px solid $border-color; - width: 44px; - height: 1px; + .left-connector { + @include flat-connector-before; } } } @@ -479,12 +500,7 @@ } .build-content { - display: inline-block; - padding: 8px 10px 9px; - width: 100%; - border: 1px solid $border-color; - border-radius: 30px; - background-color: $white-light; + @include build-content(); } a.build-content:hover, @@ -622,8 +638,7 @@ } } -// Dropdown button in mini pipeline graph -button.mini-pipeline-graph-dropdown-toggle { +@mixin mini-pipeline-item() { border-radius: 100px; background-color: $white-light; border-width: 1px; @@ -636,30 +651,6 @@ button.mini-pipeline-graph-dropdown-toggle { position: relative; vertical-align: middle; - > .fa.fa-caret-down { - position: absolute; - left: 20px; - top: 5px; - display: inline-block; - visibility: hidden; - opacity: 0; - color: inherit; - font-size: 12px; - transition: visibility 0.1s, opacity 0.1s linear; - } - - &:active, - &:focus, - &:hover { - outline: none; - width: 35px; - - .fa.fa-caret-down { - visibility: visible; - opacity: 1; - } - } - // Dropdown button animation in mini pipeline graph &.ci-status-icon-success { @include mini-pipeline-graph-color($green-100, $green-500, $green-600); @@ -691,6 +682,35 @@ button.mini-pipeline-graph-dropdown-toggle { } } +// Dropdown button in mini pipeline graph +button.mini-pipeline-graph-dropdown-toggle { + @include mini-pipeline-item(); + + > .fa.fa-caret-down { + position: absolute; + left: 20px; + top: 5px; + display: inline-block; + visibility: hidden; + opacity: 0; + color: inherit; + font-size: 12px; + transition: visibility 0.1s, opacity 0.1s linear; + } + + &:active, + &:focus, + &:hover { + outline: none; + width: 35px; + + .fa.fa-caret-down { + visibility: visible; + opacity: 1; + } + } +} + /** Action icons inside dropdowns: - mini graph in pipelines table @@ -744,7 +764,7 @@ button.mini-pipeline-graph-dropdown-toggle { } } - // SVGs in the commit widget and mr widget + // SVGs in the commit widget and mr widget a.ci-action-icon-container.ci-action-icon-wrapper svg { top: 2px; } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 06078f1d12e..5d0d59e12f2 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -405,7 +405,7 @@ table.u2f-registrations { margin-right: $gl-padding / 4; } - .label-verification-status { + .badge-verification-status { border-width: 1px; border-style: solid; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 17d7087bd85..d5c6048037a 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -546,7 +546,7 @@ margin-right: 0; } - &.help-block { + &.form-text.text-muted { margin-left: 0; right: 0; } @@ -952,7 +952,7 @@ height: 30px; } - .help-block { + .form-text.text-muted { margin-top: 2px; color: $blue-500; cursor: pointer; @@ -1088,10 +1088,6 @@ font-size: 12px; } -.ide-new-modal-label { - line-height: 34px; -} - .multi-file-commit-panel-success-message { position: absolute; top: 61px; diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index d6a6bc7d4a1..737942f3eb2 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,7 +1,11 @@ class Admin::DashboardController < Admin::ApplicationController include CountHelper + COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest, + Note, Snippet, Key, Milestone].freeze + def index + @counts = Gitlab::Database::Count.approximate_counts(COUNTED_ITEMS) @projects = Project.order_id_desc.without_deleted.with_route.limit(10) @users = User.order_id_desc.limit(10) @groups = Group.order_id_desc.with_route.limit(10) diff --git a/app/controllers/groups/shared_projects_controller.rb b/app/controllers/groups/shared_projects_controller.rb new file mode 100644 index 00000000000..f2f835767e0 --- /dev/null +++ b/app/controllers/groups/shared_projects_controller.rb @@ -0,0 +1,31 @@ +module Groups + class SharedProjectsController < Groups::ApplicationController + respond_to :json + before_action :group + skip_cross_project_access_check :index + + def index + shared_projects = GroupProjectsFinder.new( + group: group, + current_user: current_user, + params: finder_params, + options: { only_shared: true } + ).execute + serializer = GroupChildSerializer.new(current_user: current_user) + .with_pagination(request, response) + + render json: serializer.represent(shared_projects) + end + + private + + def finder_params + @finder_params ||= begin + # Make the `search` param consistent for the frontend, + # which will be using `filter`. + params[:search] ||= params[:filter] if params[:filter] + params.permit(:sort, :search) + end + end + end +end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index ac71f72e624..9f5ad23a20f 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -93,8 +93,6 @@ class ProfilesController < Profiles::ApplicationController :linkedin, :location, :name, - :password, - :password_confirmation, :public_email, :skype, :twitter, diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 03a1fe5aaec..1e8f3ac1433 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -9,6 +9,7 @@ class Projects::ClustersController < Projects::ApplicationController before_action :authorize_update_cluster!, only: [:update] before_action :authorize_admin_cluster!, only: [:destroy] before_action :update_applications_status, only: [:status] + helper_method :token_in_session STATUS_POLLING_INTERVAL = 10_000 @@ -190,8 +191,7 @@ class Projects::ClustersController < Projects::ApplicationController end def token_in_session - @token_in_session ||= - session[GoogleApi::CloudPlatform::Client.session_key_for_token] + session[GoogleApi::CloudPlatform::Client.session_key_for_token] end def expires_at_in_session diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 52d528e816e..0821362f5df 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -7,6 +7,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize] before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics] before_action :verify_api_request!, only: :terminal_websocket_authorize + before_action :expire_etag_cache, only: [:index] def index @environments = project.environments @@ -148,6 +149,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController Gitlab::Workhorse.verify_api_request!(request.headers) end + def expire_etag_cache + return if request.format.json? + + # this forces to reload json content + Gitlab::EtagCaching::Store.new.tap do |store| + store.touch(project_environments_path(project, format: :json)) + end + end + def environment_params params.require(:environment).permit(:name, :external_url) end diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb index 67d4ea2ca8f..29632bef7e5 100644 --- a/app/controllers/projects/merge_requests/application_controller.rb +++ b/app/controllers/projects/merge_requests/application_controller.rb @@ -24,6 +24,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont :source_branch, :source_project_id, :state_event, + :squash, :target_branch, :target_project_id, :task_num, diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 62b739918e6..507a07c6e1b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -253,7 +253,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def merge_params_attributes - [:should_remove_source_branch, :commit_message] + [:should_remove_source_branch, :commit_message, :squash] end def merge_when_pipeline_succeeds_active? @@ -282,7 +282,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha - @merge_request.update(merge_error: nil) + @merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false)) if params[:merge_when_pipeline_succeeds].present? return :failed unless @merge_request.actual_head_pipeline diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 1b0751f48c5..242e6491456 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -14,6 +14,8 @@ class Projects::WikisController < Projects::ApplicationController def show @page = @project_wiki.find_page(params[:id], params[:version_id]) + view_param = @project_wiki.empty? ? params[:view] : 'create' + if @page render 'show' elsif file = @project_wiki.find_file(params[:id], params[:version_id]) @@ -26,12 +28,12 @@ class Projects::WikisController < Projects::ApplicationController disposition: 'inline', filename: file.name ) - else - return render('empty') unless can?(current_user, :create_wiki, @project) - + elsif can?(current_user, :create_wiki, @project) && view_param == 'create' @page = build_page(title: params[:id]) render 'edit' + else + render 'empty' end end diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb index f73cf8adb4d..b6bdb2b7b0f 100644 --- a/app/finders/group_projects_finder.rb +++ b/app/finders/group_projects_finder.rb @@ -39,25 +39,15 @@ class GroupProjectsFinder < ProjectsFinder end def collection_with_user - if group.users.include?(current_user) - if only_shared? - [shared_projects] - elsif only_owned? - [owned_projects] - else - [shared_projects, owned_projects] - end + if only_shared? + [shared_projects.public_or_visible_to_user(current_user)] + elsif only_owned? + [owned_projects.public_or_visible_to_user(current_user)] else - if only_shared? - [shared_projects.public_or_visible_to_user(current_user)] - elsif only_owned? - [owned_projects.public_or_visible_to_user(current_user)] - else - [ - owned_projects.public_or_visible_to_user(current_user), - shared_projects.public_or_visible_to_user(current_user) - ] - end + [ + owned_projects.public_or_visible_to_user(current_user), + shared_projects.public_or_visible_to_user(current_user) + ] end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index b948e431882..adc423af9e1 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -204,7 +204,7 @@ module ApplicationSettingsHelper :pages_domain_verification_enabled, :password_authentication_enabled_for_web, :password_authentication_enabled_for_git, - :performance_bar_allowed_group_id, + :performance_bar_allowed_group_path, :performance_bar_enabled, :plantuml_enabled, :plantuml_url, diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb index 24ee62e68ba..5cd98f40f78 100644 --- a/app/helpers/count_helper.rb +++ b/app/helpers/count_helper.rb @@ -1,5 +1,9 @@ module CountHelper - def approximate_count_with_delimiters(model) - number_with_delimiter(Gitlab::Database::Count.approximate_count(model)) + def approximate_count_with_delimiters(count_data, model) + count = count_data[model] + + raise "Missing model #{model} from count data" unless count + + number_with_delimiter(count) end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index c19c5b9cc82..74251c260f0 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -97,8 +97,9 @@ module MergeRequestsHelper { merge_when_pipeline_succeeds: true, should_remove_source_branch: true, - sha: merge_request.diff_head_sha - }.merge(merge_params_ee(merge_request)) + sha: merge_request.diff_head_sha, + squash: merge_request.squash + } end def tab_link_for(merge_request, tab, options = {}, &block) @@ -149,8 +150,4 @@ module MergeRequestsHelper current_user.fork_of(project) end end - - def merge_params_ee(merge_request) - {} - end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 7754c34d6f0..a84a39235d8 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -11,6 +11,7 @@ module NavHelper class_name = page_gutter_class class_name << 'page-with-contextual-sidebar' if defined?(@left_sidebar) && @left_sidebar class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar + class_name -= ['right-sidebar-expanded'] if defined?(@right_sidebar) && !@right_sidebar class_name end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index fa54eafd3a3..55078e1a2d2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -257,6 +257,9 @@ module ProjectsHelper if project.builds_enabled? && can?(current_user, :read_pipeline, project) nav_tabs << :pipelines + end + + if can?(current_user, :read_environment, project) || can?(current_user, :read_cluster, project) nav_tabs << :operations end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index e8ccb320fae..b12f7a2c83f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -230,6 +230,7 @@ class ApplicationSetting < ActiveRecord::Base after_commit do reset_memoized_terms end + after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') } def self.defaults { @@ -386,31 +387,6 @@ class ApplicationSetting < ActiveRecord::Base super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) }) end - def performance_bar_allowed_group_id=(group_full_path) - group_full_path = nil if group_full_path.blank? - - if group_full_path.nil? - if group_full_path != performance_bar_allowed_group_id - super(group_full_path) - Gitlab::PerformanceBar.expire_allowed_user_ids_cache - end - - return - end - - group = Group.find_by_full_path(group_full_path) - - if group - if group.id != performance_bar_allowed_group_id - super(group.id) - Gitlab::PerformanceBar.expire_allowed_user_ids_cache - end - else - super(nil) - Gitlab::PerformanceBar.expire_allowed_user_ids_cache - end - end - def performance_bar_allowed_group Group.find_by_id(performance_bar_allowed_group_id) end @@ -420,15 +396,6 @@ class ApplicationSetting < ActiveRecord::Base performance_bar_allowed_group_id.present? end - # - If `enable` is true, we early return since the actual attribute that holds - # the enabling/disabling is `performance_bar_allowed_group_id` - # - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil` - def performance_bar_enabled=(enable) - return if Gitlab::Utils.to_boolean(enable) - - self.performance_bar_allowed_group_id = nil - end - # Choose one of the available repository storage options. Currently all have # equal weighting. def pick_repository_storage @@ -506,4 +473,8 @@ class ApplicationSetting < ActiveRecord::Base errors.add(:terms, "You need to set terms to be enforced") unless terms.present? end + + def expire_performance_bar_allowed_user_ids_cache + Gitlab::PerformanceBar.expire_allowed_user_ids_cache + end end diff --git a/app/models/concerns/batch_destroy_dependent_associations.rb b/app/models/concerns/batch_destroy_dependent_associations.rb new file mode 100644 index 00000000000..353ee2e73d0 --- /dev/null +++ b/app/models/concerns/batch_destroy_dependent_associations.rb @@ -0,0 +1,28 @@ +# Provides a way to work around Rails issue where dependent objects are all +# loaded into memory before destroyed: https://github.com/rails/rails/issues/22510. +# +# This concern allows an ActiveRecord module to destroy all its dependent +# associations in batches. The idea is borrowed from https://github.com/thisismydesign/batch_dependent_associations. +# +# The differences here with that gem: +# +# 1. We allow excluding certain associations. +# 2. We don't need to support delete_all since we can use the EachBatch concern. +module BatchDestroyDependentAssociations + extend ActiveSupport::Concern + + DEPENDENT_ASSOCIATIONS_BATCH_SIZE = 1000 + + def dependent_associations_to_destroy + self.class.reflect_on_all_associations(:has_many).select { |assoc| assoc.options[:dependent] == :destroy } + end + + def destroy_dependent_associations_in_batches(exclude: []) + dependent_associations_to_destroy.each do |association| + next if exclude.include?(association.name) + + # rubocop:disable GitlabSecurity/PublicSend + public_send(association.name).find_each(batch_size: DEPENDENT_ASSOCIATIONS_BATCH_SIZE, &:destroy) + end + end +end diff --git a/app/models/concerns/diff_file.rb b/app/models/concerns/diff_file.rb new file mode 100644 index 00000000000..72332072012 --- /dev/null +++ b/app/models/concerns/diff_file.rb @@ -0,0 +1,9 @@ +module DiffFile + extend ActiveSupport::Concern + + def to_hash + keys = Gitlab::Git::Diff::SERIALIZE_KEYS - [:diff] + + as_json(only: keys).merge(diff: diff).with_indifferent_access + end +end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 616a626419b..d752d5bcdee 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -3,6 +3,7 @@ # A note of this type can be resolvable. class DiffNote < Note include NoteOnDiff + include Gitlab::Utils::StrongMemoize NOTEABLE_TYPES = %w(MergeRequest Commit).freeze @@ -12,7 +13,6 @@ class DiffNote < Note validates :original_position, presence: true validates :position, presence: true - validates :diff_line, presence: true, if: :on_text? validates :line_code, presence: true, line_code: true, if: :on_text? validates :noteable_type, inclusion: { in: NOTEABLE_TYPES } validate :positions_complete @@ -23,6 +23,7 @@ class DiffNote < Note before_validation :update_position, on: :create, if: :on_text? before_validation :set_line_code, if: :on_text? after_save :keep_around_commits + after_commit :create_diff_file, on: :create def discussion_class(*) DiffDiscussion @@ -53,21 +54,25 @@ class DiffNote < Note position.position_type == "image" end + def create_diff_file + return unless should_create_diff_file? + + diff_file = fetch_diff_file + diff_line = diff_file.line_for_position(self.original_position) + + creation_params = diff_file.diff.to_hash + .except(:too_large) + .merge(diff: diff_file.diff_hunk(diff_line)) + + create_note_diff_file(creation_params) + end + def diff_file - @diff_file ||= - begin - if created_at_diff?(noteable.diff_refs) - # We're able to use the already persisted diffs (Postgres) if we're - # presenting a "current version" of the MR discussion diff. - # So no need to make an extra Gitaly diff request for it. - # As an extra benefit, the returned `diff_file` already - # has `highlighted_diff_lines` data set from Redis on - # `Diff::FileCollection::MergeRequestDiff`. - noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first - else - original_position.diff_file(self.project.repository) - end - end + strong_memoize(:diff_file) do + enqueue_diff_file_creation_job if should_create_diff_file? + + fetch_diff_file + end end def diff_line @@ -98,6 +103,38 @@ class DiffNote < Note private + def enqueue_diff_file_creation_job + # Avoid enqueuing multiple file creation jobs at once for a note (i.e. + # parallel calls to `DiffNote#diff_file`). + lease = Gitlab::ExclusiveLease.new("note_diff_file_creation:#{id}", timeout: 1.hour.to_i) + return unless lease.try_obtain + + CreateNoteDiffFileWorker.perform_async(id) + end + + def should_create_diff_file? + on_text? && note_diff_file.nil? && self == discussion.first_note + end + + def fetch_diff_file + if note_diff_file + diff = Gitlab::Git::Diff.new(note_diff_file.to_hash) + Gitlab::Diff::File.new(diff, + repository: project.repository, + diff_refs: original_position.diff_refs) + elsif created_at_diff?(noteable.diff_refs) + # We're able to use the already persisted diffs (Postgres) if we're + # presenting a "current version" of the MR discussion diff. + # So no need to make an extra Gitaly diff request for it. + # As an extra benefit, the returned `diff_file` already + # has `highlighted_diff_lines` data set from Redis on + # `Diff::FileCollection::MergeRequestDiff`. + noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first + else + original_position.diff_file(self.project.repository) + end + end + def supported? for_commit? || self.noteable.has_complete_diff_refs? end diff --git a/app/models/event.rb b/app/models/event.rb index 741a84194e2..ac0b1c7b27c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -40,6 +40,7 @@ class Event < ActiveRecord::Base ).freeze RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour + REPOSITORY_UPDATED_AT_INTERVAL = 5.minutes delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true @@ -391,6 +392,7 @@ class Event < ActiveRecord::Base def set_last_repository_updated_at Project.unscoped.where(id: project_id) + .where("last_repository_updated_at < ? OR last_repository_updated_at IS NULL", REPOSITORY_UPDATED_AT_INTERVAL.ago) .update_all(last_repository_updated_at: created_at) end diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index 189942c5ad8..f7f930e86ed 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -24,12 +24,9 @@ class InternalId < ActiveRecord::Base # # The operation locks the record and gathers a `ROW SHARE` lock (in PostgreSQL). # As such, the increment is atomic and safe to be called concurrently. - # - # If a `maximum_iid` is passed in, this overrides the incremented value if it's - # greater than that. This can be used to correct the increment value if necessary. - def increment_and_save!(maximum_iid) + def increment_and_save! lock! - self.last_value = [(last_value || 0) + 1, (maximum_iid || 0) + 1].max + self.last_value = (last_value || 0) + 1 save! last_value end @@ -93,16 +90,7 @@ class InternalId < ActiveRecord::Base # and increment its last value # # Note this will acquire a ROW SHARE lock on the InternalId record - - # Note we always calculate the maximum iid present here and - # pass it in to correct the InternalId entry if it's last_value is off. - # - # This can happen in a transition phase where both `AtomicInternalId` and - # `NonatomicInternalId` code runs (e.g. during a deploy). - # - # This is subject to be cleaned up with the 10.8 release: - # https://gitlab.com/gitlab-org/gitlab-ce/issues/45389. - (lookup || create_record).increment_and_save!(maximum_iid) + (lookup || create_record).increment_and_save! end end @@ -128,15 +116,11 @@ class InternalId < ActiveRecord::Base InternalId.create!( **scope, usage: usage_value, - last_value: maximum_iid + last_value: init.call(subject) || 0 ) end rescue ActiveRecord::RecordNotUnique lookup end - - def maximum_iid - @maximum_iid ||= init.call(subject) || 0 - end end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 9c4384a6e42..bc97fc3a5d9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1140,4 +1140,11 @@ class MergeRequest < ActiveRecord::Base maintainer_push_possible? && Ability.allowed?(user, :push_code, source_project) end + + def squash_in_progress? + # The source project can be deleted + return false unless source_project + + source_project.repository.squash_in_progress?(id) + end end diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb index 1199ff5af22..cd8ba6b904d 100644 --- a/app/models/merge_request_diff_file.rb +++ b/app/models/merge_request_diff_file.rb @@ -1,5 +1,6 @@ class MergeRequestDiffFile < ActiveRecord::Base include Gitlab::EncodingHelper + include DiffFile belongs_to :merge_request_diff @@ -12,10 +13,4 @@ class MergeRequestDiffFile < ActiveRecord::Base def diff binary? ? super.unpack('m0').first : super end - - def to_hash - keys = Gitlab::Git::Diff::SERIALIZE_KEYS - [:diff] - - as_json(only: keys).merge(diff: diff).with_indifferent_access - end end diff --git a/app/models/note.rb b/app/models/note.rb index 109405d3f17..02f7a9b1e4f 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -63,6 +63,7 @@ class Note < ActiveRecord::Base has_many :todos has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :system_note_metadata + has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id delegate :gfm_reference, :local_reference, to: :noteable delegate :name, to: :project, prefix: true @@ -100,7 +101,8 @@ class Note < ActiveRecord::Base scope :inc_author_project, -> { includes(:project, :author) } scope :inc_author, -> { includes(:author) } scope :inc_relations_for_view, -> do - includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata) + includes(:project, :author, :updated_by, :resolved_by, :award_emoji, + :system_note_metadata, :note_diff_file) end scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) } diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb new file mode 100644 index 00000000000..e688018a6d9 --- /dev/null +++ b/app/models/note_diff_file.rb @@ -0,0 +1,7 @@ +class NoteDiffFile < ActiveRecord::Base + include DiffFile + + belongs_to :diff_note, inverse_of: :note_diff_file + + validates :diff_note, presence: true +end diff --git a/app/models/project.rb b/app/models/project.rb index 0fe9f8880b4..e275ac4dc6f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -24,6 +24,7 @@ class Project < ActiveRecord::Base include ChronicDurationAttribute include FastDestroyAll::Helpers include WithUploads + include BatchDestroyDependentAssociations extend Gitlab::ConfigHelper diff --git a/app/models/repository.rb b/app/models/repository.rb index 0e1bf11d7c0..82cf47ba04e 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -957,6 +957,22 @@ class Repository remote_branch: merge_request.target_branch) end + def blob_data_at(sha, path) + blob = blob_at(sha, path) + return unless blob + + blob.load_all_data! + blob.data + end + + def squash(user, merge_request) + raw.squash(user, merge_request.id, branch: merge_request.target_branch, + start_sha: merge_request.diff_start_sha, + end_sha: merge_request.diff_head_sha, + author: merge_request.author, + message: merge_request.title) + end + private # TODO Generice finder, later split this on finders by Ref or Oid @@ -971,14 +987,6 @@ class Repository ::Commit.new(commit, @project) if commit end - def blob_data_at(sha, path) - blob = blob_at(sha, path) - return unless blob - - blob.load_all_data! - blob.data - end - def cache @cache ||= Gitlab::RepositoryCache.new(self) end diff --git a/app/models/service.rb b/app/models/service.rb index 831c2ea1141..1d259bcfec7 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -206,10 +206,11 @@ class Service < ActiveRecord::Base args.each do |arg| class_eval %{ def #{arg}? + # '!!' is used because nil or empty string is converted to nil if Gitlab.rails5? - !ActiveModel::Type::Boolean::FALSE_VALUES.include?(#{arg}) + !!ActiveRecord::Type::Boolean.new.cast(#{arg}) else - ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg}) + !!ActiveRecord::Type::Boolean.new.type_cast_from_database(#{arg}) end end } diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb index 15ec0f89bb2..ee150eefd9e 100644 --- a/app/serializers/group_child_entity.rb +++ b/app/serializers/group_child_entity.rb @@ -31,7 +31,7 @@ class GroupChildEntity < Grape::Entity end # Project only attributes - expose :star_count, + expose :star_count, :archived, if: lambda { |_instance, _options| project? } # Group only attributes diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index d0165c148eb..141070aef45 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -10,6 +10,7 @@ class MergeRequestWidgetEntity < IssuableEntity expose :merge_when_pipeline_succeeds expose :source_branch expose :source_project_id + expose :squash expose :target_branch expose :target_project_id expose :allow_maintainer_to_push diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb index d6d3a661dab..e70445cfb67 100644 --- a/app/services/application_settings/update_service.rb +++ b/app/services/application_settings/update_service.rb @@ -3,6 +3,10 @@ module ApplicationSettings def execute update_terms(@params.delete(:terms)) + if params.key?(:performance_bar_allowed_group_path) + params[:performance_bar_allowed_group_id] = performance_bar_allowed_group_id + end + @application_setting.update(@params) end @@ -18,5 +22,13 @@ module ApplicationSettings ApplicationSetting::Term.create(terms: terms) @application_setting.reset_memoized_terms end + + def performance_bar_allowed_group_id + performance_bar_enabled = !params.key?(:performance_bar_enabled) || params.delete(:performance_bar_enabled) + group_full_path = params.delete(:performance_bar_allowed_group_path) + return nil unless Gitlab::Utils.to_boolean(performance_bar_enabled) + + Group.find_by_full_path(group_full_path)&.id if group_full_path.present? + end end end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 2209a60a840..126da891c78 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -34,6 +34,19 @@ module MergeRequests handle_merge_error(log_message: e.message, save_message_on_model: true) end + def source + return merge_request.diff_head_sha unless merge_request.squash + + squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute(merge_request) + + case squash_result[:status] + when :success + squash_result[:squash_sha] + when :error + raise ::MergeRequests::MergeService::MergeError, squash_result[:message] + end + end + private def error_check! @@ -116,9 +129,5 @@ module MergeRequests def merge_request_info merge_request.to_reference(full: true) end - - def source - @source ||= @merge_request.diff_head_sha - end end end diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb new file mode 100644 index 00000000000..a40fb2786bd --- /dev/null +++ b/app/services/merge_requests/squash_service.rb @@ -0,0 +1,28 @@ +module MergeRequests + class SquashService < MergeRequests::WorkingCopyBaseService + def execute(merge_request) + @merge_request = merge_request + @repository = target_project.repository + + squash || error('Failed to squash. Should be done manually.') + end + + def squash + if merge_request.commits_count < 2 + return success(squash_sha: merge_request.diff_head_sha) + end + + if merge_request.squash_in_progress? + return error('Squash task canceled: another squash is already in progress.') + end + + squash_sha = repository.squash(current_user, merge_request) + + success(squash_sha: squash_sha) + rescue => e + log_error("Failed to squash merge request #{merge_request.to_reference(full: true)}:") + log_error(e.message) + false + end + end +end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 077d27c5836..de0125ed0dd 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -137,7 +137,13 @@ module Projects trash_repositories! - project.team.truncate + # Rails attempts to load all related records into memory before + # destroying: https://github.com/rails/rails/issues/22510 + # This ensures we delete records in batches. + # + # Exclude container repositories because its before_destroy would be + # called multiple times, and it doesn't destroy any database records. + project.destroy_dependent_associations_in_batches(exclude: [:container_repositories]) project.destroy! end end diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index f2a8afccdeb..5bdca26a584 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -11,7 +11,8 @@ module ObjectStorage ObjectStorageUnavailable = Class.new(StandardError) DIRECT_UPLOAD_TIMEOUT = 4.hours - TMP_UPLOAD_PATH = 'tmp/upload'.freeze + DIRECT_UPLOAD_EXPIRE_OFFSET = 15.minutes + TMP_UPLOAD_PATH = 'tmp/uploads'.freeze module Store LOCAL = 1 @@ -174,11 +175,12 @@ module ObjectStorage id = [CarrierWave.generate_cache_id, SecureRandom.hex].join('-') upload_path = File.join(TMP_UPLOAD_PATH, id) connection = ::Fog::Storage.new(self.object_store_credentials) - expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT + expire_at = Time.now + DIRECT_UPLOAD_TIMEOUT + DIRECT_UPLOAD_EXPIRE_OFFSET options = { 'Content-Type' => 'application/octet-stream' } { ID: id, + Timeout: DIRECT_UPLOAD_TIMEOUT, GetURL: connection.get_object_url(remote_store_path, upload_path, expire_at), DeleteURL: connection.delete_object_url(remote_store_path, upload_path, expire_at), StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options) diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml index 8001b42e2f9..030e8610b47 100644 --- a/app/views/admin/application_settings/_performance_bar.html.haml +++ b/app/views/admin/application_settings/_performance_bar.html.haml @@ -9,8 +9,8 @@ = f.check_box :performance_bar_enabled Enable the Performance Bar .form-group.row - = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'col-form-label col-sm-2' + = f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'col-form-label col-sm-2' .col-sm-10 - = f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path + = f.text_field :performance_bar_allowed_group_path, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml index 9d05a5aa234..edd8e5e9eb8 100644 --- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml +++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml @@ -9,7 +9,7 @@ = f.label :mirror_available do = f.check_box :mirror_available Allow mirrors to be setup for projects - %span.help-block + %span.form-text.text-muted If disabled, only admins will be able to setup mirrors in projects. = link_to icon('question-circle'), help_page_path('workflow/repository_mirroring') diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml index 4d74568d69a..83a30504222 100644 --- a/app/views/admin/application_settings/_signin.html.haml +++ b/app/views/admin/application_settings/_signin.html.haml @@ -23,7 +23,7 @@ must be used to authenticate. - if omniauth_enabled? && button_based_providers.any? .form-group.row - = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' + = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'col-form-label col-sm-2' = hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]' .col-sm-10 .btn-group{ data: { toggle: 'buttons' } } diff --git a/app/views/admin/application_settings/_terms.html.haml b/app/views/admin/application_settings/_terms.html.haml index 32b060972ec..44bf7b65a8e 100644 --- a/app/views/admin/application_settings/_terms.html.haml +++ b/app/views/admin/application_settings/_terms.html.haml @@ -8,7 +8,7 @@ = f.label :enforce_terms do = f.check_box :enforce_terms = _("Require all users to accept Terms of Service when they access GitLab.") - .help-block + .form-text.text-muted = _("When enabled, users cannot use GitLab until the terms have been accepted.") .form-group .col-sm-12 @@ -16,7 +16,7 @@ = _("Terms of Service Agreement") .col-sm-12 = f.text_area :terms, class: 'form-control', rows: 8 - .help-block + .form-text.text-muted = _("Markdown enabled") = f.submit _("Save changes"), class: "btn btn-success" diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml index c37a89237f0..0f2524047e3 100644 --- a/app/views/admin/application_settings/_visibility_and_access.html.haml +++ b/app/views/admin/application_settings/_visibility_and_access.html.haml @@ -27,7 +27,7 @@ .form-check = level %span.form-text.text-muted#restricted-visibility-help - Selected levels cannot be used by non-admin users for projects or snippets. + Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users. .form-group.row = f.label :import_sources, class: 'col-form-label col-sm-2' diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 3df4ce93fa8..3cdeb103bb8 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -12,7 +12,7 @@ = link_to admin_projects_path do %h3.text-center Projects: - = approximate_count_with_delimiters(Project) + = approximate_count_with_delimiters(@counts, Project) %hr = link_to('New project', new_project_path, class: "btn btn-new") .col-sm-4 @@ -21,7 +21,7 @@ = link_to admin_users_path do %h3.text-center Users: - = approximate_count_with_delimiters(User) + = approximate_count_with_delimiters(@counts, User) = render_if_exists 'users_statistics' %hr = link_to 'New user', new_admin_user_path, class: "btn btn-new" @@ -31,7 +31,7 @@ = link_to admin_groups_path do %h3.text-center Groups: - = approximate_count_with_delimiters(Group) + = approximate_count_with_delimiters(@counts, Group) %hr = link_to 'New group', new_admin_group_path, class: "btn btn-new" .row @@ -42,31 +42,31 @@ %p Forks %span.light.float-right - = approximate_count_with_delimiters(ForkedProjectLink) + = approximate_count_with_delimiters(@counts, ForkedProjectLink) %p Issues %span.light.float-right - = approximate_count_with_delimiters(Issue) + = approximate_count_with_delimiters(@counts, Issue) %p Merge Requests %span.light.float-right - = approximate_count_with_delimiters(MergeRequest) + = approximate_count_with_delimiters(@counts, MergeRequest) %p Notes %span.light.float-right - = approximate_count_with_delimiters(Note) + = approximate_count_with_delimiters(@counts, Note) %p Snippets %span.light.float-right - = approximate_count_with_delimiters(Snippet) + = approximate_count_with_delimiters(@counts, Snippet) %p SSH Keys %span.light.float-right - = approximate_count_with_delimiters(Key) + = approximate_count_with_delimiters(@counts, Key) %p Milestones %span.light.float-right - = approximate_count_with_delimiters(Milestone) + = approximate_count_with_delimiters(@counts, Milestone) %p Active Users %span.light.float-right diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 5ec612d0c72..6d75ccd5add 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -13,7 +13,7 @@ .card .card-header Group info: - %ul.well-list + %ul.content-list %li .avatar-container.s60 = group_icon(@group, class: "avatar s60") @@ -64,7 +64,7 @@ Projects %span.badge.badge-pill #{@group.projects.count} - %ul.well-list + %ul.content-list - @projects.each do |project| %li %strong @@ -82,7 +82,7 @@ Projects shared with #{@group.name} %span.badge.badge-pill #{@group.shared_projects.count} - %ul.well-list + %ul.content-list - @group.shared_projects.sort_by(&:name).each do |project| %li %strong @@ -118,7 +118,7 @@ %span.badge.badge-pill= @group.members.size .float-right = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm" - %ul.well-list.group-users-list.content-list.members-list + %ul.content-list.group-users-list.content-list.members-list = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } .card-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 29ec712b6b7..0a22a142858 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -22,7 +22,7 @@ .card .card-header Project info: - %ul.well-list + %ul.content-list %li %span.light Name: %strong @@ -166,7 +166,7 @@ .float-right = link_to admin_group_path(@group), class: 'btn btn-sm' do = icon('pencil-square-o', text: 'Manage access') - %ul.well-list.content-list.members-list + %ul.content-list.members-list = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } .card-footer = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' @@ -180,7 +180,7 @@ %span.badge.badge-pill= @project.users.size .float-right = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm" - %ul.well-list.project_members.content-list.members-list + %ul.content-list.project_members.members-list = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } .card-footer = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml index af22652e07c..4fcb9aad343 100644 --- a/app/views/admin/users/_profile.html.haml +++ b/app/views/admin/users/_profile.html.haml @@ -1,7 +1,7 @@ .card .card-header Profile - %ul.well-list + %ul.content-list %li %span.light Member since %strong= user.created_at.to_s(:medium) diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 469a7bd9715..cf50d45f755 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -4,7 +4,7 @@ - if @user.groups.any? .card .card-header Group projects - %ul.card-body-list + %ul.hover-list - @user.group_members.includes(:source).each do |group_member| - group = group_member.group %li.group_member @@ -28,7 +28,7 @@ .col-md-6 .card .card-header Joined projects (#{@joined_projects.count}) - %ul.card-body-list + %ul.hover-list - @joined_projects.sort_by(&:full_name).each do |project| - member = project.team.find_member(@user.id) %li.project_member diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index a74fcea65d8..b0562226f5f 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -8,7 +8,7 @@ .card .card-header = @user.name - %ul.well-list + %ul.content-list %li = image_tag avatar_icon_for_user(@user, 60), class: "avatar s60" %li @@ -21,7 +21,7 @@ .card .card-header Account: - %ul.well-list + %ul.content-list %li %span.light Name: %strong= @user.name diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index f85f5c5be88..85f2d00bde3 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -14,7 +14,7 @@ - if event.push_with_commits? .event-body - %ul.well-list.event_commits + %ul.content-list.event_commits = render "events/commit", project: project, event: event - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user) diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 96ed63937fa..cae2df4699e 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,78 +1,39 @@ - breadcrumb_title "General Settings" - @content_class = "limit-container-width" unless fluid_layout - -.card.prepend-top-default - .card-header - Group settings - .card-body - = form_for @group, html: { multipart: true, class: "gl-show-field-errors" }, authenticity_token: true do |f| - = form_errors(@group) - = render 'shared/group_form', f: f - - .form-group.row - .offset-sm-2.col-sm-10 - .avatar-container.s160 - = group_icon(@group, alt: '', class: 'avatar group-avatar s160') - %p.light - - if @group.avatar? - You can change the group avatar here - - else - You can upload a group avatar here - = render 'shared/choose_group_avatar_button', f: f - - if @group.avatar? - %hr - = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _("Avatar will be removed. Are you sure?")}, method: :delete, class: "btn btn-danger btn-inverted" - - = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group - - .form-group.row - .offset-sm-2.col-sm-10 - = render 'shared/allow_request_access', form: f - - .form-group.row - %label.col-form-label.col-sm-2 - = s_("GroupSettings|Share with group lock") - .col-sm-10 - .form-check - = f.label :share_with_group_lock do - = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group) - %strong - - group_link = link_to @group.name, group_path(@group) - = s_("GroupSettings|Prevent sharing a project within %{group} with other groups").html_safe % { group: group_link } - %br - %span.descr= share_with_group_lock_help_text(@group) - - = render 'group_admin_settings', f: f - - .form-actions - = f.submit 'Save group', class: "btn btn-save" - -.card.bg-danger - .card-header Remove group - .card-body - = form_tag(@group, method: :delete) do - %p - Removing group will cause all child projects and resources to be removed. - %br - %strong Removed group can not be restored! - - .form-actions - = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) } - -- if supports_nested_groups? - .card.bg-warning - .card-header Transfer group - .card-body - = form_for @group, url: transfer_group_path(@group), method: :put do |f| - .form-group - = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: "Search groups", data: { data: parent_group_options(@group) } }) - = hidden_field_tag 'new_parent_group_id' - - %ul - %li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}. - %li You can only transfer the group to a group you manage. - %li You will need to update your local repositories to point to the new location. - %li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility. - = f.submit 'Transfer group', class: "btn btn-warning" +- expanded = Rails.env.test? + + +%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('General') + %button.btn.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + = _('Update your group name, description, avatar, and other general settings.') + .settings-content + = render 'groups/settings/general' + +%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('Permissions') + %button.btn.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + = _('Enable or disable certain group features and choose access levels.') + .settings-content + = render 'groups/settings/permissions' + +%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('Advanced') + %button.btn.js-settings-toggle{ type: 'button' } + = expanded ? _('Collapse') : _('Expand') + %p + = _('Perform advanced options such as changing path, transferring, or removing the group.') + .settings-content + = render 'groups/settings/advanced' = render 'shared/confirm_modal', phrase: @group.path diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index cd07b95155c..ba186875a86 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -8,7 +8,7 @@ .controls = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do New project - %ul.well-list + %ul.content-list - @projects.each do |project| %li .list-item-name diff --git a/app/views/groups/runners/_runner.html.haml b/app/views/groups/runners/_runner.html.haml index 76650a961d6..3f89b04a5fc 100644 --- a/app/views/groups/runners/_runner.html.haml +++ b/app/views/groups/runners/_runner.html.haml @@ -8,13 +8,13 @@ = link_to edit_group_runner_path(@group, runner) do = icon('edit') - .pull-right + .float-right - if runner.active? = link_to _('Pause'), pause_group_runner_path(@group, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: _("Are you sure?") } - else = link_to _('Resume'), resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-success btn-sm' = link_to _('Remove Runner'), group_runner_path(@group, runner), data: { confirm: _("Are you sure?") }, method: :delete, class: 'btn btn-danger btn-sm' - .pull-right + .float-right %small.light \##{runner.id} - if runner.description.present? diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml new file mode 100644 index 00000000000..b7c673db705 --- /dev/null +++ b/app/views/groups/settings/_advanced.html.haml @@ -0,0 +1,49 @@ +.sub-section + %h4.warning-title Change group path + = form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f| + = form_errors(@group) + .form-group + %p + Changing group path can have unintended side effects. + = succeed '.' do + = link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank' + + .input-group.gl-field-error-anchor + .group-root-path.input-group-prepend.has-tooltip{ title: group_path(@group), :'data-placement' => 'bottom' } + .input-group-text + %span>= root_url + - if parent + %strong= parent.full_path + '/' + = f.hidden_field :parent_id + = f.text_field :path, placeholder: 'open-source', class: 'form-control', + autofocus: local_assigns[:autofocus] || false, required: true, + pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, + title: 'Please choose a group path with no special characters.', + "data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}" + + = f.submit 'Change group path', class: 'btn btn-warning' + +.sub-section + %h4.danger-title Remove group + = form_tag(@group, method: :delete) do + %p + Removing group will cause all child projects and resources to be removed. + %br + %strong Removed group can not be restored! + + = button_to 'Remove group', '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(@group) } + +- if supports_nested_groups? + .sub-section + %h4.warning-title Transfer group + = form_for @group, url: transfer_group_path(@group), method: :put do |f| + .form-group + = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } }) + = hidden_field_tag 'new_parent_group_id' + + %ul + %li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}. + %li You can only transfer the group to a group you manage. + %li You will need to update your local repositories to point to the new location. + %li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility. + = f.submit 'Transfer group', class: 'btn btn-warning' diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml new file mode 100644 index 00000000000..64786d24266 --- /dev/null +++ b/app/views/groups/settings/_general.html.haml @@ -0,0 +1,38 @@ += form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f| + = form_errors(@group) + + %fieldset + .row + .form-group.col-md-9 + = f.label :name, class: 'label-light' do + Group name + = f.text_field :name, class: 'form-control' + + .form-group.col-md-3 + = f.label :id, class: 'label-light' do + Group ID + = f.text_field :id, class: 'form-control', readonly: true + + .form-group + = f.label :description, class: 'label-light' do + Group description + %span.light (optional) + = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250 + + = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group + + .form-group.row + .col-sm-12 + .avatar-container.s160 + = group_icon(@group, alt: '', class: 'avatar group-avatar s160') + %p.light + - if @group.avatar? + You can change the group avatar here + - else + You can upload a group avatar here + = render 'shared/choose_group_avatar_button', f: f + - if @group.avatar? + %hr + = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-danger btn-inverted' + + = f.submit 'Save group', class: 'btn btn-success' diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml new file mode 100644 index 00000000000..15a5ecf791c --- /dev/null +++ b/app/views/groups/settings/_permissions.html.haml @@ -0,0 +1,28 @@ += form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f| + = form_errors(@group) + + %fieldset + = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group + + .form-group.row + .offset-sm-2.col-sm-10 + = render 'shared/allow_request_access', form: f + + .form-group.row + %label.col-form-label.col-sm-2 + = s_('GroupSettings|Share with group lock') + .col-sm-10 + .form-check + = f.label :share_with_group_lock do + = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group) + %strong + - group_link = link_to @group.name, group_path(@group) + = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link } + %br + %span.descr= share_with_group_lock_help_text(@group) + + = render 'groups/group_admin_settings', f: f + + = render_if_exists 'groups/member_lock_setting', f: f, group: @group + + = f.submit 'Save group', class: 'btn btn-success' diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index 082e1b7befa..383d955d71f 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -6,7 +6,7 @@ %section.settings#secret-variables.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - = _('Secret variables') + = _('Variables') = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer' %button.btn.btn-default.js-settings-toggle{ type: "button" } = expanded ? _('Collapse') : _('Expand') diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 6391a13dd25..7a66bac09cb 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -36,7 +36,7 @@ .card .card-header Quick help - %ul.well-list + %ul.content-list %li= link_to 'See our website for getting help', support_url %li %button.btn-blank.btn-link.js-trigger-search-bar{ type: 'button' } diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 94b012d39a3..a06db85ef6f 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -116,9 +116,9 @@ .lead List with hover effect - %code .well-list + %code .hover-list .example - %ul.well-list + %ul.hover-list %li One item %li @@ -131,7 +131,7 @@ .example .card .card-header Your list - %ul.well-list + %ul.content-list %li One item %li diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml index 37466f7c821..9f525547dd9 100644 --- a/app/views/profiles/_event_table.html.haml +++ b/app/views/profiles/_event_table.html.haml @@ -1,7 +1,7 @@ %h5.prepend-top-0 History of authentications -%ul.well-list +%ul.content-list - events.each do |event| %li %span.description diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml index d198bfc80db..23ef31a0c85 100644 --- a/app/views/profiles/active_sessions/_active_session.html.haml +++ b/app/views/profiles/active_sessions/_active_session.html.haml @@ -1,10 +1,10 @@ - is_current_session = active_session.current?(session) %li.list-group-item - .pull-left.append-right-10{ data: { toggle: 'tooltip' }, title: active_session.human_device_type } + .float-left.append-right-10{ data: { toggle: 'tooltip' }, title: active_session.human_device_type } = active_session_device_type_icon(active_session) - .description.pull-left + .description.float-left %div %strong= active_session.ip_address - if is_current_session @@ -25,7 +25,7 @@ = l(active_session.created_at, format: :short) - unless is_current_session - .pull-right + .float-right = link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do %span.sr-only Revoke Revoke diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 914bd4eb57c..a5db9dbe7f8 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -29,7 +29,7 @@ Your Public Email will be displayed on your public profile. %li All email addresses will be used to identify your commits. - %ul.well-list + %ul.content-list %li = render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? } %span.float-right diff --git a/app/views/profiles/gpg_keys/_key_table.html.haml b/app/views/profiles/gpg_keys/_key_table.html.haml index cabb92c5a24..b9b60c218fd 100644 --- a/app/views/profiles/gpg_keys/_key_table.html.haml +++ b/app/views/profiles/gpg_keys/_key_table.html.haml @@ -1,7 +1,7 @@ - is_admin = local_assigns.fetch(:admin, false) - if @gpg_keys.any? - %ul.well-list + %ul.content-list = render partial: 'profiles/gpg_keys/key', collection: @gpg_keys, locals: { is_admin: is_admin } - else %p.settings-message.text-center diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 02d5b08f7a3..2ac514d3f6f 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -4,7 +4,7 @@ .card .card-header SSH Key - %ul.well-list + %ul.content-list %li %span.light Title: %strong= @key.title diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml index e78763bdcb2..e088140fdd2 100644 --- a/app/views/profiles/keys/_key_table.html.haml +++ b/app/views/profiles/keys/_key_table.html.haml @@ -1,7 +1,7 @@ - is_admin = local_assigns.fetch(:admin, false) - if @keys.any? - %ul.well-list + %ul.content-list = render partial: 'profiles/keys/key', collection: @keys, locals: { is_admin: is_admin } - else %p.settings-message.text-center diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 4bee6cb97eb..8f535b9d789 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -1,51 +1,49 @@ - active_tab = local_assigns.fetch(:active_tab, 'blank') - f = local_assigns.fetch(:f) -.project-import.row - .col-lg-12 - .form-group.import-btn-container.clearfix - = f.label :visibility_level, class: 'label-light' do #the label here seems wrong - Import project from - .import-buttons - - if gitlab_project_import_enabled? - .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do - = icon('gitlab', text: 'GitLab export') - %div - - if github_import_enabled? - = link_to new_import_github_path, class: 'btn js-import-github' do - = icon('github', text: 'GitHub') - %div - - if bitbucket_import_enabled? - = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do - = icon('bitbucket', text: 'Bitbucket') - - unless bitbucket_import_configured? - = render 'bitbucket_import_modal' - %div - - if gitlab_import_enabled? - = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do - = icon('gitlab', text: 'GitLab.com') - - unless gitlab_import_configured? - = render 'gitlab_import_modal' - %div - - if google_code_import_enabled? - = link_to new_import_google_code_path, class: 'btn import_google_code' do - = icon('google', text: 'Google Code') - %div - - if fogbugz_import_enabled? - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do - = icon('bug', text: 'Fogbugz') - %div - - if gitea_import_enabled? - = link_to new_import_gitea_path, class: 'btn import_gitea' do - = custom_icon('go_logo') - Gitea - %div - - if git_import_enabled? - %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } } - = icon('git', text: 'Repo by URL') - .col-lg-12 - .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } - %hr - = render "shared/import_form", f: f - = render 'new_project_fields', f: f, project_name_id: "import-url-name" +.project-import + .form-group.import-btn-container.clearfix + = f.label :visibility_level, class: 'label-light' do #the label here seems wrong + Import project from + .import-buttons + - if gitlab_project_import_enabled? + .import_gitlab_project.has-tooltip{ data: { container: 'body' } } + = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do + = icon('gitlab', text: 'GitLab export') + %div + - if github_import_enabled? + = link_to new_import_github_path, class: 'btn js-import-github' do + = icon('github', text: 'GitHub') + %div + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do + = icon('bitbucket', text: 'Bitbucket') + - unless bitbucket_import_configured? + = render 'bitbucket_import_modal' + %div + - if gitlab_import_enabled? + = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do + = icon('gitlab', text: 'GitLab.com') + - unless gitlab_import_configured? + = render 'gitlab_import_modal' + %div + - if google_code_import_enabled? + = link_to new_import_google_code_path, class: 'btn import_google_code' do + = icon('google', text: 'Google Code') + %div + - if fogbugz_import_enabled? + = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do + = icon('bug', text: 'Fogbugz') + %div + - if gitea_import_enabled? + = link_to new_import_gitea_path, class: 'btn import_gitea' do + = custom_icon('go_logo') + Gitea + %div + - if git_import_enabled? + %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } } + = icon('git', text: 'Repo by URL') + .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } + %hr + = render "shared/import_form", f: f + = render 'new_project_fields', f: f, project_name_id: "import-url-name" diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index 6366a2f729a..cfbd0459e3e 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -35,11 +35,10 @@ %span (optional) = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250 -.form-group.visibility-level-setting - = f.label :visibility_level, class: 'label-light' do - Visibility Level - = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer' - = render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false += f.label :visibility_level, class: 'label-light' do + Visibility Level + = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer' += render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel' diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 5b1f2d8953b..f641d7bc51a 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -28,7 +28,7 @@ = s_('Branches|Cant find HEAD commit for this branch') - if branch.name != @repository.root_ref - .divergence-graph.d-none.d-sm-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind), + .divergence-graph.d-none.d-md-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind), default_branch: @repository.root_ref, number_commits_ahead: diverging_count_label(number_commits_ahead) } } .graph-side @@ -39,7 +39,7 @@ .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" } %span.count.count-ahead= diverging_count_label(number_commits_ahead) - .controls.d-none.d-sm-block< + .controls.d-none.d-md-block< - if merge_project && create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do = _('Merge request') diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml index ac00e39c2e3..d8247b30575 100644 --- a/app/views/projects/clusters/gcp/_form.html.haml +++ b/app/views/projects/clusters/gcp/_form.html.haml @@ -25,7 +25,7 @@ %span.dropdown-toggle-text = _('Select project') = icon('chevron-down') - %span.help-block + %span.form-text.text-muted .form-group = provider_gcp_field.label :zone, s_('ClusterIntegration|Zone') diff --git a/app/views/projects/hooks/_index.html.haml b/app/views/projects/hooks/_index.html.haml index 776681ea09a..5990582fd55 100644 --- a/app/views/projects/hooks/_index.html.haml +++ b/app/views/projects/hooks/_index.html.haml @@ -15,7 +15,7 @@ %h5.prepend-top-default Webhooks (#{@hooks.count}) - if @hooks.any? - %ul.well-list + %ul.content-list - @hooks.each do |hook| = render 'project_hook', hook: hook - else diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 9c9e4ef8fce..459150c1067 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -18,7 +18,7 @@ %span= time_ago_with_tooltip @build.artifacts_expire_at - if @build.artifacts? - .btn-group.btn-group.d-flex{ role: :group } + .btn-group.d-flex{ role: :group } - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do Keep @@ -42,7 +42,7 @@ - if @build.trigger_variables.any? %p - %button.btn.group.btn-group.js-reveal-variables Reveal Variables + %button.btn.group.js-reveal-variables Reveal Variables %dl.js-build-variables.trigger-build-variables.hide - @build.trigger_variables.each do |trigger_variable| diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index 361d3c61d99..37c09f12f63 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -13,9 +13,9 @@ = f.hidden_field(:team_id, value: selected_id, required: true) if @teams.one? .form-text.text-muted - if @teams.one? - This is the only available team. + This is the only available team that you are a member of. - else - The list shows all available teams. + The list shows all available teams that you are a member of. To create a team, = link_to "#{Gitlab.config.mattermost.host}/create_team" do use Mattermost's interface diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index aa3fb623e58..01e38ffee20 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -20,6 +20,8 @@ window.gl = window.gl || {}; window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')} + window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}'; + #js-vue-mr-widget.mr-widget .content-block.content-block-small.emoji-list-container.js-noteable-awards diff --git a/app/views/projects/mirrors/_push.html.haml b/app/views/projects/mirrors/_push.html.haml index 4a6aefce351..c3dcd9617a6 100644 --- a/app/views/projects/mirrors/_push.html.haml +++ b/app/views/projects/mirrors/_push.html.haml @@ -30,7 +30,7 @@ #{h(@remote_mirror.last_error.strip)} = f.fields_for :remote_mirrors, @remote_mirror do |rm_form| .form-group - = rm_form.check_box :enabled, class: "pull-left" + = rm_form.check_box :enabled, class: "float-left" .prepend-left-20 = rm_form.label :enabled, "Remote mirror repository", class: "label-light append-bottom-0" %p.light.append-bottom-0 @@ -42,7 +42,7 @@ = render "projects/mirrors/instructions" .form-group - = rm_form.check_box :only_protected_branches, class: 'pull-left' + = rm_form.check_box :only_protected_branches, class: 'float-left' .prepend-left-20 = rm_form.label :only_protected_branches, class: 'label-light' = link_to icon('question-circle'), help_page_path('user/project/protected_branches') diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 986ca852411..e7178f9160c 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -4,7 +4,7 @@ .card .card-header Domains (#{@domains.count}) - %ul.well-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) } + %ul.content-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) } - @domains.each do |domain| %li.pages-domain-list-item.unstyled - if verification_enabled diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index d1e8e9d0d60..956f8fef6b8 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -29,7 +29,7 @@ .form-actions = f.submit s_('Pipeline|Create pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3 - = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default pull-right' + = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default float-right' -# haml-lint:disable InlineJavaScript %script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 988bcfb5265..414df15feeb 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -34,7 +34,7 @@ = form.label :domain, class:"prepend-top-10" do = _('Domain') = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' - .help-block + .form-text.text-muted = s_('CICD|A domain is required to use Auto Review Apps and Auto Deploy Stages.') - if cluster_ingress_ip = cluster_ingress_ip(@project) = s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe } diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 7d8dd58e7e0..ed17bd4f7dc 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -42,7 +42,7 @@ %section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - = _('Secret variables') + = _('Variables') = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank', rel: 'noopener noreferrer' %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml index d6e568bac94..62fa6e1907b 100644 --- a/app/views/projects/wikis/empty.html.haml +++ b/app/views/projects/wikis/empty.html.haml @@ -1,6 +1,4 @@ - page_title _("Wiki") +- @right_sidebar = false -%h3.page-title= s_("Wiki|Empty page") -%hr -.error_message - = s_("WikiEmptyPageError|You are not allowed to create wiki pages") += render 'shared/empty_states/wikis' diff --git a/app/views/shared/_email_with_badge.html.haml b/app/views/shared/_email_with_badge.html.haml index b7bbc109238..ad863b1967d 100644 --- a/app/views/shared/_email_with_badge.html.haml +++ b/app/views/shared/_email_with_badge.html.haml @@ -1,4 +1,4 @@ -- css_classes = %w(label label-verification-status) +- css_classes = %w(badge badge-verification-status) - css_classes << (verified ? 'verified': 'unverified') - text = verified ? 'Verified' : 'Unverified' diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 35673303b85..356e12cf9f8 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -1,19 +1,20 @@ - ci_cd_only = local_assigns.fetch(:ci_cd_only, false) -.form-group.row.import-url-data +.form-group.import-url-data = f.label :import_url, class: 'label-light' do %span = _('Git repository URL') = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true - .card.prepend-top-20 - %ul - %li - = _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe - %li - = _('If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.').html_safe - %li - = import_will_timeout_message(ci_cd_only) - %li - = import_svn_message(ci_cd_only) + .info-well.prepend-top-20 + .well-segment + %ul + %li + = _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe + %li + = _('If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.').html_safe + %li + = import_will_timeout_message(ci_cd_only) + %li + = import_svn_message(ci_cd_only) diff --git a/app/views/shared/dashboard/_no_filter_selected.html.haml b/app/views/shared/dashboard/_no_filter_selected.html.haml index b2e6967f6aa..32246dac4c7 100644 --- a/app/views/shared/dashboard/_no_filter_selected.html.haml +++ b/app/views/shared/dashboard/_no_filter_selected.html.haml @@ -1,8 +1,8 @@ .row.empty-state.text-center - .col-xs-12 + .col-12 .svg-130.prepend-top-default = image_tag 'illustrations/issue-dashboard_results-without-filter.svg' - .col-xs-12 + .col-12 .text-content %h4 = _("Please select at least one filter to see results") diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml new file mode 100644 index 00000000000..fabb1f39a34 --- /dev/null +++ b/app/views/shared/empty_states/_wikis.html.haml @@ -0,0 +1,30 @@ +- layout_path = 'shared/empty_states/wikis_layout' + +- if can?(current_user, :create_wiki, @project) + - create_path = project_wiki_path(@project, params[:id], { view: 'create' }) + - create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-new', title: s_('WikiEmpty|Create your first page') + + = render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do + %h4 + = s_('WikiEmpty|The wiki lets you write documentation for your project') + %p.text-left + = s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, it's principles, how to use it, and so on.") + = create_link + +- elsif can?(current_user, :read_issue, @project) + - issues_link = link_to s_('WikiEmptyIssueMessage|issue tracker'), project_issues_path(@project) + - new_issue_link = link_to s_('WikiEmpty|Suggest wiki improvement'), new_project_issue_path(@project), class: 'btn btn-new', title: s_('WikiEmptyIssueMessage|Suggest wiki improvement') + + = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do + %h4 + = s_('WikiEmpty|This project has no wiki pages') + %p.text-left + = s_('WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}.').html_safe % { issues_link: issues_link } + = new_issue_link + +- else + = render layout: layout_path, locals: { image_path: 'illustrations/wiki_logout_empty.svg' } do + %h4 + = s_('WikiEmpty|This project has no wiki pages') + %p + = s_('WikiEmpty|You must be a project member in order to add wiki pages.') diff --git a/app/views/shared/empty_states/_wikis_layout.html.haml b/app/views/shared/empty_states/_wikis_layout.html.haml new file mode 100644 index 00000000000..a5f100e3469 --- /dev/null +++ b/app/views/shared/empty_states/_wikis_layout.html.haml @@ -0,0 +1,7 @@ +.row.empty-state + .col-12 + .svg-content + = image_tag image_path + .col-12 + .text-content.text-center + = yield diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 602729b172a..a57cd4b20d1 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -33,7 +33,7 @@ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed - if issuable.milestone - = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 'true' } + = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 'true', boundary: 'viewport' } - else %span.no-value = _('None') diff --git a/app/views/shared/issuable/form/_default_templates.html.haml b/app/views/shared/issuable/form/_default_templates.html.haml new file mode 100644 index 00000000000..49a5ce926b3 --- /dev/null +++ b/app/views/shared/issuable/form/_default_templates.html.haml @@ -0,0 +1,4 @@ +%p.form-text.text-muted + Add + = link_to 'description templates', help_page_path('user/project/description_templates'), tabindex: -1 + to help your contributors communicate effectively! diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index 1df881e4102..90fbf19e843 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -15,3 +15,12 @@ = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch? Remove source branch when merge request is accepted. + +.form-group + .col-sm-10.col-sm-offset-2 + .checkbox + = label_tag 'merge_request[squash]' do + = hidden_field_tag 'merge_request[squash]', '0', id: nil + = check_box_tag 'merge_request[squash]', '1', issuable.squash + Squash commits when merge request is accepted. + = link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge') diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml index c4f30f5f4d9..c35d0b3751f 100644 --- a/app/views/shared/issuable/form/_title.html.haml +++ b/app/views/shared/issuable/form/_title.html.haml @@ -30,7 +30,4 @@ merge request from being merged before it's ready. - if no_issuable_templates && can?(current_user, :push_code, issuable.project) - %p.form-text.text-muted - Add - = link_to 'description templates', help_page_path('user/project/description_templates'), tabindex: -1 - to help your contributors communicate effectively! + = render 'shared/issuable/form/default_templates' diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index d8e4d2ff88c..ee6354b1c28 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -11,7 +11,7 @@ = number_with_delimiter(issuables.length) - class_prefix = dom_class(issuables).pluralize - %ul{ class: "well-list milestone-#{class_prefix}-list", id: "#{class_prefix}-list-#{id}" } + %ul{ class: "content-list milestone-#{class_prefix}-list", id: "#{class_prefix}-list-#{id}" } = render partial: 'shared/milestones/issuable', collection: issuables, as: :issuable, diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 95138af3950..becd1c4884e 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -78,7 +78,7 @@ %span= milestone.issues_visible_to_user(current_user).count .title.hide-collapsed Issues - %span.badg.badge-pille= milestone.issues_visible_to_user(current_user).count + %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).count - if show_new_issue_link?(project) = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "float-right", title: "New Issue" do New issue @@ -99,6 +99,8 @@ = _('Time tracking') = icon('spinner spin') + = render_if_exists 'shared/milestones/weight', milestone: milestone + .block.merge-requests .sidebar-collapsed-icon.has-tooltip{ title: milestone_merge_requests_tooltip_text(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } %strong diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index ee0e35cedc6..320e3788a0f 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -48,6 +48,8 @@ - close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.' %span All issues for this milestone are closed. #{close_msg} += render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project + - if is_dynamic_milestone .table-holder %table.table diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index e036b21b23f..5069e2e4ca6 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -28,7 +28,7 @@ = link_to user_snippets_path(snippet.author) do = snippet.author_name - if link_project && snippet.project_id? - %span.d-none.d-sm-block + %span.d-none.d-sm-inline-block in = link_to project_path(snippet.project) do = snippet.project.full_name diff --git a/app/views/sherlock/queries/_backtrace.html.haml b/app/views/sherlock/queries/_backtrace.html.haml index 4f5146cefb9..38b4d2c6102 100644 --- a/app/views/sherlock/queries/_backtrace.html.haml +++ b/app/views/sherlock/queries/_backtrace.html.haml @@ -3,7 +3,7 @@ .card-header %strong = t('sherlock.application_backtrace') - %ul.well-list + %ul.content-list - @query.application_backtrace.each do |location| %li %strong @@ -19,7 +19,7 @@ .card-header %strong = t('sherlock.full_backtrace') - %ul.well-list + %ul.content-list - @query.backtrace.each do |location| %li - if location.application? diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml index 34c0cc4da39..37747faed62 100644 --- a/app/views/sherlock/queries/_general.html.haml +++ b/app/views/sherlock/queries/_general.html.haml @@ -3,7 +3,7 @@ .card-header %strong = t('sherlock.general') - %ul.well-list + %ul.content-list %li %span.light #{t('sherlock.time')}: @@ -32,7 +32,7 @@ = @query.formatted_query %strong = t('sherlock.query') - %ul.well-list + %ul.content-list %li .code.js-syntax-highlight.sherlock-code :preserve @@ -47,7 +47,7 @@ = @query.explain %strong = t('sherlock.query_plan') - %ul.well-list + %ul.content-list %li .code.js-syntax-highlight.sherlock-code %pre diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml index 7ec8dde8421..9c028b5c741 100644 --- a/app/views/sherlock/transactions/_general.html.haml +++ b/app/views/sherlock/transactions/_general.html.haml @@ -3,7 +3,7 @@ .card-header %strong = t('sherlock.general') - %ul.well-list + %ul.content-list %li %span.light #{t('sherlock.id')}: diff --git a/app/views/users/terms/index.html.haml b/app/views/users/terms/index.html.haml index c5406696bdd..e0fe551cf36 100644 --- a/app/views/users/terms/index.html.haml +++ b/app/views/users/terms/index.html.haml @@ -4,10 +4,10 @@ = markdown_field(@term, :terms) .row-content-block.footer-block.clearfix - if can?(current_user, :accept_terms, @term) - .pull-right + .float-right = button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do = _('Accept terms') - if can?(current_user, :decline_terms, @term) - .pull-right + .float-right = button_to decline_term_path(@term, redirect_params), class: 'btn btn-default prepend-left-8' do = _('Decline and sign out') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 80b488f31f2..93e57512edb 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -114,3 +114,4 @@ - upload_checksum - web_hook - repository_update_remote_mirror +- create_note_diff_file diff --git a/app/workers/create_note_diff_file_worker.rb b/app/workers/create_note_diff_file_worker.rb new file mode 100644 index 00000000000..624b638a24e --- /dev/null +++ b/app/workers/create_note_diff_file_worker.rb @@ -0,0 +1,9 @@ +class CreateNoteDiffFileWorker + include ApplicationWorker + + def perform(diff_note_id) + diff_note = DiffNote.find(diff_note_id) + + diff_note.create_diff_file + end +end diff --git a/changelogs/unreleased/38919-wiki-empty-states.yml b/changelogs/unreleased/38919-wiki-empty-states.yml new file mode 100644 index 00000000000..953fa29e659 --- /dev/null +++ b/changelogs/unreleased/38919-wiki-empty-states.yml @@ -0,0 +1,5 @@ +--- +title: Add helpful messages to empty wiki view +merge_request: 19007 +author: +type: other diff --git a/changelogs/unreleased/45190-create-notes-diff-files.yml b/changelogs/unreleased/45190-create-notes-diff-files.yml new file mode 100644 index 00000000000..efe322b682d --- /dev/null +++ b/changelogs/unreleased/45190-create-notes-diff-files.yml @@ -0,0 +1,5 @@ +--- +title: Persist truncated note diffs on a new table +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/46600-fix-gitlab-revision-when-not-in-git-repo.yml b/changelogs/unreleased/46600-fix-gitlab-revision-when-not-in-git-repo.yml deleted file mode 100644 index 1d0b11cfd2a..00000000000 --- a/changelogs/unreleased/46600-fix-gitlab-revision-when-not-in-git-repo.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Replace Gitlab::REVISION with Gitlab.revision and handle installations without - a .git directory -merge_request: 19125 -author: -type: fixed diff --git a/changelogs/unreleased/46844-update-awesome_print-to-1-8-0.yml b/changelogs/unreleased/46844-update-awesome_print-to-1-8-0.yml new file mode 100644 index 00000000000..e6dc9a6187b --- /dev/null +++ b/changelogs/unreleased/46844-update-awesome_print-to-1-8-0.yml @@ -0,0 +1,5 @@ +--- +title: Update awesome_print to 1.8.0 +merge_request: 19163 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/46846-update-redis-namespace-to-1-6-0.yml b/changelogs/unreleased/46846-update-redis-namespace-to-1-6-0.yml new file mode 100644 index 00000000000..3707ad74b8f --- /dev/null +++ b/changelogs/unreleased/46846-update-redis-namespace-to-1-6-0.yml @@ -0,0 +1,5 @@ +--- +title: Update redis-namespace to 1.6.0 +merge_request: 19166 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/46849-update-rdoc-to-6-0-4.yml b/changelogs/unreleased/46849-update-rdoc-to-6-0-4.yml new file mode 100644 index 00000000000..cf0436df1a7 --- /dev/null +++ b/changelogs/unreleased/46849-update-rdoc-to-6-0-4.yml @@ -0,0 +1,5 @@ +--- +title: Update rdoc to 6.0.4 +merge_request: 19167 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/46999-line-profiling-modal-width.yml b/changelogs/unreleased/46999-line-profiling-modal-width.yml new file mode 100644 index 00000000000..130f50d1ec0 --- /dev/null +++ b/changelogs/unreleased/46999-line-profiling-modal-width.yml @@ -0,0 +1,5 @@ +--- +title: Fix UI broken in line profiling modal due to Bootstrap 4 +merge_request: 19253 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/ab-35364-throttle-updates-last-repository-at.yml b/changelogs/unreleased/ab-35364-throttle-updates-last-repository-at.yml new file mode 100644 index 00000000000..8e468233637 --- /dev/null +++ b/changelogs/unreleased/ab-35364-throttle-updates-last-repository-at.yml @@ -0,0 +1,5 @@ +--- +title: Throttle updates to Project#last_repository_updated_at. +merge_request: 19183 +author: +type: performance diff --git a/changelogs/unreleased/ab-45389-remove-double-checked-internal-id-generation.yml b/changelogs/unreleased/ab-45389-remove-double-checked-internal-id-generation.yml new file mode 100644 index 00000000000..d87604de0f8 --- /dev/null +++ b/changelogs/unreleased/ab-45389-remove-double-checked-internal-id-generation.yml @@ -0,0 +1,5 @@ +--- +title: Remove double-checked internal id generation. +merge_request: 19181 +author: +type: performance diff --git a/changelogs/unreleased/add-artifacts_expire_at-to-api.yml b/changelogs/unreleased/add-artifacts_expire_at-to-api.yml new file mode 100644 index 00000000000..7fe0d8b5720 --- /dev/null +++ b/changelogs/unreleased/add-artifacts_expire_at-to-api.yml @@ -0,0 +1,5 @@ +--- +title: Expose artifacts_expire_at field for job entity in api +merge_request: 18872 +author: Semyon Pupkov +type: added diff --git a/changelogs/unreleased/add-background-migration-to-fill-file-store.yml b/changelogs/unreleased/add-background-migration-to-fill-file-store.yml new file mode 100644 index 00000000000..ab6bde86fd4 --- /dev/null +++ b/changelogs/unreleased/add-background-migration-to-fill-file-store.yml @@ -0,0 +1,5 @@ +--- +title: Add backgound migration for filling nullfied file_store columns +merge_request: 18557 +author: +type: performance diff --git a/changelogs/unreleased/blackst0ne-squash-and-merge-in-gitlab-core-ce.yml b/changelogs/unreleased/blackst0ne-squash-and-merge-in-gitlab-core-ce.yml new file mode 100644 index 00000000000..e603c835b5e --- /dev/null +++ b/changelogs/unreleased/blackst0ne-squash-and-merge-in-gitlab-core-ce.yml @@ -0,0 +1,5 @@ +--- +title: Add `Squash and merge` to GitLab Core (CE) +merge_request: 18956 +author: "@blackst0ne" +type: added diff --git a/changelogs/unreleased/bump-kubeclient-version-3-1-0.yml b/changelogs/unreleased/bump-kubeclient-version-3-1-0.yml new file mode 100644 index 00000000000..24f240410b0 --- /dev/null +++ b/changelogs/unreleased/bump-kubeclient-version-3-1-0.yml @@ -0,0 +1,5 @@ +--- +title: Updates the version of kubeclient from 3.0 to 3.1.0 +merge_request: 19199 +author: +type: other diff --git a/changelogs/unreleased/bvl-add-username-to-terms-message.yml b/changelogs/unreleased/bvl-add-username-to-terms-message.yml new file mode 100644 index 00000000000..b95d87e9265 --- /dev/null +++ b/changelogs/unreleased/bvl-add-username-to-terms-message.yml @@ -0,0 +1,5 @@ +--- +title: Add username to terms message in git and API calls +merge_request: 19126 +author: +type: changed diff --git a/changelogs/unreleased/dm-api-projects-members-preload.yml b/changelogs/unreleased/dm-api-projects-members-preload.yml new file mode 100644 index 00000000000..e04e7c37d13 --- /dev/null +++ b/changelogs/unreleased/dm-api-projects-members-preload.yml @@ -0,0 +1,6 @@ +--- +title: Only preload member records for the relevant projects/groups/user in projects + API +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/dz-redesign-group-settings-page.yml b/changelogs/unreleased/dz-redesign-group-settings-page.yml new file mode 100644 index 00000000000..4a8dfbb61dc --- /dev/null +++ b/changelogs/unreleased/dz-redesign-group-settings-page.yml @@ -0,0 +1,5 @@ +--- +title: Redesign group settings page into expandable sections +merge_request: 19184 +author: +type: changed diff --git a/changelogs/unreleased/fix-bitbucket_import_anonymous.yml b/changelogs/unreleased/fix-bitbucket_import_anonymous.yml new file mode 100644 index 00000000000..6e214b3c957 --- /dev/null +++ b/changelogs/unreleased/fix-bitbucket_import_anonymous.yml @@ -0,0 +1,5 @@ +--- +title: Import bitbucket issues that are reported by an anonymous user +merge_request: 18199 +author: bartl +type: fixed diff --git a/changelogs/unreleased/fix-missing-timeout.yml b/changelogs/unreleased/fix-missing-timeout.yml new file mode 100644 index 00000000000..e0a61eb866c --- /dev/null +++ b/changelogs/unreleased/fix-missing-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Missing timeout value in object storage pre-authorization +merge_request: 19201 +author: +type: fixed diff --git a/changelogs/unreleased/fix-nbsp-after-sign-in-with-google.yml b/changelogs/unreleased/fix-nbsp-after-sign-in-with-google.yml new file mode 100644 index 00000000000..73b478eff3e --- /dev/null +++ b/changelogs/unreleased/fix-nbsp-after-sign-in-with-google.yml @@ -0,0 +1,5 @@ +--- +title: Fix after sign-in with Google button +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/groups-controller-show-performance.yml b/changelogs/unreleased/groups-controller-show-performance.yml new file mode 100644 index 00000000000..bab54cc455e --- /dev/null +++ b/changelogs/unreleased/groups-controller-show-performance.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance of GroupsController#show +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/ignore-writing-trace-if-it-already-archived.yml b/changelogs/unreleased/ignore-writing-trace-if-it-already-archived.yml new file mode 100644 index 00000000000..5c342e2a131 --- /dev/null +++ b/changelogs/unreleased/ignore-writing-trace-if-it-already-archived.yml @@ -0,0 +1,5 @@ +--- +title: Disallow updating job status if the job is not running +merge_request: 19101 +author: +type: fixed diff --git a/changelogs/unreleased/mattermost-api-v4.yml b/changelogs/unreleased/mattermost-api-v4.yml new file mode 100644 index 00000000000..8c5033f2a0c --- /dev/null +++ b/changelogs/unreleased/mattermost-api-v4.yml @@ -0,0 +1,5 @@ +--- +title: Updated Mattermost integration to use API v4 and only allow creation of Mattermost slash commands in the current user's teams +merge_request: 19043 +author: Harrison Healey +type: changed diff --git a/changelogs/unreleased/memoize-database-version.yml b/changelogs/unreleased/memoize-database-version.yml deleted file mode 100644 index 575348a53a1..00000000000 --- a/changelogs/unreleased/memoize-database-version.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Memoize Gitlab::Database.version -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/patch-28.yml b/changelogs/unreleased/patch-28.yml new file mode 100644 index 00000000000..1bbca138cae --- /dev/null +++ b/changelogs/unreleased/patch-28.yml @@ -0,0 +1,5 @@ +--- +title: Fix FreeBSD can not upload artifacts due to wrong tmp path +merge_request: 19148 +author: +type: fixed diff --git a/changelogs/unreleased/security-dm-delete-deploy-key.yml b/changelogs/unreleased/security-dm-delete-deploy-key.yml new file mode 100644 index 00000000000..aa94e7b6072 --- /dev/null +++ b/changelogs/unreleased/security-dm-delete-deploy-key.yml @@ -0,0 +1,5 @@ +--- +title: Fix API to remove deploy key from project instead of deleting it entirely +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-fj-import-export-assignment.yml b/changelogs/unreleased/security-fj-import-export-assignment.yml new file mode 100644 index 00000000000..4bfd71d431a --- /dev/null +++ b/changelogs/unreleased/security-fj-import-export-assignment.yml @@ -0,0 +1,5 @@ +--- +title: Fixed bug that allowed importing arbitrary project attributes +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-users-can-update-their-password-without-entering-current-password.yml b/changelogs/unreleased/security-users-can-update-their-password-without-entering-current-password.yml new file mode 100644 index 00000000000..824fbd41ab8 --- /dev/null +++ b/changelogs/unreleased/security-users-can-update-their-password-without-entering-current-password.yml @@ -0,0 +1,5 @@ +--- +title: Prevent user passwords from being changed without providing the previous password +merge_request: +author: +type: security diff --git a/changelogs/unreleased/sh-batch-dependent-destroys.yml b/changelogs/unreleased/sh-batch-dependent-destroys.yml new file mode 100644 index 00000000000..e297badc1fa --- /dev/null +++ b/changelogs/unreleased/sh-batch-dependent-destroys.yml @@ -0,0 +1,5 @@ +--- +title: Fix project destruction failing due to idle in transaction timeouts +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-admin-page-counts-take-2.yml b/changelogs/unreleased/sh-fix-admin-page-counts-take-2.yml new file mode 100644 index 00000000000..d9bd1af9380 --- /dev/null +++ b/changelogs/unreleased/sh-fix-admin-page-counts-take-2.yml @@ -0,0 +1,5 @@ +--- +title: Fix admin counters not working when PostgreSQL has secondaries +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-tag-queue-duration-api-calls.yml b/changelogs/unreleased/sh-tag-queue-duration-api-calls.yml new file mode 100644 index 00000000000..686cceaab62 --- /dev/null +++ b/changelogs/unreleased/sh-tag-queue-duration-api-calls.yml @@ -0,0 +1,5 @@ +--- +title: Log Workhorse queue duration for Grape API calls +merge_request: +author: +type: other diff --git a/changelogs/unreleased/sh-use-grape-path-helpers.yml b/changelogs/unreleased/sh-use-grape-path-helpers.yml new file mode 100644 index 00000000000..c462c7e8194 --- /dev/null +++ b/changelogs/unreleased/sh-use-grape-path-helpers.yml @@ -0,0 +1,5 @@ +--- +title: Replace grape-route-helpers with our own grape-path-helpers +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/winh-new-merge-request-encoding.yml b/changelogs/unreleased/winh-new-merge-request-encoding.yml deleted file mode 100644 index f797657e660..00000000000 --- a/changelogs/unreleased/winh-new-merge-request-encoding.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix encoding of branch names on compare and new merge request page -merge_request: 19143 -author: -type: fixed diff --git a/config/initializers/grape_route_helpers_fix.rb b/config/initializers/grape_route_helpers_fix.rb deleted file mode 100644 index 612cca3dfbd..00000000000 --- a/config/initializers/grape_route_helpers_fix.rb +++ /dev/null @@ -1,51 +0,0 @@ -if defined?(GrapeRouteHelpers) - module GrapeRouteHelpers - module AllRoutes - # Bringing in PR https://github.com/reprah/grape-route-helpers/pull/21 due to abandonment. - # - # Without the following fix, when two helper methods are the same, but have different arguments - # (for example: api_v1_cats_owners_path(id: 1) vs api_v1_cats_owners_path(id: 1, owner_id: 2)) - # if the helper method with the least number of arguments is defined first (because the route was defined first) - # then it will shadow the longer route. - # - # The fix is to sort descending by amount of arguments - def decorated_routes - @decorated_routes ||= all_routes - .map { |r| DecoratedRoute.new(r) } - .sort_by { |r| -r.dynamic_path_segments.count } - end - end - - class DecoratedRoute - # GrapeRouteHelpers gem tries to parse the versions - # from a string, not supporting Grape `version` array definition. - # - # Without the following fix, we get this on route helpers generation: - # - # => undefined method `scan' for ["v3", "v4"] - # - # 2.0.0 implementation of this method: - # - # ``` - # def route_versions - # version_pattern = /[^\[",\]\s]+/ - # if route_version - # route_version.scan(version_pattern) - # else - # [nil] - # end - # end - # ``` - def route_versions - return [nil] if route_version.nil? || route_version.empty? - - if route_version.is_a?(String) - version_pattern = /[^\[",\]\s]+/ - route_version.scan(version_pattern) - else - route_version - end - end - end - end -end diff --git a/config/routes/group.rb b/config/routes/group.rb index 7c4c3d370e0..fff0914c3cd 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -30,6 +30,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do resource :variables, only: [:show, :update] resources :children, only: [:index] + resources :shared_projects, only: [:index] resources :labels, except: [:show] do post :toggle_subscription, on: :member diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index e1e8f36b663..d16060e8f45 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -75,4 +75,5 @@ - [pipeline_background, 1] - [repository_update_remote_mirror, 1] - [repository_remove_remote, 1] + - [create_note_diff_file, 1] diff --git a/db/migrate/20180515005612_add_squash_to_merge_requests.rb b/db/migrate/20180515005612_add_squash_to_merge_requests.rb new file mode 100644 index 00000000000..f526b45bd4b --- /dev/null +++ b/db/migrate/20180515005612_add_squash_to_merge_requests.rb @@ -0,0 +1,19 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddSquashToMergeRequests < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + unless column_exists?(:merge_requests, :squash) + add_column_with_default :merge_requests, :squash, :boolean, default: false, allow_null: false + end + end + + def down + remove_column :merge_requests, :squash if column_exists?(:merge_requests, :squash) + end +end diff --git a/db/migrate/20180515121227_create_notes_diff_files.rb b/db/migrate/20180515121227_create_notes_diff_files.rb new file mode 100644 index 00000000000..7108bc1a64b --- /dev/null +++ b/db/migrate/20180515121227_create_notes_diff_files.rb @@ -0,0 +1,21 @@ +class CreateNotesDiffFiles < ActiveRecord::Migration + DOWNTIME = false + + disable_ddl_transaction! + + def change + create_table :note_diff_files do |t| + t.references :diff_note, references: :notes, null: false, index: { unique: true } + t.text :diff, null: false + t.boolean :new_file, null: false + t.boolean :renamed_file, null: false + t.boolean :deleted_file, null: false + t.string :a_mode, null: false + t.string :b_mode, null: false + t.text :new_path, null: false + t.text :old_path, null: false + end + + add_foreign_key :note_diff_files, :notes, column: :diff_note_id, on_delete: :cascade + end +end diff --git a/db/migrate/20180524132016_merge_requests_target_id_iid_state_partial_index.rb b/db/migrate/20180524132016_merge_requests_target_id_iid_state_partial_index.rb new file mode 100644 index 00000000000..cee576b91c8 --- /dev/null +++ b/db/migrate/20180524132016_merge_requests_target_id_iid_state_partial_index.rb @@ -0,0 +1,27 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MergeRequestsTargetIdIidStatePartialIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + INDEX_NAME = 'index_merge_requests_on_target_project_id_and_iid_opened' + + disable_ddl_transaction! + + def up + # On GitLab.com this index will take up roughly 5 MB of space. + add_concurrent_index( + :merge_requests, + [:target_project_id, :iid], + where: "state = 'opened'", + name: INDEX_NAME + ) + end + + def down + remove_concurrent_index_by_name(:merge_requests, INDEX_NAME) + end +end diff --git a/db/migrate/20180529093006_ensure_remote_mirror_columns.rb b/db/migrate/20180529093006_ensure_remote_mirror_columns.rb new file mode 100644 index 00000000000..290416cb61c --- /dev/null +++ b/db/migrate/20180529093006_ensure_remote_mirror_columns.rb @@ -0,0 +1,24 @@ +class EnsureRemoteMirrorColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column :remote_mirrors, :last_update_started_at, :datetime unless column_exists?(:remote_mirrors, :last_update_started_at) + add_column :remote_mirrors, :remote_name, :string unless column_exists?(:remote_mirrors, :remote_name) + + unless column_exists?(:remote_mirrors, :only_protected_branches) + add_column_with_default(:remote_mirrors, + :only_protected_branches, + :boolean, + default: false, + allow_null: false) + end + end + + def down + # db/migrate/20180503131624_create_remote_mirrors.rb will remove the table + end +end diff --git a/db/post_migrate/20180424151928_fill_file_store.rb b/db/post_migrate/20180424151928_fill_file_store.rb new file mode 100644 index 00000000000..b41feb233be --- /dev/null +++ b/db/post_migrate/20180424151928_fill_file_store.rb @@ -0,0 +1,72 @@ +class FillFileStore < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + class JobArtifact < ActiveRecord::Base + include EachBatch + self.table_name = 'ci_job_artifacts' + BATCH_SIZE = 10_000 + + def self.params_for_background_migration + yield self.where(file_store: nil), 'FillFileStoreJobArtifact', 5.minutes, BATCH_SIZE + end + end + + class LfsObject < ActiveRecord::Base + include EachBatch + self.table_name = 'lfs_objects' + BATCH_SIZE = 10_000 + + def self.params_for_background_migration + yield self.where(file_store: nil), 'FillFileStoreLfsObject', 5.minutes, BATCH_SIZE + end + end + + class Upload < ActiveRecord::Base + include EachBatch + self.table_name = 'uploads' + self.inheritance_column = :_type_disabled # Disable STI + BATCH_SIZE = 10_000 + + def self.params_for_background_migration + yield self.where(store: nil), 'FillStoreUpload', 5.minutes, BATCH_SIZE + end + end + + def up + # NOTE: Schedule background migrations that fill 'NULL' value by '1'(ObjectStorage::Store::LOCAL) on `file_store`, `store` columns + # + # Here are the target columns + # - ci_job_artifacts.file_store + # - lfs_objects.file_store + # - uploads.store + + FillFileStore::JobArtifact.params_for_background_migration do |relation, class_name, delay_interval, batch_size| + queue_background_migration_jobs_by_range_at_intervals(relation, + class_name, + delay_interval, + batch_size: batch_size) + end + + FillFileStore::LfsObject.params_for_background_migration do |relation, class_name, delay_interval, batch_size| + queue_background_migration_jobs_by_range_at_intervals(relation, + class_name, + delay_interval, + batch_size: batch_size) + end + + FillFileStore::Upload.params_for_background_migration do |relation, class_name, delay_interval, batch_size| + queue_background_migration_jobs_by_range_at_intervals(relation, + class_name, + delay_interval, + batch_size: batch_size) + end + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index 37d336b9928..42fea8e4380 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180521171529) do +ActiveRecord::Schema.define(version: 20180529093006) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1217,6 +1217,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do t.integer "latest_merge_request_diff_id" t.string "rebase_commit_sha" t.boolean "allow_maintainer_to_push" + t.boolean "squash", default: false, null: false end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -1232,6 +1233,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree + add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)", using: :btree add_index "merge_requests", ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} @@ -1302,6 +1304,20 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_index "namespaces", ["runners_token"], name: "index_namespaces_on_runners_token", unique: true, using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree + create_table "note_diff_files", force: :cascade do |t| + t.integer "diff_note_id", null: false + t.text "diff", null: false + t.boolean "new_file", null: false + t.boolean "renamed_file", null: false + t.boolean "deleted_file", null: false + t.string "a_mode", null: false + t.string "b_mode", null: false + t.text "new_path", null: false + t.text "old_path", null: false + end + + add_index "note_diff_files", ["diff_note_id"], name: "index_note_diff_files_on_diff_note_id", unique: true, using: :btree + create_table "notes", force: :cascade do |t| t.text "note" t.string "noteable_type" @@ -2243,6 +2259,7 @@ ActiveRecord::Schema.define(version: 20180521171529) do add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade + add_foreign_key "note_diff_files", "notes", column: "diff_note_id", on_delete: :cascade add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id" add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 960970aea30..1c508c77ffa 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -3,7 +3,7 @@ > **Note:** Custom Git hooks must be configured on the filesystem of the GitLab server. Only GitLab server administrators will be able to complete these tasks. -Please explore [webhooks] as an option if you do not +Please explore [webhooks] and [CI] as an option if you do not have filesystem access. For a user configurable Git hook interface, see [Push Rules](https://docs.gitlab.com/ee/push_rules/push_rules.html), available in GitLab Enterprise Edition. @@ -80,6 +80,7 @@ STDERR takes precedence over STDOUT.  +[CI]: ../ci/README.md [hooks]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks [webhooks]: ../user/project/integrations/webhooks.md [5073]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5073 diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md index ca6d8d2de67..b5124b1d540 100644 --- a/doc/administration/high_availability/database.md +++ b/doc/administration/high_availability/database.md @@ -33,16 +33,7 @@ If you use a cloud-managed service, or provide your own PostgreSQL: external_url 'https://gitlab.example.com' # Disable all components except PostgreSQL - postgresql['enable'] = true - bootstrap['enable'] = false - nginx['enable'] = false - unicorn['enable'] = false - sidekiq['enable'] = false - redis['enable'] = false - prometheus['enable'] = false - gitaly['enable'] = false - gitlab_workhorse['enable'] = false - mailroom['enable'] = false + roles ['postgres_role'] # PostgreSQL configuration gitlab_rails['db_password'] = 'DB password' diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index e201848791c..0d9c10687f2 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -47,7 +47,8 @@ for each GitLab application server in your environment. URL. Depending your the NFS configuration, you may need to change some GitLab data locations. See [NFS documentation](nfs.md) for `/etc/gitlab/gitlab.rb` configuration values for various scenarios. The example below assumes you've - added NFS mounts in the default data locations. + added NFS mounts in the default data locations. Additionally the UID and GIDs + given are just examples and you should configure with your preferred values. ```ruby external_url 'https://gitlab.example.com' @@ -68,6 +69,14 @@ for each GitLab application server in your environment. gitlab_rails['redis_port'] = '6379' gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server gitlab_rails['redis_password'] = 'Redis Password' + + # Ensure UIDs and GIDs match between servers for permissions via NFS + user['uid'] = 9000 + user['gid'] = 9000 + web_server['uid'] = 9001 + web_server['gid'] = 9001 + registry['uid'] = 9002 + registry['gid'] = 9002 ``` > **Note:** To maintain uniformity of links across HA clusters, the `external_url` @@ -75,25 +84,24 @@ for each GitLab application server in your environment. servers should point to the external url that users will use to access GitLab. In a typical HA setup, this will be the url of the load balancer which will route traffic to all GitLab application servers in the HA cluster. - -1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. + + > **Note:** When you specify `https` in the `external_url`, as in the example + above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If + certificates are not present, Nginx will fail to start. See + [Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) + for more information. ## First GitLab application server -As a final step, run the setup rake task on the first GitLab application server. -It is not necessary to run this on additional application servers. +As a final step, run the setup rake task **only on** the first GitLab application server. +Do not run this on additional application servers. 1. Initialize the database by running `sudo gitlab-rake gitlab:setup`. +1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. > **WARNING:** Only run this setup task on **NEW** GitLab instances because it will wipe any existing data. -> **Note:** When you specify `https` in the `external_url`, as in the example - above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If - certificates are not present, Nginx will fail to start. See - [Nginx documentation](http://docs.gitlab.com/omnibus/settings/nginx.html#enable-https) - for more information. - ## Extra configuration for additional GitLab application servers Additional GitLab servers (servers configured **after** the first GitLab server) @@ -101,8 +109,7 @@ need some extra configuration. 1. Configure shared secrets. These values can be obtained from the primary GitLab server in `/etc/gitlab/gitlab-secrets.json`. Add these to - `/etc/gitlab/gitlab.rb` **prior to** running the first `reconfigure` in - the steps above. + `/etc/gitlab/gitlab.rb` **prior to** running the first `reconfigure`. ```ruby gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860' @@ -115,6 +122,8 @@ need some extra configuration. from running on upgrade. Only the primary GitLab application server should handle migrations. +1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. + ## Troubleshooting - `mount: wrong fs type, bad option, bad superblock on` diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index 957f17e3ea3..87e96b71dd4 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -25,7 +25,9 @@ options: errors when the Omnibus package tries to alter permissions. Note that GitLab and other bundled components do **not** run as `root` but as non-privileged users. The recommendation for `no_root_squash` is to allow the Omnibus package - to set ownership and permissions on files, as needed. + to set ownership and permissions on files, as needed. In some cases where the + `no_root_squash` option is not available, the `root` flag can achieve the same + result. - `sync` - Force synchronous behavior. Default is asynchronous and under certain circumstances it could lead to data loss if a failure occurs before data has synced. diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md index 91e844c7b42..32ad63c3706 100644 --- a/doc/administration/integration/terminal.md +++ b/doc/administration/integration/terminal.md @@ -1,12 +1,13 @@ # Web terminals -> [Introduced][ce-7690] in GitLab 8.15. Only project masters and owners can - access web terminals. +> +[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7690) +in GitLab 8.15. Only project masters and owners can access web terminals. -With the introduction of the [Kubernetes project service][kubservice], GitLab -gained the ability to store and use credentials for a Kubernetes cluster. One -of the things it uses these credentials for is providing access to -[web terminals](../../ci/environments.html#web-terminals) for environments. +With the introduction of the [Kubernetes integration](../../user/project/clusters/index.md), +GitLab gained the ability to store and use credentials for a Kubernetes cluster. +One of the things it uses these credentials for is providing access to +[web terminals](../../ci/environments.md#web-terminals) for environments. ## How it works @@ -80,6 +81,3 @@ Terminal sessions use long-lived connections; by default, these may last forever. You can configure a maximum session time in the Admin area of your GitLab instance if you find this undesirable from a scalability or security point of view. - -[ce-7690]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7690 -[kubservice]: ../../user/project/integrations/kubernetes.md diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index f47add48345..1c79e86dcb4 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -29,7 +29,8 @@ For installations from source you'll have to install and configure it yourself. Prometheus and it's exporters are on by default, starting with GitLab 9.0. Prometheus will run as the `gitlab-prometheus` user and listen on -`http://localhost:9090`. Each exporter will be automatically be set up as a +`http://localhost:9090`. By default Prometheus is only accessible from the GitLab server itself. +Each exporter will be automatically set up as a monitoring target for Prometheus, unless individually disabled. To disable Prometheus and all of its exporters, as well as any added in the future: @@ -44,14 +45,16 @@ To disable Prometheus and all of its exporters, as well as any added in the futu 1. Save the file and [reconfigure GitLab][reconfigure] for the changes to take effect -## Changing the port Prometheus listens on +## Changing the port and address Prometheus listens on >**Note:** The following change was added in [GitLab Omnibus 8.17][1261]. Although possible, -it's not recommended to change the default address and port Prometheus listens +it's not recommended to change the port Prometheus listens on as this might affect or conflict with other services running on the GitLab server. Proceed at your own risk. +In order to access Prometheus from outside the GitLab server you will need to +set a FQDN or IP in `prometheus['listen_address']`. To change the address/port that Prometheus listens on: 1. Edit `/etc/gitlab/gitlab.rb` @@ -80,9 +83,9 @@ You can visit `http://localhost:9090` for the dashboard that Prometheus offers b >**Note:** If SSL has been enabled on your GitLab instance, you may not be able to access -Prometheus on the same browser as GitLab due to [HSTS][hsts]. We plan to +Prometheus on the same browser as GitLab if using the same FQDN due to [HSTS][hsts]. We plan to [provide access via GitLab][multi-user-prometheus], but in the interim there are -some workarounds: using a separate browser for Prometheus, resetting HSTS, or +some workarounds: using a separate FQDN, using server IP, using a separate browser for Prometheus, resetting HSTS, or having [Nginx proxy it][nginx-custom-config]. The performance data collected by Prometheus can be viewed directly in the diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md index 6ec5baeb6e3..cfd601b8866 100644 --- a/doc/administration/raketasks/storage.md +++ b/doc/administration/raketasks/storage.md @@ -24,7 +24,6 @@ gitlab-rake gitlab:storage:migrate_to_hashed ```bash rake gitlab:storage:migrate_to_hashed - ``` You can monitor the progress in the _Admin > Monitoring > Background jobs_ screen. @@ -52,7 +51,6 @@ gitlab-rake gitlab:storage:legacy_projects ```bash rake gitlab:storage:legacy_projects - ``` ------ @@ -86,7 +84,6 @@ gitlab-rake gitlab:storage:hashed_projects ```bash rake gitlab:storage:hashed_projects - ``` ------ @@ -120,7 +117,6 @@ gitlab-rake gitlab:storage:legacy_attachments ```bash rake gitlab:storage:legacy_attachments - ``` ------ @@ -137,7 +133,6 @@ gitlab-rake gitlab:storage:list_legacy_attachments ```bash rake gitlab:storage:list_legacy_attachments - ``` ## List attachments on Hashed storage @@ -154,7 +149,6 @@ gitlab-rake gitlab:storage:hashed_attachments ```bash rake gitlab:storage:hashed_attachments - ``` ------ @@ -171,7 +165,6 @@ gitlab-rake gitlab:storage:list_hashed_attachments ```bash rake gitlab:storage:list_hashed_attachments - ``` [storage-types]: ../repository_storage_types.md diff --git a/doc/api/jobs.md b/doc/api/jobs.md index e4e48edd9a7..0fbfc7cf0fd 100644 --- a/doc/api/jobs.md +++ b/doc/api/jobs.md @@ -38,6 +38,7 @@ Example of response "size": 1000 }, "finished_at": "2015-12-24T17:54:27.895Z", + "artifacts_expire_at": "2016-01-23T17:54:27.895Z" "id": 7, "name": "teaspoon", "pipeline": { @@ -81,6 +82,7 @@ Example of response "created_at": "2015-12-24T15:51:21.727Z", "artifacts_file": null, "finished_at": "2015-12-24T17:54:24.921Z", + "artifacts_expire_at": "2016-01-23T17:54:24.921Z", "id": 6, "name": "rspec:other", "pipeline": { @@ -152,6 +154,7 @@ Example of response "size": 1000 }, "finished_at": "2015-12-24T17:54:27.895Z", + "artifacts_expire_at": "2016-01-23T17:54:27.895Z" "id": 7, "name": "teaspoon", "pipeline": { @@ -195,6 +198,7 @@ Example of response "created_at": "2015-12-24T15:51:21.727Z", "artifacts_file": null, "finished_at": "2015-12-24T17:54:24.921Z", + "artifacts_expire_at": "2016-01-23T17:54:24.921Z" "id": 6, "name": "rspec:other", "pipeline": { @@ -261,6 +265,7 @@ Example of response "created_at": "2015-12-24T15:51:21.880Z", "artifacts_file": null, "finished_at": "2015-12-24T17:54:31.198Z", + "artifacts_expire_at": "2016-01-23T17:54:31.198Z", "id": 8, "name": "rubocop", "pipeline": { diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 4e34831422a..8849f490c4f 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -107,6 +107,7 @@ Parameters: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "time_stats": { "time_estimate": 0, @@ -226,6 +227,7 @@ Parameters: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "time_stats": { @@ -305,6 +307,7 @@ Parameters: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "time_stats": { @@ -541,7 +544,8 @@ POST /projects/:id/merge_requests | `labels` | string | no | Labels for MR as a comma-separated list | | `milestone_id` | integer | no | The global ID of a milestone | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | -| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | +| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | +| `squash` | boolean | no | Squash commits into a single commit when merging | ```json { @@ -595,6 +599,7 @@ POST /projects/:id/merge_requests "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "allow_maintainer_to_push": false, @@ -627,6 +632,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid | `description` | string | no | Description of MR | | `state_event` | string | no | New state (close/reopen) | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | +| `squash` | boolean | no | Squash commits into a single commit when merging | | `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. | | `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | @@ -683,6 +689,7 @@ Must include at least one non-required attribute from above. "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "allow_maintainer_to_push": false, @@ -790,6 +797,7 @@ Parameters: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "time_stats": { @@ -868,6 +876,7 @@ Parameters: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1", "discussion_locked": false, "time_stats": { @@ -1200,6 +1209,7 @@ Example response: "changes_count": "1", "should_remove_source_branch": true, "force_remove_source_branch": false, + "squash": false, "web_url": "http://example.com/example/example/merge_requests/1" }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", diff --git a/doc/api/settings.md b/doc/api/settings.md index e06b1bfb6df..36a0782d8f2 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -55,6 +55,7 @@ Example response: "ed25519_key_restriction": 0, "enforce_terms": true, "terms": "Hello world!", + "performance_bar_allowed_group_id": 42 } ``` @@ -120,8 +121,9 @@ PUT /application/settings | `metrics_timeout` | integer | yes (if `metrics_enabled` is `true`) | The amount of seconds after which InfluxDB will time out. | | `password_authentication_enabled_for_web` | boolean | no | Enable authentication for the web interface via a GitLab account password. Default is `true`. | | `password_authentication_enabled_for_git` | boolean | no | Enable authentication for Git over HTTP(S) via a GitLab account password. Default is `true`. | -| `performance_bar_allowed_group_id` | string | no | The group that is allowed to enable the performance bar | -| `performance_bar_enabled` | boolean | no | Allow enabling the performance bar | +| `performance_bar_allowed_group_path` | string | no | Path of the group that is allowed to toggle the performance bar | +| `performance_bar_allowed_group_id` | string | no | Deprecated: Use `performance_bar_allowed_group_path` instead. Path of the group that is allowed to toggle the performance bar | +| `performance_bar_enabled` | boolean | no | Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance bar | | `plantuml_enabled` | boolean | no | Enable PlantUML integration. Default is `false`. | | `plantuml_url` | string | yes (if `plantuml_enabled` is `true`) | The PlantUML instance URL for integration. | | `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling. | @@ -133,7 +135,7 @@ PUT /application/settings | `repository_checks_enabled` | boolean | no | GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues. | | `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. | | `require_two_factor_authentication` | boolean | no | Require all users to setup Two-factor authentication | -| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is null which means there is no restriction. | +| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is null which means there is no restriction. | | `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. | | `send_user_confirmation_email` | boolean | no | Send confirmation email on sign-up | | `sentry_dsn` | string | yes (if `sentry_enabled` is true) | Sentry Data Source Name | @@ -201,5 +203,6 @@ Example response: "ed25519_key_restriction": 0, "enforce_terms": true, "terms": "Hello world!", + "performance_bar_allowed_group_id": 42 } ``` diff --git a/doc/ci/README.md b/doc/ci/README.md index 8d1d72c2a2b..7666219acb0 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -19,7 +19,7 @@ Here's some info we've gathered to get you started. The first steps towards your GitLab CI/CD journey. - [Getting started with GitLab CI/CD](quick_start/README.md): understand how GitLab CI/CD works. -- GitLab CI/CD configuration file: [`.gitlab-ci.yml`](yaml/README.md) - Learn all about the ins and outs of `.gitlab-ci.yml`. +- [GitLab CI/CD configuration file: `.gitlab-ci.yml`](yaml/README.md) - Learn all about the ins and outs of `.gitlab-ci.yml`. - [Pipelines and jobs](pipelines.md): configure your GitLab CI/CD pipelines to build, test, and deploy your application. - Runners: The [GitLab Runner](https://docs.gitlab.com/runner/) is responsible by running the jobs in your CI/CD pipeline. On GitLab.com, Shared Runners are enabled by default, so you don't need to set up anything to start to use them with GitLab CI/CD. @@ -46,7 +46,9 @@ you don't need to set up anything to start to use them with GitLab CI/CD. ## Exploring GitLab CI/CD - [CI/CD Variables](variables/README.md) - Learn how to use variables defined in - your `.gitlab-ci.yml` or secured ones defined in your project's settings + your `.gitlab-ci.yml` or the ones defined in your project's settings + - [Where variables can be used](variables/where_variables_can_be_used.md) - A + deeper look on where and how the CI/CD variables can be used - **The permissions model** - Learn about the access levels a user can have for performing certain CI actions - [User permissions](../user/permissions.md#gitlab-ci) diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md index 7102af5c529..985ec4b972c 100644 --- a/doc/ci/autodeploy/index.md +++ b/doc/ci/autodeploy/index.md @@ -1,129 +1 @@ -# Auto Deploy - -> [Introduced][mr-8135] in GitLab 8.15. -> Auto deploy is an experimental feature and is **not recommended for Production use** at this time. - -> As of GitLab 9.1, access to the container registry is only available while the -Pipeline is running. Restarting a pod, scaling a service, or other actions which -require on-going access **will fail**. On-going secure access is planned for a -subsequent release. - -> As of GitLab 10.0, Auto Deploy templates are **deprecated** and the -functionality has been included in [Auto -DevOps](../../topics/autodevops/index.md). - -Auto deploy is an easy way to configure GitLab CI for the deployment of your -application. GitLab Community maintains a list of `.gitlab-ci.yml` -templates for various infrastructure providers and deployment scripts -powering them. These scripts are responsible for packaging your application, -setting up the infrastructure and spinning up necessary services (for -example a database). - -## How it works - -The Autodeploy templates are based on the [kubernetes-deploy][kube-deploy] -project which is used to simplify the deployment process to Kubernetes by -providing intelligent `build`, `deploy`, and `destroy` commands which you can -use in your `.gitlab-ci.yml` as is. It uses [Herokuish](https://github.com/gliderlabs/herokuish), -which uses [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) -to do some of the work, plus some of GitLab's own tools to package it all up. For -your convenience, a [Docker image][kube-image] is also provided. - -You can use the [Kubernetes project service](../../user/project/integrations/kubernetes.md) -to store credentials to your infrastructure provider and they will be available -during the deployment. - -## Quick start - -We made a [simple guide](quick_start_guide.md) to using Auto Deploy with GitLab.com. - -For a demonstration of GitLab Auto Deploy, read the blog post [Auto Deploy from GitLab to an OpenShift Container Cluster](https://about.gitlab.com/2017/05/16/devops-containers-gitlab-openshift/) - -## Supported templates - -The list of supported auto deploy templates is available in the -[gitlab-ci-yml project][auto-deploy-templates]. - -## Configuration - ->**Note:** -In order to understand why the following steps are required, read the -[how it works](#how-it-works) section. - -To configure Autodeploy, you will need to: - -1. Enable a deployment [project service][project-services] to store your - credentials. For example, if you want to deploy to OpenShift you have to - enable [Kubernetes service][kubernetes-service]. -1. Configure GitLab Runner to use the - [Docker or Kubernetes executor](https://docs.gitlab.com/runner/executors/) with - [privileged mode enabled][docker-in-docker]. -1. Navigate to the "Project" tab and click "Set up auto deploy" button. -  -1. Select a template. -  -1. Commit your changes and create a merge request. -1. Test your deployment configuration using a [Review App][review-app] that was - created automatically for you. - -## Private project support - -> Experimental support [introduced][mr-2] in GitLab 9.1. - -When a project has been marked as private, GitLab's [Container Registry][container-registry] requires authentication when downloading containers. Auto deploy will automatically provide the required authentication information to Kubernetes, allowing temporary access to the registry. Authentication credentials will be valid while the pipeline is running, allowing for a successful initial deployment. - -After the pipeline completes, Kubernetes will no longer be able to access the container registry. Restarting a pod, scaling a service, or other actions which require on-going access to the registry will fail. On-going secure access is planned for a subsequent release. - -## PostgreSQL database support - -> Experimental support [introduced][mr-8] in GitLab 9.1. - -In order to support applications that require a database, [PostgreSQL][postgresql] is provisioned by default. Credentials to access the database are preconfigured, but can be customized by setting the associated [variables](#postgresql-variables). These credentials can be used for defining a `DATABASE_URL` of the format: `postgres://user:password@postgres-host:postgres-port/postgres-database`. It is important to note that the database itself is temporary, and contents will be not be saved. - -PostgreSQL provisioning can be disabled by setting the variable `DISABLE_POSTGRES` to `"yes"`. - -The following PostgreSQL variables are supported: - -1. `DISABLE_POSTGRES: "yes"`: disable automatic deployment of PostgreSQL -1. `POSTGRES_USER: "my-user"`: use custom username for PostgreSQL -1. `POSTGRES_PASSWORD: "password"`: use custom password for PostgreSQL -1. `POSTGRES_DB: "my database"`: use custom database name for PostgreSQL - -## Auto Monitoring - -> Introduced in [GitLab 9.5](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438). - -Apps auto-deployed using one the [Kubernetes templates](#supported-templates) can also be automatically monitored for: - -* Response Metrics: latency, throughput, error rate -* System Metrics: CPU utilization, memory utilization - -Metrics are gathered from [nginx-ingress](../../user/project/integrations/prometheus_library/nginx_ingress.md) and [Kubernetes](../../user/project/integrations/prometheus_library/kubernetes.md). - -To view the metrics, open the [Monitoring dashboard for a deployed environment](../environments.md#monitoring-environments). - - - -### Configuring Auto Monitoring - -If GitLab has been deployed using the [omnibus-gitlab](../../install/kubernetes/gitlab_omnibus.md) Helm chart, no configuration is required. - -If you have installed GitLab using a different method: - -1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster -1. If you would like response metrics, ensure you are running at least version 0.9.0 of NGINX Ingress and [enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml). -1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) the NGINX Ingress deployment to be scraped by Prometheus using `prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`. - -[mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135 -[mr-2]: https://gitlab.com/gitlab-examples/kubernetes-deploy/merge_requests/2 -[mr-8]: https://gitlab.com/gitlab-examples/kubernetes-deploy/merge_requests/8 -[project-settings]: https://docs.gitlab.com/ce/public_access/public_access.html -[project-services]: ../../user/project/integrations/project_services.md -[auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy -[kubernetes-service]: ../../user/project/integrations/kubernetes.md -[docker-in-docker]: ../docker/using_docker_build.md#use-docker-in-docker-executor -[review-app]: ../review_apps/index.md -[kube-image]: https://gitlab.com/gitlab-examples/kubernetes-deploy/container_registry "Kubernetes deploy Container Registry" -[kube-deploy]: https://gitlab.com/gitlab-examples/kubernetes-deploy "Kubernetes deploy example project" -[container-registry]: https://docs.gitlab.com/ce/user/project/container_registry.html -[postgresql]: https://www.postgresql.org/ +This document was moved to [another location](../../topics/autodevops/index.md#auto-deploy). diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 3a491f0073c..7f034409580 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -24,7 +24,7 @@ Environments are like tags for your CI jobs, describing where code gets deployed Deployments are created when [jobs] deploy versions of code to environments, so every environment can have one or more deployments. GitLab keeps track of your deployments, so you always know what is currently being deployed on your -servers. If you have a deployment service such as [Kubernetes][kubernetes-service] +servers. If you have a deployment service such as [Kubernetes][kube] enabled for your project, you can use it to assist with your deployments, and can even access a [web terminal](#web-terminals) for your environment from within GitLab! @@ -246,23 +246,14 @@ As the name suggests, it is possible to create environments on the fly by just declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is the basis of [Review apps](review_apps/index.md). ->**Note:** -The `name` and `url` parameters can use most of the defined CI variables, -including predefined, secure variables and `.gitlab-ci.yml` -[`variables`](yaml/README.md#variables). You however cannot use variables -defined under `script` or on the Runner's side. There are other variables that -are unsupported in environment name context: -- `CI_PIPELINE_ID` -- `CI_JOB_ID` -- `CI_JOB_TOKEN` -- `CI_BUILD_ID` -- `CI_BUILD_TOKEN` -- `CI_REGISTRY_USER` -- `CI_REGISTRY_PASSWORD` -- `CI_REPOSITORY_URL` -- `CI_ENVIRONMENT_URL` -- `CI_DEPLOY_USER` -- `CI_DEPLOY_PASSWORD` +NOTE: **Note:** +The `name` and `url` parameters can use most of the CI/CD variables, +including [predefined](variables/README.md#predefined-variables-environment-variables), +[secret](variables/README.md#secret-variables) and +[`.gitlab-ci.yml` variables](yaml/README.md#variables). You however cannot use variables +defined under `script` or on the Runner's side. There are also other variables that +are unsupported in the context of `environment:name`. You can read more about +[where variables can be used](variables/where_variables_can_be_used.md). GitLab Runner exposes various [environment variables][variables] when a job runs, and as such, you can use them as environment names. Let's add another job in @@ -605,7 +596,7 @@ Web terminals were added in GitLab 8.15 and are only available to project masters and owners. If you deploy to your environments with the help of a deployment service (e.g., -the [Kubernetes service][kubernetes-service]), GitLab can open +the [Kubernetes integration][kube]), GitLab can open a terminal session to your environment! This is a very powerful feature that allows you to debug issues without leaving the comfort of your web browser. To enable it, just follow the instructions given in the service integration @@ -671,7 +662,6 @@ Below are some links you may find interesting: [Pipelines]: pipelines.md [jobs]: yaml/README.md#jobs [yaml]: yaml/README.md -[kubernetes-service]: ../user/project/integrations/kubernetes.md [environments]: #environments [deployments]: #deployments [permissions]: ../user/permissions.md @@ -683,5 +673,5 @@ Below are some links you may find interesting: [gitlab-flow]: ../workflow/gitlab_flow.md [gitlab runner]: https://docs.gitlab.com/runner/ [git-strategy]: yaml/README.md#git-strategy -[kube]: ../user/project/integrations/kubernetes.md +[kube]: ../user/project/clusters/index.md [prom]: ../user/project/integrations/prometheus.md diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index aedf7958c8a..f10423b92cf 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -22,6 +22,12 @@ For example, if you define `API_TOKEN=secure` as a secret variable and `API_TOKEN=yaml` in your `.gitlab-ci.yml`, the `API_TOKEN` will take the value `secure` as the secret variables are higher in the chain. +## Unsupported variables + +There are cases where some variables cannot be used in the context of a +`.gitlab-ci.yml` definition (for example under `script`). Read more +about which variables are [not supported](where_variables_can_be_used.md). + ## Predefined variables (Environment variables) Some of the predefined environment variables are available only if a minimum @@ -36,6 +42,7 @@ future GitLab releases.** | Variable | GitLab | Runner | Description | |-------------------------------- |--------|--------|-------------| +| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job | | **CI** | all | 0.4 | Mark that job is executed in CI environment | | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | | **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. | @@ -46,6 +53,8 @@ future GitLab releases.** | **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. | | **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` | | **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | +| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| +| **CI_DEPLOY_PASSWORD** | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| | **CI_DISPOSABLE_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. | | **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job | | **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. | @@ -82,16 +91,13 @@ future GitLab releases.** | **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs | | **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs | | **CI_SHARED_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. | -| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job | | **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job | | **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment | -| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job | +| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job | | **GITLAB_USER_LOGIN** | 10.0 | all | The login username of the user who started the job | | **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job | | **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job | -| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| -| **CI_DEPLOY_PASSWORD** | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.| ## 9.0 Renaming @@ -215,8 +221,8 @@ are set in the build environment. These variables are only defined for [deployment jobs](../environments.md). Please consult the documentation of the project services that you are using to learn which variables they define. -An example project service that defines deployment variables is -[Kubernetes Service](../../user/project/integrations/kubernetes.md#deployment-variables). +An example project service that defines deployment variables is the +[Kubernetes integration](../../user/project/clusters/index.md#deployment-variables). ## Debug tracing @@ -540,34 +546,6 @@ Below you can find supported syntax reference: Pattern matching is case-sensitive by default. Use `i` flag modifier, like `/pattern/i` to make a pattern case-insensitive. -### Unsupported predefined variables - -Because GitLab evaluates variables before creating jobs, we do not support a -few variables that depend on persistence layer, like `$CI_JOB_ID`. - -Environments (like `production` or `staging`) are also being created based on -what jobs pipeline consists of, thus some environment-specific variables are -not supported as well. - -We do not support variables containing tokens because of security reasons. - -You can find a full list of unsupported variables below: - -- `CI_PIPELINE_ID` -- `CI_JOB_ID` -- `CI_JOB_TOKEN` -- `CI_BUILD_ID` -- `CI_BUILD_TOKEN` -- `CI_REGISTRY_USER` -- `CI_REGISTRY_PASSWORD` -- `CI_REPOSITORY_URL` -- `CI_ENVIRONMENT_URL` -- `CI_DEPLOY_USER` -- `CI_DEPLOY_PASSWORD` - -These variables are also not supported in a context of a -[dynamic environment name][dynamic-environments]. - [ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784 "Simple protection of CI secret variables" [eep]: https://about.gitlab.com/products/ "Available only in GitLab Premium" [envs]: ../environments.md @@ -579,5 +557,4 @@ These variables are also not supported in a context of a [triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger [subgroups]: ../../user/group/subgroups/index.md [builds-policies]: ../yaml/README.md#only-and-except-complex -[dynamic-environments]: ../environments.md#dynamic-environments [gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md new file mode 100644 index 00000000000..9800784d918 --- /dev/null +++ b/doc/ci/variables/where_variables_can_be_used.md @@ -0,0 +1,113 @@ +# Where variables can be used + +As it's described in the [CI/CD variables](README.md) docs, you can +define many different variables. Some of them can be used for all GitLab CI/CD +features, but some of them are more or less limited. + +This document describes where and how the different types of variables can be used. + +## Variables usage + +There are basically two places where you can use any defined variables: + +1. On GitLab's side there's `.gitlab-ci.yml` +1. On the Runner's side there's `config.toml` + +### `.gitlab-ci.yml` file + +| Definition | Can be expanded? | Expansion place | Description | +|--------------------------------------|-------------------|-----------------|--------------| +| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>**Supported:** all variables defined for a job (secret variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>**Not suported:** variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> | +| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion **doesn't support**: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> | +| `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `services:[]:name` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment | +| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) | +| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>**Not supported:**<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> | + +### `config.toml` file + +NOTE: **Note:** +You can read more about `config.toml` in the [Runner's docs](https://docs.gitlab.com/runner/configuration/advanced-configuration.html). + +| Definition | Can be expanded? | Description | +|--------------------------------------|------------------|-------------| +| `runners.environment` | yes | The variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `runners.kubernetes.pod_labels` | yes | The Variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | +| `runners.kubernetes.pod_annotations` | yes | The Variable expansion is made by the Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | + +## Expansion mechanisms + +There are three expansion mechanisms: + +- GitLab +- GitLab Runner +- Execution shell environment + +### GitLab internal variable expansion mechanism + +The expanded part needs to be in a form of `$variable`, or `${variable}` or `%variable%`. +Each form is handled in the same way, no matter which OS/shell will finally handle the job, +since the expansion is done in GitLab before any Runner will get the job. + +### GitLab Runner internal variable expansion mechanism + +- **Supported:** secret variables, `.gitlab-ci.yml` variables, `config.toml` variables, and + variables from triggers and pipeline schedules +- **Not supported:** variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`) + +The Runner uses Go's `os.Expand()` method for variable expansion. It means that it will handle +only variables defined as `$variable` and `${variable}`. What's also important, is that +the expansion is done only once, so nested variables may or may not work, depending on the +ordering of variables definitions. + +### Execution shell environment + +This is an expansion that takes place during the `script` execution. +How it works depends on the used shell (bash/sh/cmd/PowerShell). For example, if the job's +`script` contains a line `echo $MY_VARIABLE-${MY_VARIABLE_2}`, it should be properly handled +by bash/sh (leaving empty strings or some values depending whether the variables were +defined or not), but will not work with Windows' cmd/PowerShell, since these shells +are using a different variables syntax. + +**Supported:** + +- The `script` may use all available variables that are default for the shell (e.g., `$PATH` which + should be present in all bash/sh shells) and all variables defined by GitLab CI/CD (secret variables, + `.gitlab-ci.yml` variables, `config.toml` variables, and variables from triggers and pipeline schedules). +- The `script` may also use all variables defined in the lines before. So, for example, if you define + a variable `export MY_VARIABLE="test"`: + + - in `before_script`, it will work in the following lines of `before_script` and + all lines of the related `script` + - in `script`, it will work in the following lines of `script` + - in `after_script`, it will work in following lines of `after_script` + +## Persisted variables + +NOTE: **Note:** +Some of the persisted variables contain tokens and cannot be used by some definitions +due to security reasons. + +The following variables are known as "persisted": + +- `CI_PIPELINE_ID` +- `CI_JOB_ID` +- `CI_JOB_TOKEN` +- `CI_BUILD_ID` +- `CI_BUILD_TOKEN` +- `CI_REGISTRY_USER` +- `CI_REGISTRY_PASSWORD` +- `CI_REPOSITORY_URL` +- `CI_DEPLOY_USER` +- `CI_DEPLOY_PASSWORD` + +They are: + +- **supported** for all definitions as [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "Runner" +- **not supported:** + - by the definitions [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "GitLab" + - in the `only` and `except` [variables expressions](README.md#variables-expressions) diff --git a/doc/development/code_review.md b/doc/development/code_review.md index d03b7fa23ca..23c80799235 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -22,7 +22,7 @@ There are a few rules to get your merge request accepted: 1. If your merge request includes UX, frontend and backend changes [^1], it must be **approved by a [UX team member, a frontend and a backend maintainer][team]**. 1. If your merge request includes a new dependency or a filesystem change, it must - be **approved by a [Build team member][team]**. See [how to work with the Build team][build handbook] for more details. + be *approved by a [Distribution team member][team]*. See how to work with the [Distribution team for more details.](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) 1. To lower the amount of merge requests maintainers need to review, you can ask or assign any [reviewers][projects] for a first review. 1. If you need some guidance (e.g. it's your first merge request), feel free diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 057a4094aed..7f061d06da8 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -368,27 +368,17 @@ resolve when you add the indentation to the equation. EE-specific views should be placed in `ee/app/views/`, using extra sub-directories if appropriate. +#### Using `render_if_exists` + Instead of using regular `render`, we should use `render_if_exists`, which will not render anything if it cannot find the specific partial. We use this so that we could put `render_if_exists` in CE, keeping code the same between CE and EE. -Also, it should search for the EE partial first, and then CE partial, and -then if nothing found, render nothing. - -This has two uses: - -- CE renders nothing, and EE renders its EE partial. -- CE renders its CE partial, and EE renders its EE partial, while the view - file stays the same. - The advantages of this: - Minimal code difference between CE and EE. - Very clear hints about where we're extending EE views while reading CE codes. -- Whenever we want to show something different in CE, we could just add CE - partials. Same applies the other way around. If we just use - `render_if_exists`, it would be very easy to change the content in EE. The disadvantage of this: @@ -396,6 +386,42 @@ The disadvantage of this: port `render_if_exists` to CE. - If we have typos in the partial name, it would be silently ignored. +#### Using `render_ce` + +For `render` and `render_if_exists`, they search for the EE partial first, +and then CE partial. They would only render a particular partial, not all +partials with the same name. We could take the advantage of this, so that +the same partial path (e.g. `shared/issuable/form/default_templates`) could +be referring to the CE partial in CE (i.e. +`app/views/shared/issuable/form/_default_templates.html.haml`), while EE +partial in EE (i.e. +`ee/app/views/shared/issuable/form/_default_templates.html.haml`). This way, +we could show different things between CE and EE. + +However sometimes we would also want to reuse the CE partial in EE partial +because we might just want to add something to the existing CE partial. We +could workaround this by adding another partial with a different name, but it +would be tedious to do so. + +In this case, we could as well just use `render_ce` which would ignore any EE +partials. One example would be +`ee/app/views/shared/issuable/form/_default_templates.html.haml`: + +``` haml +- if @project.feature_available?(:issuable_default_templates) + = render_ce 'shared/issuable/form/default_templates' +- elsif show_promotions? + = render 'shared/promotions/promote_issue_templates' +``` + +In the above example, we can't use +`render 'shared/issuable/form/default_templates'` because it would find the +same EE partial, causing infinite recursion. Instead, we could use `render_ce` +so it ignores any partials in `ee/` and then it would render the CE partial +(i.e. `app/views/shared/issuable/form/_default_templates.html.haml`) +for the same path (i.e. `shared/issuable/form/default_templates`). This way +we could easily wrap around the CE partial. + ### Code in `lib/` Place EE-specific logic in the top-level `EE` module namespace. Namespace the diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md index b469a9c6aef..3d8da6accc1 100644 --- a/doc/development/fe_guide/icons.md +++ b/doc/development/fe_guide/icons.md @@ -1,26 +1,44 @@ -# Icons +# Icons and SVG Illustrations -We are using SVG Icons in GitLab with a SVG Sprite, due to this the icons are only loaded once and then referenced through an ID. The sprite SVG is located under `/assets/icons.svg`. Our goal is to replace one by one all inline SVG Icons (as those currently bloat the HTML) and also all Font Awesome usages. +We manage our own Icon and Illustration library in the [gitlab-svgs][gitlab-svgs] repository. +This repository is published on [npm][npm] and managed as a dependency via yarn. +You can browse all available Icons and Illustrations [here][svg-preview]. +To upgrade to a new version run `yarn upgrade @gitlab-org/gitlab-svgs`. -### Usage in HAML/Rails +## Icons -To use a sprite Icon in HAML or Rails we use a specific helper function : +We are using SVG Icons in GitLab with a SVG Sprite. +This means the icons are only loaded once, and are referenced through an ID. +The sprite SVG is located under `/assets/icons.svg`. + +Our goal is to replace one by one all inline SVG Icons (as those currently bloat the HTML) and also all Font Awesome icons. -`sprite_icon(icon_name, size: nil, css_class: '')` +### Usage in HAML/Rails -**icon_name** Use the icon_name that you can find in the SVG Sprite ([Overview is available here](http://gitlab-org.gitlab.io/gitlab-svgs/)`). +To use a sprite Icon in HAML or Rails we use a specific helper function : -**size (optional)** Use one of the following sizes : 16,24,32,48,72 (this will be translated into a `s16` class) +```ruby +sprite_icon(icon_name, size: nil, css_class: '') +``` -**css_class (optional)** If you want to add additional css classes +- **icon_name** Use the icon_name that you can find in the SVG Sprite + ([Overview is available here][svg-preview]). +- **size (optional)** Use one of the following sizes : 16, 24, 32, 48, 72 (this will be translated into a `s16` class) +- **css_class (optional)** If you want to add additional css classes **Example** -`= sprite_icon('issues', size: 72, css_class: 'icon-danger')` +```haml += sprite_icon('issues', size: 72, css_class: 'icon-danger') +``` **Output from example above** -`<svg class="s72 icon-danger"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons.svg#issues"></use></svg>` +```html +<svg class="s72 icon-danger"> + <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons.svg#issues"></use> +</svg> +``` ### Usage in Vue @@ -28,33 +46,71 @@ We have a special Vue component for our sprite icons in `\vue_shared\components\ Sample usage : -`<icon - name="retry" - :size="32" - css-classes="top" - />` - -**name** Name of the Icon in the SVG Sprite ([Overview is available here](http://gitlab-org.gitlab.io/gitlab-svgs/)`). - -**size (optional)** Number value for the size which is then mapped to a specific CSS class (Available Sizes: 8,12,16,18,24,32,48,72 are mapped to `sXX` css classes) - -**css-classes (optional)** Additional CSS Classes to add to the svg tag. +```javascript +<script> +import Icon from "~/vue_shared/components/icon.vue" + +export default { + components: { + Icon, + }, +}; +<script> +<template> + <icon + name="issues" + :size="72" + css-classes="icon-danger" + /> +</template> +``` + +- **name** Name of the Icon in the SVG Sprite ([Overview is available here][svg-preview]). +- **size (optional)** Number value for the size which is then mapped to a specific CSS class + (Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` css classes) +- **css-classes (optional)** Additional CSS Classes to add to the svg tag. ### Usage in HTML/JS -Please use the following function inside JS to render an icon : +Please use the following function inside JS to render an icon: `gl.utils.spriteIcon(iconName)` -## Adding a new icon to the sprite +## SVG Illustrations -All Icons and Illustrations are managed in the [gitlab-svgs](https://gitlab.com/gitlab-org/gitlab-svgs) repository which is added as a dev-dependency. +Please use from now on for any SVG based illustrations simple `img` tags to show an illustration by simply using either `image_tag` or `image_path` helpers. +Please use the class `svg-content` around it to ensure nice rendering. -To upgrade to a new SVG Sprite version run `yarn upgrade @gitlab-org/gitlab-svgs`. +### Usage in HAML/Rails -# SVG Illustrations +**Example** -Please use from now on for any SVG based illustrations simple `img` tags to show an illustration by simply using either `image_tag` or `image_path` helpers. Please use the class `svg-content` around it to ensure nice rendering. The illustrations are also organised in the [gitlab-svgs](https://gitlab.com/gitlab-org/gitlab-svgs) repository (as they are then automatically optimised). +```haml +.svg-content + = image_tag 'illustrations/merge_requests.svg' +``` -**Example** +### Usage in Vue -`= image_tag 'illustrations/merge_requests.svg'` +To use an SVG illustrations in a template provide the path as a property and display it through a standard img tag. + +Component: + +```js +<script> +export default { + props: { + svgIllustrationPath: { + type: String, + required: true, + }, + }, +}; +<script> +<template> + <img :src="svgIllustrationPath" /> +</template> +``` + +[npm]: https://www.npmjs.com/package/@gitlab-org/gitlab-svgs +[gitlab-svgs]: https://gitlab.com/gitlab-org/gitlab-svgs +[svg-preview]: https://gitlab-org.gitlab.io/gitlab-svgs diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 6d3796e7560..11b9a2e6a64 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -54,8 +54,8 @@ Vuex specific design patterns and practices. ## [Axios](axios.md) Axios specific practices and gotchas. -## [Icons](icons.md) -How we use SVG for our Icons. +## [Icons and Illustrations](icons.md) +How we use SVG for our Icons and Illustrations. ## [Components](components.md) diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index f971d8b7388..e31ee087358 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -8,7 +8,7 @@ All new features built with Vue.js must follow a [Flux architecture][flux]. The main goal we are trying to achieve is to have only one data flow and only one data entry. In order to achieve this goal, you can either use [vuex](#vuex) or use the [store pattern][state-management], explained below: -Each Vue bundle needs a Store - where we keep all the data -,a Service - that we use to communicate with the server - and a main Vue component. +Each Vue bundle needs a Store - where we keep all the data -, a Service - that we use to communicate with the server - and a main Vue component. Think of the Main Vue Component as the entry point of your application. This is the only smart component that should exist in each Vue feature. @@ -17,7 +17,7 @@ This component is responsible for: 1. Calling the Store to store the data received 1. Mounting all the other components -  + You can also read about this architecture in vue docs about [state management][state-management] and about [one way data flow][one-way-data-flow]. @@ -51,14 +51,14 @@ of the new feature should be. The Store and the Service should be imported and initialized in this file and provided as a prop to the main component. -Don't forget to follow [these steps.][page_specific_javascript] +Don't forget to follow [these steps][page_specific_javascript]. ### Bootstrapping Gotchas -#### Providing data from Haml to JavaScript +#### Providing data from HAML to JavaScript While mounting a Vue application may be a need to provide data from Rails to JavaScript. To do that, provide the data through `data` attributes in the HTML element and query them while mounting the application. -_Note:_ You should only do this while initing the application, because the mounted element will be replaced with Vue-generated DOM. +_Note:_ You should only do this while initializing the application, because the mounted element will be replaced with Vue-generated DOM. The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function instead of querying the DOM inside the main vue component is that makes tests easier by avoiding the need to @@ -68,6 +68,7 @@ create a fixture or an HTML element in the unit test. See the following example: // haml .js-vue-app{ data: { endpoint: 'foo' }} +// index.js document.addEventListener('DOMContentLoaded', () => new Vue({ el: '.js-vue-app', data() { @@ -87,13 +88,11 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ``` #### Accessing the `gl` object -When we need to query the `gl` object for data that won't change during the application's lyfecyle, we should do it in the same place where we query the DOM. +When we need to query the `gl` object for data that won't change during the application's life cyle, we should do it in the same place where we query the DOM. By following this practice, we can avoid the need to mock the `gl` object, which will make tests easier. It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component: -##### example: ```javascript - document.addEventListener('DOMContentLoaded', () => new Vue({ el: '.js-vue-app', render(createElement) { @@ -121,25 +120,6 @@ in one table would not be a good use of this pattern. You can read more about components in Vue.js site, [Component System][component-system] -#### Components Gotchas -1. Using SVGs icons in components: To use an SVG icon in a template use the `icon.vue` -1. Using SVGs illustrations in components: To use an SVG illustrations in a template provide the path as a prop and display it through a standard img tag. - ```javascript - <script> - export default { - props: { - svgIllustrationPath: { - type: String, - required: true, - }, - }, - }; - <script> - <template> - <img :src="svgIllustrationPath" /> - </template> - ``` - ### A folder for the Store #### Vuex @@ -163,13 +143,13 @@ Refer to [axios](axios.md) for more details. Axios instance should only be imported in the service file. - ```javascript - import axios from 'javascripts/lib/utils/axios_utils'; - ``` +```javascript +import axios from '~/lib/utils/axios_utils'; +``` ### End Result -The following example shows an application: +The following example shows an application: ```javascript // store.js @@ -177,8 +157,8 @@ export default class Store { /** * This is where we will iniatialize the state of our data. - * Usually in a small SPA you don't need any options when starting the store. In the case you do - * need guarantee it's an Object and it's documented. + * Usually in a small SPA you don't need any options when starting the store. + * In that case you do need guarantee it's an Object and it's documented. * * @param {Object} options */ @@ -186,7 +166,7 @@ export default class Store { this.options = options; // Create a state object to handle all our data in the same place - this.todos = []: + this.todos = []; } setTodos(todos = []) { @@ -207,7 +187,7 @@ export default class Store { } // service.js -import axios from 'javascripts/lib/utils/axios_utils' +import axios from '~/lib/utils/axios_utils' export default class Service { constructor(options) { @@ -233,8 +213,8 @@ export default { type: Object, required: true, }, - } -} + }, +}; </script> <template> <div> @@ -275,7 +255,7 @@ export default { }, created() { - this.service = new Service('todos'); + this.service = new Service('/todos'); this.getTodos(); }, @@ -284,9 +264,9 @@ export default { getTodos() { this.isLoading = true; - this.service.getTodos() - .then(response => response.json()) - .then((response) => { + this.service + .getTodos() + .then(response => { this.store.setTodos(response); this.isLoading = false; }) @@ -296,18 +276,21 @@ export default { }); }, - addTodo(todo) { - this.service.addTodo(todo) - then(response => response.json()) - .then((response) => { - this.store.addTodo(response); - }) - .catch(() => { - // Show an error - }); - } - } -} + addTodo(event) { + this.service + .addTodo({ + title: 'New entry', + text: `You clicked on ${event.target.tagName}`, + }) + .then(response => { + this.store.addTodo(response); + }) + .catch(() => { + // Show an error + }); + }, + }, +}; </script> <template> <div class="container"> @@ -333,7 +316,7 @@ export default { <div> </template> -// bundle.js +// index.js import todoComponent from 'todos_main_component.vue'; new Vue({ @@ -365,76 +348,79 @@ Each Vue component has a unique output. This output is always present in the ren Although we can test each method of a Vue component individually, our goal must be to test the output of the render/template function, which represents the state at all times. -Make use of Vue Resource Interceptors to mock data returned by the service. +Make use of the [axios mock adapter](axios.md#mock-axios-response-on-tests) to mock data returned. Here's how we would test the Todo App above: ```javascript -import component from 'todos_main_component'; +import Vue from 'vue'; +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; describe('Todos App', () => { - it('should render the loading state while the request is being made', () => { + let vm; + let mock; + + beforeEach(() => { + // Create a mock adapter for stubbing axios API requests + mock = new MockAdapter(axios); + const Component = Vue.extend(component); - const vm = new Component().$mount(); + // Mount the Component + vm = new Component().$mount(); + }); + + afterEach(() => { + // Reset the mock adapter + mock.restore(); + // Destroy the mounted component + vm.$destroy(); + }); + it('should render the loading state while the request is being made', () => { expect(vm.$el.querySelector('i.fa-spin')).toBeDefined(); }); - describe('with data', () => { - // Mock the service to return data - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify([{ + it('should render todos returned by the endpoint', done => { + // Mock the get request on the API endpoint to return data + mock.onGet('/todos').replyOnce(200, [ + { title: 'This is a todo', - body: 'This is the text' - }]), { - status: 200, - })); - }; - - let vm; - - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - - const Component = Vue.extend(component); + text: 'This is the text', + }, + ]); - vm = new Component().$mount(); + Vue.nextTick(() => { + const items = vm.$el.querySelectorAll('.js-todo-list div') + expect(items.length).toBe(1); + expect(items[0].textContent).toContain('This is the text'); + done(); }); + }); - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); - }); + it('should add a todos on button click', (done) => { + // Mock the put request and check that the sent data object is correct + mock.onPut('/todos').replyOnce((req) => { + expect(req.data).toContain('text'); + expect(req.data).toContain('title'); - it('should render todos', (done) => { - setTimeout(() => { - expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(1); - done(); - }, 0); + return [201, {}]; }); - }); - describe('add todo', () => { - let vm; - beforeEach(() => { - const Component = Vue.extend(component); - vm = new Component().$mount(); - }); - it('should add a todos', (done) => { - setTimeout(() => { - vm.$el.querySelector('.js-add-todo').click(); + vm.$el.querySelector('.js-add-todo').click(); - // Add a new interceptor to mock the add Todo request - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(2); - }); - }, 0); + // Add a new interceptor to mock the add Todo request + Vue.nextTick(() => { + expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(2); + done(); }); }); }); ``` -#### `mountComponent` helper + +### `mountComponent` helper There is a helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props: ```javascript @@ -447,13 +433,10 @@ const data = {prop: 'foo'}; const vm = mountComponent(Component, data); ``` -#### Test the component's output +### Test the component's output The main return value of a Vue component is the rendered output. In order to test the component we need to test the rendered output. [Vue][vue-test] guide's to unit test show us exactly that: -### Stubbing API responses -Refer to [mock axios](axios.md#mock-axios-response-on-tests) - [vue-docs]: http://vuejs.org/guide/index.html [issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards @@ -466,4 +449,3 @@ Refer to [mock axios](axios.md#mock-axios-response-on-tests) [issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6 [flux]: https://facebook.github.io/flux [axios]: https://github.com/axios/axios -[axios-interceptors]: https://github.com/axios/axios#interceptors diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md index ed35f08432f..2a3a126ca5c 100644 --- a/doc/development/new_fe_guide/development/accessibility.md +++ b/doc/development/new_fe_guide/development/accessibility.md @@ -1,3 +1,48 @@ -# Accessibility +# Accessiblity +Using semantic HTML plays a key role when it comes to accessibility. -> TODO: Add content +## Accessible Rich Internet Applications - ARIA +WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities. + +> Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements. + +### Role +The `role` attribute describes the role the element plays in the context of the document. + +Check the list of WAI-ARIA roles [here][roles] + +## Icons +When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`. + +On the other hand, if an icon is crucial to understand the context we should do one of the following: +1. Use `aria-label` in the element with a meaningful description +1. Use `aria-labelledby` to point to an element that contains the explanation for that icon + +## Form inputs +In forms we should use the `for` attribute in the label statement: +``` +<div> + <label for="name">Fill in your name:</label> + <input type="text" id="name" name="name"> +</div> +``` + +## Testing + +1. On MacOS you can use [VoiceOver][voice-over] by pressing `cmd+F5`. +1. On Windows you can use [Narrator][narrator] by pressing Windows logo key + Ctrl + Enter. + +## Online resources + +- [Chrome Accessibility Developer Tools][dev-tools] for testing accessibility +- [Audit Rules Page][audit-rules] for best practices +- [Lighthouse Accessibility Score][lighthouse] for accessibility audits + +[using-aria]: https://www.w3.org/TR/using-aria/#notes2 +[dev-tools]: https://github.com/GoogleChrome/accessibility-developer-tools +[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules +[aria-w3c]: https://www.w3.org/TR/wai-aria-1.1/ +[roles]: https://www.w3.org/TR/wai-aria-1.1/#landmark_roles +[voice-over]: https://www.apple.com/accessibility/mac/vision/ +[narrator]: https://www.microsoft.com/en-us/accessibility/windows +[lighthouse]: https://developers.google.com/web/tools/lighthouse/scoring#a11y diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index b57520a00e0..4a3b3125f59 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -193,7 +193,7 @@ List with avatar, title and description using .content-list  -List with hover effect .well-list +List with hover effect .content-list  diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 557375a1da9..e63ed88249d 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -72,6 +72,8 @@ website with GitLab Pages **Other features:** +- [Wiki](wiki/index.md): Document your GitLab project in an integrated Wiki +- [Snippets](../snippets.md): Store, share and collaborate on code snippets - [Cycle Analytics](cycle_analytics.md): Review your development lifecycle - [Syntax highlighting](highlighting.md): An alternative to customize your code blocks, overriding GitLab's default choice of language diff --git a/doc/user/project/integrations/img/kubernetes_configuration.png b/doc/user/project/integrations/img/kubernetes_configuration.png Binary files differdeleted file mode 100644 index e535e2b8d46..00000000000 --- a/doc/user/project/integrations/img/kubernetes_configuration.png +++ /dev/null diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md index f502d1c9821..9342a2cbb00 100644 --- a/doc/user/project/integrations/kubernetes.md +++ b/doc/user/project/integrations/kubernetes.md @@ -1,137 +1 @@ ---- -last_updated: 2017-12-28 ---- - -# GitLab Kubernetes / OpenShift integration - -CAUTION: **Warning:** -The Kubernetes service integration has been deprecated in GitLab 10.3. If the -service is active, the cluster information will still be editable, however we -advise to disable and reconfigure the clusters using the new -[Clusters](../clusters/index.md) page. If the service is inactive, the fields -will not be editable. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information. - -GitLab can be configured to interact with Kubernetes, or other systems using the -Kubernetes API (such as OpenShift). - -Each project can be configured to connect to a different Kubernetes cluster, see -the [configuration](#configuration) section. - -## Configuration - -Navigate to the [Integrations page](project_services.md#accessing-the-project-services) -of your project and select the **Kubernetes** service to configure it. Fill in -all the needed parameters, check the "Active" checkbox and hit **Save changes** -for the changes to take effect. - - - -The Kubernetes service takes the following parameters: - -- **API URL** - - It's the URL that GitLab uses to access the Kubernetes API. Kubernetes - exposes several APIs, we want the "base" URL that is common to all of them, - e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`. -- **CA certificate** (optional) - - If the API is using a self-signed TLS certificate, you'll also need to include - the `ca.crt` contents here. -- **Project namespace** (optional) - The following apply: - - By default you don't have to fill it in; by leaving it blank, GitLab will - create one for you. - - Each project should have a unique namespace. - - The project namespace is not necessarily the namespace of the secret, if - you're using a secret with broader permissions, like the secret from `default`. - - You should **not** use `default` as the project namespace. - - If you or someone created a secret specifically for the project, usually - with limited permissions, the secret's namespace and project namespace may - be the same. -- **Token** - - GitLab authenticates against Kubernetes using service tokens, which are - scoped to a particular `namespace`. If you don't have a service token yet, - you can follow the - [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) - to create one. You can also view or create service tokens in the - [Kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#config) - (under **Config > Secrets**). - -TIP: **Tip:** -If you have a single cluster that you want to use for all your projects, -you can pre-fill the settings page with a default template. To configure the -template, see [Services Templates](services_templates.md). - -## Deployment variables - -The Kubernetes service exposes the following -[deployment variables](../../../ci/variables/README.md#deployment-variables) in the -GitLab CI/CD build environment: - -- `KUBE_URL` - Equal to the API URL. -- `KUBE_TOKEN` - The Kubernetes token. -- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified. - The default value is `<project_name>-<project_id>`. You can overwrite it to - use different one if needed, otherwise the `KUBE_NAMESPACE` variable will - receive the default value. -- `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path - to a file containing PEM data. -- `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data. -- `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment. - CA bundle would be embedded if specified. - -## What you can get with the Kubernetes integration - -Here's what you can do with GitLab if you enable the Kubernetes integration. - -### Deploy Boards - -> Available in [GitLab Premium][ee]. - -GitLab's Deploy Boards offer a consolidated view of the current health and -status of each CI [environment](../../../ci/environments.md) running on Kubernetes, -displaying the status of the pods in the deployment. Developers and other -teammates can view the progress and status of a rollout, pod by pod, in the -workflow they already use without any need to access Kubernetes. - -[> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) - -### Canary Deployments - -> Available in [GitLab Premium][ee]. - -Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments) -and visualize your canary deployments right inside the Deploy Board, without -the need to leave GitLab. - -[> Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) - -### Kubernetes monitoring - -Automatically detect and monitor Kubernetes metrics. Automatic monitoring of -[NGINX ingress](./prometheus_library/nginx.md) is also supported. - -[> Read more about Kubernetes monitoring](prometheus_library/kubernetes.md) - -### Auto DevOps - -Auto DevOps automatically detects, builds, tests, deploys, and monitors your -applications. - -To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring) -you will need the Kubernetes project integration enabled. - -[> Read more about Auto DevOps](../../../topics/autodevops/index.md) - -### Web terminals - -NOTE: **Note:** -Introduced in GitLab 8.15. You must be the project owner or have `master` permissions -to use terminals. Support is limited to the first container in the -first pod of your environment. - -When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals) -support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in -Docker and Kubernetes, so you get a new shell session within your existing -containers. To use this integration, you should deploy to Kubernetes using -the deployment variables above, ensuring any pods you create are labelled with -`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest! - -[ee]: https://about.gitlab.com/products/ +This document was moved to [another location](../clusters/index.md). diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md index 074eeb729e3..8c51eb9915e 100644 --- a/doc/user/project/integrations/project_services.md +++ b/doc/user/project/integrations/project_services.md @@ -39,7 +39,6 @@ Click on the service links to see further configuration instructions and details | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | [JIRA](jira.md) | JIRA issue tracker | | JetBrains TeamCity CI | A continuous integration and build server | -| [Kubernetes](kubernetes.md) _(Has been deprecated in GitLab 10.3)_ | A containerized deployment service | | [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands | | [Mattermost Notifications](mattermost.md) | Receive event notifications in Mattermost | | [Microsoft teams](microsoft_teams.md) | Receive notifications for actions that happen on GitLab into a room on Microsoft Teams using Office 365 Connectors | diff --git a/doc/user/project/merge_requests/img/squash_edit_form.png b/doc/user/project/merge_requests/img/squash_edit_form.png Binary files differnew file mode 100644 index 00000000000..496c6f44ea7 --- /dev/null +++ b/doc/user/project/merge_requests/img/squash_edit_form.png diff --git a/doc/user/project/merge_requests/img/squash_mr_commits.png b/doc/user/project/merge_requests/img/squash_mr_commits.png Binary files differnew file mode 100644 index 00000000000..5fc6a8c48bb --- /dev/null +++ b/doc/user/project/merge_requests/img/squash_mr_commits.png diff --git a/doc/user/project/merge_requests/img/squash_mr_widget.png b/doc/user/project/merge_requests/img/squash_mr_widget.png Binary files differnew file mode 100644 index 00000000000..9cb458b2a35 --- /dev/null +++ b/doc/user/project/merge_requests/img/squash_mr_widget.png diff --git a/doc/user/project/merge_requests/img/squash_squashed_commit.png b/doc/user/project/merge_requests/img/squash_squashed_commit.png Binary files differnew file mode 100644 index 00000000000..0cf5875f82c --- /dev/null +++ b/doc/user/project/merge_requests/img/squash_squashed_commit.png diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 5932f5a2bc1..b75bcacc9d7 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -29,12 +29,12 @@ With GitLab merge requests, you can: - Enable [semi-linear history merge requests](#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch - [Create new merge requests by email](#create-new-merge-requests-by-email) - Allow maintainers of the target project to push directly to the fork by [allowing edits from maintainers](maintainer_access.md) +- [Squash and merge](squash_and_merge.md) for a cleaner commit history With **[GitLab Enterprise Edition][ee]**, you can also: - View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) **[PREMIUM]** - Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers **[STARTER]** -- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history **[STARTER]** - Analyze the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) **[STARTER]** ## Use cases @@ -57,7 +57,7 @@ B. Consider you're a web developer writing a webpage for your company's: 1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md) 1. You request your web designers for their implementation 1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager **[STARTER]** -1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Starter) +1. Once approved, your merge request is [squashed and merged](squash_and_merge.md), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) 1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production ## Merge requests per project diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md new file mode 100644 index 00000000000..a6efe893853 --- /dev/null +++ b/doc/user/project/merge_requests/squash_and_merge.md @@ -0,0 +1,80 @@ +# Squash and merge + +> [Introduced][ee-1024] in [GitLab Starter][ee] 8.17, and in [GitLab CE][ce] [11.0][ce-18956]. + +Combine all commits of your merge request into one and retain a clean history. + +## Overview + +Squashing lets you tidy up the commit history of a branch when accepting a merge +request. It applies all of the changes in the merge request as a single commit, +and then merges that commit using the merge method set for the project. + +In other words, squashing a merge request turns a long list of commits: + +![List of commits from a merge request][mr-commits] + +Into a single commit on merge: + +![A squashed commit followed by a merge commit][squashed-commit] + +The squashed commit's commit message is the merge request title. And note that +the squashed commit is still followed by a merge commit, as the merge +method for this example repository uses a merge commit. Squashing also works +with the fast-forward merge strategy, see +[squashing and fast-forward merge](#squash-and-fast-forward-merge) for more +details. + +## Use cases + +When working on a feature branch, you sometimes want to commit your current +progress, but don't really care about the commit messages. Those 'work in +progress commits' don't necessarily contain important information and as such +you'd rather not include them in your target branch. + +With squash and merge, when the merge request is ready to be merged, +all you have to do is enable squashing before you press merge to join +the commits include in the merge request into a single commit. + +This way, the history of your base branch remains clean with +meaningful commit messages and is simpler to [revert] if necessary. + +## Enabling squash for a merge request + +Anyone who can create or edit a merge request can choose for it to be squashed +on the merge request form: + +![Squash commits checkbox on edit form][squash-edit-form] + +--- + +This can then be overridden at the time of accepting the merge request: + +![Squash commits checkbox on accept merge request form][squash-mr-widget] + +## Commit metadata for squashed commits + +The squashed commit has the following metadata: + +* Message: the title of the merge request. +* Author: the author of the merge request. +* Committer: the user who initiated the squash. + +## Squash and fast-forward merge + +When a project has the [fast-forward merge setting enabled][ff-merge], the merge +request must be able to be fast-forwarded without squashing in order to squash +it. This is because squashing is only available when accepting a merge request, +so a merge request may need to be rebased before squashing, even though +squashing can itself be considered equivalent to rebasing. + +[ee-1024]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1024 +[ce-18956]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18956 +[mr-commits]: img/squash_mr_commits.png +[squashed-commit]: img/squash_squashed_commit.png +[squash-edit-form]: img/squash_edit_form.png +[squash-mr-widget]: img/squash_mr_widget.png +[ff-merge]: fast_forward_merge.md#enabling-fast-forward-merges +[ce]: https://about.gitlab.com/products/ +[ee]: https://about.gitlab.com/products/ +[revert]: revert_changes.md diff --git a/doc/user/snippets.md b/doc/user/snippets.md index 8397c0b00ef..7efb6bafee7 100644 --- a/doc/user/snippets.md +++ b/doc/user/snippets.md @@ -1,34 +1,51 @@ # Snippets -Snippets are little bits of code or text. +With GitLab Snippets you can store and share bits of code and text with other users.  -There are 2 types of snippets - project snippets and personal snippets. +There are 2 types of snippets, personal snippets and project snippets. -## Comments - -With GitLab Snippets you engage in a conversation about that piece of code, -facilitating the collaboration among users. +## Personal snippets -> **Note:** -Comments on snippets was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/12910) in [GitLab Community Edition 9.2](https://about.gitlab.com/2017/05/22/gitlab-9-2-released/#comments-for-personal-snippets). +Personal snippets are not related to any project and can be created completely +independently. There are 3 visibility levels that can be set, public, internal +and private. See [Public access](../public_access/public_access.md) for more information. ## Project snippets -Project snippets are always related to a specific project - see [Project's features](project/index.md#project-39-s-features) for more information. +Project snippets are always related to a specific project. +See [Project's features](project/index.md#project-39-s-features) for more information. -## Personal snippets +## Discover snippets + +There are two main ways of how you can discover snippets in GitLab. -Personal snippets are not related to any project and can be created completely independently. There are 3 visibility levels that can be set (public, internal, private - see [Public Access](../public_access/public_access.md) for more information). +For exploring all snippets that are visible to you, you can go to the Snippets +dashboard of your GitLab instance via the top navigation. For GitLab.com you can +find it [here](https://gitlab.com/dashboard/snippets). This navigates you to an +overview that shows snippets you created and allows you to explore all snippets. + +If you want to discover snippets that belong to a specific project, you can navigate +to the Snippets page via the left side navigation on the project page. + +## Snippet comments + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/12910) in GitLab 9.2. + +With GitLab Snippets you engage in a conversation about that piece of code, +facilitating the collaboration among users. ## Downloading snippets You can download the raw content of a snippet. -By default snippets will be downloaded with Linux-style line endings (`LF`). If you want to preserve the original line endings you need to add a parameter `line_ending=raw` (eg. `https://gitlab.com/snippets/SNIPPET_ID/raw?line_ending=raw`). In case a snippet was created using the GitLab web interface the original line ending is Windows-like (`CRLF`). +By default snippets will be downloaded with Linux-style line endings (`LF`). If +you want to preserve the original line endings you need to add a parameter `line_ending=raw` +(e.g., `https://gitlab.com/snippets/SNIPPET_ID/raw?line_ending=raw`). In case a +snippet was created using the GitLab web interface the original line ending is Windows-like (`CRLF`). -## Embedded Snippets +## Embedded snippets > Introduced in GitLab 10.8. diff --git a/lib/api/api.rb b/lib/api/api.rb index de20b2b8e67..206fabe5c43 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -15,7 +15,8 @@ module API include: [ GrapeLogging::Loggers::FilterParameters.new, GrapeLogging::Loggers::ClientEnv.new, - Gitlab::GrapeLogging::Loggers::UserLogger.new + Gitlab::GrapeLogging::Loggers::UserLogger.new, + Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new ] allow_access_with_scope :api diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 70d43ac1d79..b7aadc27e71 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -148,10 +148,10 @@ module API requires :key_id, type: Integer, desc: 'The ID of the deploy key' end delete ":id/deploy_keys/:key_id" do - key = user_project.deploy_keys.find(params[:key_id]) - not_found!('Deploy Key') unless key + deploy_key_project = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id]) + not_found!('Deploy Key') unless deploy_key_project - destroy_conditionally!(key) + destroy_conditionally!(deploy_key_project) end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 174c5af91d5..c4537036a3a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -568,6 +568,8 @@ module API expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request| merge_request end + + expose :squash end class MergeRequest < MergeRequestBasic @@ -830,8 +832,8 @@ module API class ProjectWithAccess < Project expose :permissions do expose :project_access, using: Entities::ProjectAccess do |project, options| - if options.key?(:project_members) - (options[:project_members] || []).find { |member| member.source_id == project.id } + if options[:project_members] + options[:project_members].find { |member| member.source_id == project.id } else project.project_member(options[:current_user]) end @@ -839,8 +841,8 @@ module API expose :group_access, using: Entities::GroupAccess do |project, options| if project.group - if options.key?(:group_members) - (options[:group_members] || []).find { |member| member.source_id == project.namespace_id } + if options[:group_members] + options[:group_members].find { |member| member.source_id == project.namespace_id } else project.group.group_member(options[:current_user]) end @@ -851,13 +853,24 @@ module API def self.preload_relation(projects_relation, options = {}) relation = super(projects_relation, options) - unless options.key?(:group_members) - relation = relation.preload(group: [group_members: [:source, user: [notification_settings: :source]]]) + # MySQL doesn't support LIMIT inside an IN subquery + if Gitlab::Database.mysql? + project_ids = relation.pluck('projects.id') + namespace_ids = relation.pluck(:namespace_id) + else + project_ids = relation.select('projects.id') + namespace_ids = relation.select(:namespace_id) end - unless options.key?(:project_members) - relation = relation.preload(project_members: [:source, user: [notification_settings: :source]]) - end + options[:project_members] = options[:current_user] + .project_members + .where(source_id: project_ids) + .preload(:source, user: [notification_settings: :source]) + + options[:group_members] = options[:current_user] + .group_members + .where(source_id: namespace_ids) + .preload(:source, user: [notification_settings: :source]) relation end @@ -933,8 +946,16 @@ module API end class ApplicationSetting < Grape::Entity - expose :id - expose(*::ApplicationSettingsHelper.visible_attributes) + def self.exposed_attributes + attributes = ::ApplicationSettingsHelper.visible_attributes + attributes.delete(:performance_bar_allowed_group_path) + attributes.delete(:performance_bar_enabled) + + attributes + end + + expose :id, :performance_bar_allowed_group_id + expose(*exposed_attributes) expose(:restricted_visibility_levels) do |setting, _options| setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) } end @@ -1020,6 +1041,7 @@ module API class Job < JobBasic expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? } expose :runner, with: Runner + expose :artifacts_expire_at end class JobBasicWithProject < JobBasic diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index abe3d353984..83151be82ad 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -89,12 +89,6 @@ module API end end - # Return the repository full path so that gitlab-shell has it when - # handling ssh commands - def repository_path - repository.path_to_repo - end - # Return the Gitaly Address if it is enabled def gitaly_payload(action) return unless %w[git-receive-pack git-upload-pack git-upload-archive].include?(action) diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb index a2cbed30229..bc7333ca4b3 100644 --- a/lib/api/helpers/related_resources_helpers.rb +++ b/lib/api/helpers/related_resources_helpers.rb @@ -1,7 +1,7 @@ module API module Helpers module RelatedResourcesHelpers - include GrapeRouteHelpers::NamedRouteMatcher + include GrapePathHelpers::NamedRouteMatcher def issues_available?(project, options) available?(:issues, project, options[:current_user]) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index a3dac36b8b6..a9803be9f69 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -59,7 +59,11 @@ module API status: true, gl_repository: gl_repository, gl_username: user&.username, - repository_path: repository_path, + + # This repository_path is a bogus value but gitlab-shell still requires + # its presence. https://gitlab.com/gitlab-org/gitlab-shell/issues/135 + repository_path: '/', + gitaly: gitaly_payload(params[:action]) } end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index bc4df16e3a8..1ba9a09346f 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -10,12 +10,6 @@ module API helpers do params :optional_params_ee do end - - params :merge_params_ee do - end - - def update_merge_request_ee(merge_request) - end end def self.update_params_at_least_one_of @@ -29,6 +23,7 @@ module API target_branch title discussion_locked + squash ] end @@ -146,6 +141,7 @@ module API optional :labels, type: String, desc: 'Comma-separated list of label names' optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' optional :allow_maintainer_to_push, type: Boolean, desc: 'Whether a maintainer of the target project can push to the source project' + optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' use :optional_params_ee end @@ -308,8 +304,7 @@ module API optional :merge_when_pipeline_succeeds, type: Boolean, desc: 'When true, this merge request will be merged when the pipeline succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' - - use :merge_params_ee + optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' end put ':id/merge_requests/:merge_request_iid/merge' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42317') @@ -327,7 +322,7 @@ module API check_sha_param!(params, merge_request) - update_merge_request_ee(merge_request) + merge_request.update(squash: params[:squash]) if params[:squash] merge_params = { commit_message: params[:merge_commit_message], diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 8871792060b..3ef3680c5d9 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -58,16 +58,9 @@ module API projects = paginate(projects) projects, options = with_custom_attributes(projects, options) - if current_user - project_members = current_user.project_members.preload(:source, user: [notification_settings: :source]) - group_members = current_user.group_members.preload(:source, user: [notification_settings: :source]) - end - options = options.reverse_merge( with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, statistics: params[:statistics], - project_members: project_members, - group_members: group_members, current_user: current_user ) options[:with] = Entities::BasicProjectDetails if params[:simple] diff --git a/lib/api/runner.rb b/lib/api/runner.rb index a7f1cb1131f..5b7ae89440c 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -123,6 +123,7 @@ module API end put '/:id' do job = authenticate_job! + forbidden!('Job is not running') unless job.running? job.trace.set(params[:trace]) if params[:trace] @@ -131,9 +132,9 @@ module API case params[:state].to_s when 'success' - job.success + job.success! when 'failed' - job.drop(params[:failure_reason] || :unknown_failure) + job.drop!(params[:failure_reason] || :unknown_failure) end end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index e31c332b6e4..02ef89f997f 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -24,7 +24,7 @@ module API optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility' optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility' - optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' + optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources' @@ -49,6 +49,9 @@ module API optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled for the web interface' # support legacy names, can be removed in v5 mutually_exclusive :password_authentication_enabled_for_web, :password_authentication_enabled, :signin_enabled optional :password_authentication_enabled_for_git, type: Boolean, desc: 'Flag indicating if password authentication is enabled for Git over HTTP(S)' + optional :performance_bar_allowed_group_path, type: String, desc: 'Path of the group that is allowed to toggle the performance bar.' + optional :performance_bar_allowed_group_id, type: String, desc: 'Depreated: Use :performance_bar_allowed_group_path instead. Path of the group that is allowed to toggle the performance bar.' # support legacy names, can be removed in v6 + optional :performance_bar_enabled, type: String, desc: 'Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance.' # support legacy names, can be removed in v6 optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' given require_two_factor_authentication: ->(val) { val } do requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' @@ -126,6 +129,7 @@ module API optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.' optional :gitaly_timeout_medium, type: Integer, desc: 'Medium Gitaly timeout, in seconds. Set to 0 to disable timeouts.' optional :gitaly_timeout_fast, type: Integer, desc: 'Gitaly fast operation timeout, in seconds. Set to 0 to disable timeouts.' + optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", @@ -134,12 +138,25 @@ module API desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys." end - optional(*::ApplicationSettingsHelper.visible_attributes) - at_least_one_of(*::ApplicationSettingsHelper.visible_attributes) + optional_attributes = ::ApplicationSettingsHelper.visible_attributes << :performance_bar_allowed_group_id + + optional(*optional_attributes) + at_least_one_of(*optional_attributes) end put "application/settings" do attrs = declared_params(include_missing: false) + # support legacy names, can be removed in v6 + if attrs.has_key?(:performance_bar_allowed_group_id) + attrs[:performance_bar_allowed_group_path] = attrs.delete(:performance_bar_allowed_group_id) + end + + # support legacy names, can be removed in v6 + if attrs.has_key?(:performance_bar_enabled) + performance_bar_enabled = attrs.delete(:performance_bar_allowed_group_id) + attrs[:performance_bar_allowed_group_path] = nil unless performance_bar_enabled + end + # support legacy names, can be removed in v5 if attrs.has_key?(:signin_enabled) attrs[:password_authentication_enabled_for_web] = attrs.delete(:signin_enabled) diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index 68b4d7c3982..28fcf6c6e84 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -134,6 +134,8 @@ module API expose :should_remove_source_branch?, as: :should_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch + expose :squash + expose :web_url do |merge_request, options| Gitlab::UrlBuilder.build(merge_request) end diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb index 9b0f70e2bfe..af5afd1c334 100644 --- a/lib/api/v3/merge_requests.rb +++ b/lib/api/v3/merge_requests.rb @@ -44,6 +44,7 @@ module API optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' optional :labels, type: String, desc: 'Comma-separated list of label names' optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' + optional :squash, type: Boolean, desc: 'Squash commits when merging' end end @@ -166,7 +167,7 @@ module API use :optional_params at_least_one_of :title, :target_branch, :description, :assignee_id, :milestone_id, :labels, :state_event, - :remove_source_branch + :remove_source_branch, :squash end put path do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42127') @@ -195,6 +196,7 @@ module API optional :merge_when_build_succeeds, type: Boolean, desc: 'When true, this merge request will be merged when the build succeeds' optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch' + optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' end put "#{path}/merge" do merge_request = find_project_merge_request(params[:merge_request_id]) @@ -211,6 +213,8 @@ module API render_api_error!("SHA does not match HEAD of source branch: #{merge_request.diff_head_sha}", 409) end + merge_request.update(squash: params[:squash]) if params[:squash] + merge_params = { commit_message: params[:merge_commit_message], should_remove_source_branch: params[:should_remove_source_branch] diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index c3360c391af..84670d6582e 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -73,29 +73,40 @@ module Backup end def prepare_directories - # TODO: Need to find a way to do this for gitaly - # Gitaly discussion issue: https://gitlab.com/gitlab-org/gitaly/issues/1194 - Gitlab.config.repositories.storages.each do |name, repository_storage| - path = repository_storage.legacy_disk_path - next unless File.exist?(path) - - # Move all files in the existing repos directory except . and .. to - # repositories.old.<timestamp> directory - bk_repos_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}-repositories.old." + Time.now.to_i.to_s) - FileUtils.mkdir_p(bk_repos_path, mode: 0700) - files = Dir.glob(File.join(path, "*"), File::FNM_DOTMATCH) - [File.join(path, "."), File.join(path, "..")] - - begin - FileUtils.mv(files, bk_repos_path) - rescue Errno::EACCES - access_denied_error(path) - rescue Errno::EBUSY - resource_busy_error(path) + delete_all_repositories(name, repository_storage) + end + end + + def delete_all_repositories(name, repository_storage) + gitaly_migrate(:delete_all_repositories) do |is_enabled| + if is_enabled + Gitlab::GitalyClient::StorageService.new(name).delete_all_repositories + else + local_delete_all_repositories(name, repository_storage) end end end + def local_delete_all_repositories(name, repository_storage) + path = repository_storage.legacy_disk_path + return unless File.exist?(path) + + # Move all files in the existing repos directory except . and .. to + # repositories.old.<timestamp> directory + bk_repos_path = File.join(Gitlab.config.backup.path, "tmp", "#{name}-repositories.old." + Time.now.to_i.to_s) + FileUtils.mkdir_p(bk_repos_path, mode: 0700) + files = Dir.glob(File.join(path, "*"), File::FNM_DOTMATCH) - [File.join(path, "."), File.join(path, "..")] + + begin + FileUtils.mv(files, bk_repos_path) + rescue Errno::EACCES + access_denied_error(path) + rescue Errno::EBUSY + resource_busy_error(path) + end + end + def restore_custom_hooks(project) # TODO: Need to find a way to do this for gitaly # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195 @@ -113,6 +124,7 @@ module Backup def restore prepare_directories gitlab_shell = Gitlab::Shell.new + Project.find_each(batch_size: 1000) do |project| progress.print " * #{project.full_path} ... " path_to_project_bundle = path_to_bundle(project) @@ -121,7 +133,6 @@ module Backup restore_repo_success = nil if File.exist?(path_to_project_bundle) begin - gitlab_shell.remove_repository(project.repository_storage, project.disk_path) if project.repository_exists? project.repository.create_from_bundle path_to_project_bundle restore_repo_success = true rescue => e @@ -146,7 +157,6 @@ module Backup if File.exist?(path_to_wiki_bundle) progress.print " * #{wiki.full_path} ... " begin - gitlab_shell.remove_repository(wiki.repository_storage, wiki.disk_path) if wiki.repository_exists? wiki.repository.create_from_bundle(path_to_wiki_bundle) progress.puts "[DONE]".color(:green) rescue => e @@ -224,5 +234,11 @@ module Backup def display_repo_path(project) project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path end + + def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block) + Gitlab::GitalyClient.migrate(method, status: status, &block) + rescue GRPC::NotFound, GRPC::BadStatus => e + raise Error, e + end end end diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index 6bee5ea15b9..7b5915899cf 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -69,7 +69,8 @@ module Banzai { group: [:owners, :group_members] }, :invited_groups, :project_members, - :project_feature + :project_feature, + :route ] } ), diff --git a/lib/bitbucket/representation/issue.rb b/lib/bitbucket/representation/issue.rb index 054064395c3..44bcbc250b3 100644 --- a/lib/bitbucket/representation/issue.rb +++ b/lib/bitbucket/representation/issue.rb @@ -12,7 +12,7 @@ module Bitbucket end def author - raw.fetch('reporter', {}).fetch('username', nil) + raw.dig('reporter', 'username') end def description diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb index af310aa12fc..1893cb001b2 100644 --- a/lib/gitlab/auth/user_access_denied_reason.rb +++ b/lib/gitlab/auth/user_access_denied_reason.rb @@ -8,12 +8,12 @@ module Gitlab def rejection_message case rejection_type when :internal - 'This action cannot be performed by internal users' + "This action cannot be performed by internal users" when :terms_not_accepted - 'You must accept the Terms of Service in order to perform this action. '\ - 'Please access GitLab from a web browser to accept these terms.' + "You (#{@user.to_reference}) must accept the Terms of Service in order to perform this action. "\ + "Please access GitLab from a web browser to accept these terms." else - 'Your account has been blocked.' + "Your account has been blocked." end end diff --git a/lib/gitlab/background_migration/fill_file_store_job_artifact.rb b/lib/gitlab/background_migration/fill_file_store_job_artifact.rb new file mode 100644 index 00000000000..22b0ac71920 --- /dev/null +++ b/lib/gitlab/background_migration/fill_file_store_job_artifact.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true +# rubocop:disable Metrics/AbcSize +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class FillFileStoreJobArtifact + class JobArtifact < ActiveRecord::Base + self.table_name = 'ci_job_artifacts' + end + + def perform(start_id, stop_id) + FillFileStoreJobArtifact::JobArtifact + .where(file_store: nil) + .where(id: (start_id..stop_id)) + .update_all(file_store: 1) + end + end + end +end diff --git a/lib/gitlab/background_migration/fill_file_store_lfs_object.rb b/lib/gitlab/background_migration/fill_file_store_lfs_object.rb new file mode 100644 index 00000000000..d0816ae3ed5 --- /dev/null +++ b/lib/gitlab/background_migration/fill_file_store_lfs_object.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true +# rubocop:disable Metrics/AbcSize +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class FillFileStoreLfsObject + class LfsObject < ActiveRecord::Base + self.table_name = 'lfs_objects' + end + + def perform(start_id, stop_id) + FillFileStoreLfsObject::LfsObject + .where(file_store: nil) + .where(id: (start_id..stop_id)) + .update_all(file_store: 1) + end + end + end +end diff --git a/lib/gitlab/background_migration/fill_store_upload.rb b/lib/gitlab/background_migration/fill_store_upload.rb new file mode 100644 index 00000000000..94c65459a67 --- /dev/null +++ b/lib/gitlab/background_migration/fill_store_upload.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +# rubocop:disable Metrics/AbcSize +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class FillStoreUpload + class Upload < ActiveRecord::Base + self.table_name = 'uploads' + self.inheritance_column = :_type_disabled + end + + def perform(start_id, stop_id) + FillStoreUpload::Upload + .where(store: nil) + .where(id: (start_id..stop_id)) + .update_all(store: 1) + end + end + end +end diff --git a/lib/gitlab/database/count.rb b/lib/gitlab/database/count.rb index 3374203960e..5f549ed2b3c 100644 --- a/lib/gitlab/database/count.rb +++ b/lib/gitlab/database/count.rb @@ -17,31 +17,69 @@ module Gitlab ].freeze end - def self.approximate_count(model) - return model.count unless Gitlab::Database.postgresql? + # Takes in an array of models and returns a Hash for the approximate + # counts for them. If the model's table has not been vacuumed or + # analyzed recently, simply run the Model.count to get the data. + # + # @param [Array] + # @return [Hash] of Model -> count mapping + def self.approximate_counts(models) + table_to_model_map = models.each_with_object({}) do |model, hash| + hash[model.table_name] = model + end - execute_estimate_if_updated_recently(model) || model.count - end + table_names = table_to_model_map.keys + counts_by_table_name = Gitlab::Database.postgresql? ? reltuples_from_recently_updated(table_names) : {} - def self.execute_estimate_if_updated_recently(model) - ActiveRecord::Base.connection.select_value(postgresql_estimate_query(model)).to_i if reltuples_updated_recently?(model) - rescue *CONNECTION_ERRORS + # Convert table -> count to Model -> count + counts_by_model = counts_by_table_name.each_with_object({}) do |pair, hash| + model = table_to_model_map[pair.first] + hash[model] = pair.second + end + + missing_tables = table_names - counts_by_table_name.keys + + missing_tables.each do |table| + model = table_to_model_map[table] + counts_by_model[model] = model.count + end + + counts_by_model end - def self.reltuples_updated_recently?(model) - time = "to_timestamp(#{1.hour.ago.to_i})" - query = <<~SQL - SELECT 1 FROM pg_stat_user_tables WHERE relname = '#{model.table_name}' AND - (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time}) - SQL + # Returns a hash of the table names that have recently updated tuples. + # + # @param [Array] table names + # @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 }) + def self.reltuples_from_recently_updated(table_names) + query = postgresql_estimate_query(table_names) + rows = [] - ActiveRecord::Base.connection.select_all(query).count > 0 + # Querying tuple stats only works on the primary. Due to load + # balancing, we need to ensure this query hits the load balancer. The + # easiest way to do this is to start a transaction. + ActiveRecord::Base.transaction do + rows = ActiveRecord::Base.connection.select_all(query) + end + + rows.each_with_object({}) { |row, data| data[row['table_name']] = row['estimate'].to_i } rescue *CONNECTION_ERRORS - false + {} end - def self.postgresql_estimate_query(model) - "SELECT reltuples::bigint AS estimate FROM pg_class where relname = '#{model.table_name}'" + # Generates the PostgreSQL query to return the tuples for tables + # that have been vacuumed or analyzed in the last hour. + # + # @param [Array] table names + # @returns [Hash] Table name to count mapping (e.g. { 'projects' => 5, 'users' => 100 }) + def self.postgresql_estimate_query(table_names) + time = "to_timestamp(#{1.hour.ago.to_i})" + <<~SQL + SELECT pg_class.relname AS table_name, reltuples::bigint AS estimate FROM pg_class + LEFT JOIN pg_stat_user_tables ON pg_class.relname = pg_stat_user_tables.relname + WHERE pg_class.relname IN (#{table_names.map { |table| "'#{table}'" }.join(',')}) + AND (last_vacuum > #{time} OR last_autovacuum > #{time} OR last_analyze > #{time} OR last_autoanalyze > #{time}) + SQL end end end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 014854da55c..765fb0289a8 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -76,6 +76,13 @@ module Gitlab line_code(line) if line end + # Returns the raw diff content up to the given line index + def diff_hunk(diff_line) + # Adding 2 because of the @@ diff header and Enum#take should consider + # an extra line, because we're passing an index. + raw_diff.each_line.take(diff_line.index + 2).join + end + def old_sha diff_refs&.base_sha end diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index 8cf59fa8e28..8c72d00c1f3 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -138,8 +138,8 @@ module Gitlab def ee_branch_presence_check! ee_remotes.keys.each do |remote| - [ee_branch_prefix, ee_branch_suffix].each do |branch| - _, status = step("Fetching #{remote}/#{ee_branch_prefix}", %W[git fetch #{remote} #{branch}]) + [ce_branch, ee_branch_prefix, ee_branch_suffix].each do |branch| + _, status = step("Fetching #{remote}/#{branch}", %W[git fetch #{remote} #{branch}]) if status.zero? @ee_remote_with_branch = remote diff --git a/lib/gitlab/gitaly_client/storage_service.rb b/lib/gitlab/gitaly_client/storage_service.rb new file mode 100644 index 00000000000..eb0e910665b --- /dev/null +++ b/lib/gitlab/gitaly_client/storage_service.rb @@ -0,0 +1,15 @@ +module Gitlab + module GitalyClient + class StorageService + def initialize(storage) + @storage = storage + end + + # Delete all repositories in the storage. This is a slow and VERY DESTRUCTIVE operation. + def delete_all_repositories + request = Gitaly::DeleteAllRepositoriesRequest.new(storage_name: @storage) + GitalyClient.call(@storage, :storage_service, :delete_all_repositories, request) + end + end + end +end diff --git a/lib/gitlab/grape_logging/loggers/queue_duration_logger.rb b/lib/gitlab/grape_logging/loggers/queue_duration_logger.rb new file mode 100644 index 00000000000..0adac79f25a --- /dev/null +++ b/lib/gitlab/grape_logging/loggers/queue_duration_logger.rb @@ -0,0 +1,26 @@ +# This grape_logging module (https://github.com/aserafin/grape_logging) makes it +# possible to log how much time an API request was queued by Workhorse. +module Gitlab + module GrapeLogging + module Loggers + class QueueDurationLogger < ::GrapeLogging::Loggers::Base + attr_accessor :start_time + + def before + @start_time = Time.now + end + + def parameters(request, _) + proxy_start = request.env['HTTP_GITLAB_WORKHORSE_PROXY_START'].presence + + return {} unless proxy_start && start_time + + # Time in milliseconds since gitlab-workhorse started the request + duration = (start_time.to_f * 1_000 - proxy_start.to_f / 1_000_000).round(2) + + { 'queue_duration': duration } + end + end + end + end +end diff --git a/lib/gitlab/hashed_storage/rake_helper.rb b/lib/gitlab/hashed_storage/rake_helper.rb new file mode 100644 index 00000000000..8aba42ccfce --- /dev/null +++ b/lib/gitlab/hashed_storage/rake_helper.rb @@ -0,0 +1,71 @@ +module Gitlab + module HashedStorage + module RakeHelper + def self.batch_size + ENV.fetch('BATCH', 200).to_i + end + + def self.listing_limit + ENV.fetch('LIMIT', 500).to_i + end + + def self.project_id_batches(&block) + Project.with_unmigrated_storage.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches + ids = relation.pluck(:id) + + yield ids.min, ids.max + end + end + + def self.legacy_attachments_relation + Upload.joins(<<~SQL).where('projects.storage_version < :version OR projects.storage_version IS NULL', version: Project::HASHED_STORAGE_FEATURES[:attachments]) + JOIN projects + ON (uploads.model_type='Project' AND uploads.model_id=projects.id) + SQL + end + + def self.hashed_attachments_relation + Upload.joins(<<~SQL).where('projects.storage_version >= :version', version: Project::HASHED_STORAGE_FEATURES[:attachments]) + JOIN projects + ON (uploads.model_type='Project' AND uploads.model_id=projects.id) + SQL + end + + def self.relation_summary(relation_name, relation) + relation_count = relation.count + $stdout.puts "* Found #{relation_count} #{relation_name}".color(:green) + + relation_count + end + + def self.projects_list(relation_name, relation) + listing(relation_name, relation.with_route) do |project| + $stdout.puts " - #{project.full_path} (id: #{project.id})".color(:red) + end + end + + def self.attachments_list(relation_name, relation) + listing(relation_name, relation) do |upload| + $stdout.puts " - #{upload.path} (id: #{upload.id})".color(:red) + end + end + + def self.listing(relation_name, relation) + relation_count = relation_summary(relation_name, relation) + return unless relation_count > 0 + + limit = listing_limit + + if relation_count > limit + $stdout.puts " ! Displaying first #{limit} #{relation_name}..." + end + + relation.find_each(batch_size: batch_size).with_index do |element, index| + yield element + + break if index + 1 >= limit + end + end + end + end +end diff --git a/lib/gitlab/import_export/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb index 34169319b26..7c9fc5c15bb 100644 --- a/lib/gitlab/import_export/attribute_cleaner.rb +++ b/lib/gitlab/import_export/attribute_cleaner.rb @@ -7,14 +7,15 @@ module Gitlab new(*args).clean end - def initialize(relation_hash:, relation_class:) + def initialize(relation_hash:, relation_class:, excluded_keys: []) @relation_hash = relation_hash @relation_class = relation_class + @excluded_keys = excluded_keys end def clean @relation_hash.reject do |key, _value| - prohibited_key?(key) || !@relation_class.attribute_method?(key) + prohibited_key?(key) || !@relation_class.attribute_method?(key) || excluded_key?(key) end.except('id') end @@ -23,6 +24,12 @@ module Gitlab def prohibited_key?(key) key.end_with?('_id') && !ALLOWED_REFERENCES.include?(key) end + + def excluded_key?(key) + return false if @excluded_keys.empty? + + @excluded_keys.include?(key) + end end end end diff --git a/lib/gitlab/import_export/attributes_finder.rb b/lib/gitlab/import_export/attributes_finder.rb index 56042ddecbf..0c8fda07294 100644 --- a/lib/gitlab/import_export/attributes_finder.rb +++ b/lib/gitlab/import_export/attributes_finder.rb @@ -32,6 +32,10 @@ module Gitlab @methods[key].nil? ? {} : { methods: @methods[key] } end + def find_excluded_keys(klass_name) + @excluded_attributes[klass_name.to_sym]&.map(&:to_s) || [] + end + private def find_attributes_only(value) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 21ac7f7e0b6..36c7534cd7a 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -98,8 +98,6 @@ excluded_attributes: - :import_jid - :created_at - :updated_at - - :import_jid - - :import_jid - :id - :star_count - :last_activity_at diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index d5590dde40f..4eb67fbe11e 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -88,16 +88,18 @@ module Gitlab end def project_params - @project_params ||= json_params.merge(override_params) + @project_params ||= begin + attrs = json_params.merge(override_params) + + # Cleaning all imported and overridden params + Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: attrs, + relation_class: Project, + excluded_keys: excluded_keys_for_relation(:project)) + end end def override_params - return {} unless params = @project.import_data&.data&.fetch('override_params', nil) - - @override_params ||= params.select do |key, _value| - Project.column_names.include?(key.to_s) && - !reader.project_tree[:except].include?(key.to_sym) - end + @override_params ||= @project.import_data&.data&.fetch('override_params', nil) || {} end def json_params @@ -171,7 +173,8 @@ module Gitlab relation_hash: parsed_relation_hash(relation_hash, relation.to_sym), members_mapper: members_mapper, user: @user, - project: @restored_project) + project: @restored_project, + excluded_keys: excluded_keys_for_relation(relation)) end.compact relation_hash_list.is_a?(Array) ? relation_array : relation_array.first @@ -192,6 +195,10 @@ module Gitlab def reader @reader ||= Gitlab::ImportExport::Reader.new(shared: @shared) end + + def excluded_keys_for_relation(relation) + @reader.attributes_finder.find_excluded_keys(relation) + end end end end diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb index eb7f5120592..e621c40fc7a 100644 --- a/lib/gitlab/import_export/reader.rb +++ b/lib/gitlab/import_export/reader.rb @@ -1,7 +1,7 @@ module Gitlab module ImportExport class Reader - attr_reader :tree + attr_reader :tree, :attributes_finder def initialize(shared:) @shared = shared diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 4a41a69840b..b736b2c3fe5 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -36,13 +36,21 @@ module Gitlab new(*args).create end - def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:) + def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:, excluded_keys: []) @relation_name = OVERRIDES[relation_sym] || relation_sym @relation_hash = relation_hash.except('noteable_id') @members_mapper = members_mapper @user = user @project = project @imported_object_retries = 0 + + # Remove excluded keys from relation_hash + # We don't do this in the parsed_relation_hash because of the 'transformed attributes' + # For example, MergeRequestDiffFiles exports its diff attribute as utf8_diff. Then, + # in the create method that attribute is renamed to diff. And because diff is an excluded key, + # if we clean the excluded keys in the parsed_relation_hash, it will be removed + # from the object attributes and the export will fail. + @relation_hash.except!(*excluded_keys) end # Creates an object from an actual model with name "relation_sym" with params from diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb index 3e54456e936..4e611e7f16c 100644 --- a/lib/gitlab/import_formatter.rb +++ b/lib/gitlab/import_formatter.rb @@ -9,6 +9,7 @@ module Gitlab end def author_line(author) + author ||= "Anonymous" "*Created by: #{author}*\n\n" end end diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb index 33e450d7f0a..704813dfdf0 100644 --- a/lib/mattermost/command.rb +++ b/lib/mattermost/command.rb @@ -1,7 +1,7 @@ module Mattermost class Command < Client def create(params) - response = session_post("/api/v3/teams/#{params[:team_id]}/commands/create", + response = session_post('/api/v4/commands', body: params.to_json) response['token'] diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb index 85f78e44f32..2aa7a2f64d8 100644 --- a/lib/mattermost/session.rb +++ b/lib/mattermost/session.rb @@ -112,7 +112,7 @@ module Mattermost end def destroy - post('/api/v3/users/logout') + post('/api/v4/users/logout') end def oauth_uri @@ -120,7 +120,7 @@ module Mattermost @oauth_uri = nil - response = get("/api/v3/oauth/gitlab/login", follow_redirects: false) + response = get('/oauth/gitlab/login', follow_redirects: false, format: 'text/html') return unless (300...400) === response.code redirect_uri = response.headers['location'] diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb index 75513a9ba04..95c2f6f9d6b 100644 --- a/lib/mattermost/team.rb +++ b/lib/mattermost/team.rb @@ -1,14 +1,14 @@ module Mattermost class Team < Client - # Returns **all** teams for an admin + # Returns all teams that the current user is a member of def all - session_get('/api/v3/teams/all').values + session_get("/api/v4/users/me/teams") end # Creates a team on the linked Mattermost instance, the team admin will be the # `current_user` passed to the Mattermost::Client instance def create(name:, display_name:, type:) - session_post('/api/v3/teams/create', body: { + session_post('/api/v4/teams', body: { name: name, display_name: display_name, type: type diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb index 7cfe76b7b71..da24a36603e 100644 --- a/lib/peek/rblineprof/custom_controller_helpers.rb +++ b/lib/peek/rblineprof/custom_controller_helpers.rb @@ -41,10 +41,10 @@ module Peek ] end.sort_by{ |a,b,c,d,e,f| -f } - output = "<div class='modal-dialog modal-full'><div class='modal-content'>" + output = "<div class='modal-dialog modal-lg'><div class='modal-content'>" output << "<div class='modal-header'>" - output << "<button class='close btn btn-link btn-sm' type='button' data-dismiss='modal'>X</button>" output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>" + output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>×</span></button>" output << "</div>" output << "<div class='modal-body'>" diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake index 6e8bd9078c8..68d6f9d7cb1 100644 --- a/lib/tasks/gitlab/storage.rake +++ b/lib/tasks/gitlab/storage.rake @@ -3,6 +3,7 @@ namespace :gitlab do desc 'GitLab | Storage | Migrate existing projects to Hashed Storage' task migrate_to_hashed: :environment do legacy_projects_count = Project.with_unmigrated_storage.count + helper = Gitlab::HashedStorage::RakeHelper if legacy_projects_count == 0 puts 'There are no projects requiring storage migration. Nothing to do!' @@ -10,9 +11,9 @@ namespace :gitlab do next end - print "Enqueuing migration of #{legacy_projects_count} projects in batches of #{batch_size}" + print "Enqueuing migration of #{legacy_projects_count} projects in batches of #{helper.batch_size}" - project_id_batches do |start, finish| + helper.project_id_batches do |start, finish| StorageMigratorWorker.perform_async(start, finish) print '.' @@ -23,118 +24,50 @@ namespace :gitlab do desc 'Gitlab | Storage | Summary of existing projects using Legacy Storage' task legacy_projects: :environment do - relation_summary('projects', Project.without_storage_feature(:repository)) + helper = Gitlab::HashedStorage::RakeHelper + helper.relation_summary('projects using Legacy Storage', Project.without_storage_feature(:repository)) end desc 'Gitlab | Storage | List existing projects using Legacy Storage' task list_legacy_projects: :environment do - projects_list('projects using Legacy Storage', Project.without_storage_feature(:repository)) + helper = Gitlab::HashedStorage::RakeHelper + helper.projects_list('projects using Legacy Storage', Project.without_storage_feature(:repository)) end desc 'Gitlab | Storage | Summary of existing projects using Hashed Storage' task hashed_projects: :environment do - relation_summary('projects using Hashed Storage', Project.with_storage_feature(:repository)) + helper = Gitlab::HashedStorage::RakeHelper + helper.relation_summary('projects using Hashed Storage', Project.with_storage_feature(:repository)) end desc 'Gitlab | Storage | List existing projects using Hashed Storage' task list_hashed_projects: :environment do - projects_list('projects using Hashed Storage', Project.with_storage_feature(:repository)) + helper = Gitlab::HashedStorage::RakeHelper + helper.projects_list('projects using Hashed Storage', Project.with_storage_feature(:repository)) end desc 'Gitlab | Storage | Summary of project attachments using Legacy Storage' task legacy_attachments: :environment do - relation_summary('attachments using Legacy Storage', legacy_attachments_relation) + helper = Gitlab::HashedStorage::RakeHelper + helper.relation_summary('attachments using Legacy Storage', helper.legacy_attachments_relation) end desc 'Gitlab | Storage | List existing project attachments using Legacy Storage' task list_legacy_attachments: :environment do - attachments_list('attachments using Legacy Storage', legacy_attachments_relation) + helper = Gitlab::HashedStorage::RakeHelper + helper.attachments_list('attachments using Legacy Storage', helper.legacy_attachments_relation) end desc 'Gitlab | Storage | Summary of project attachments using Hashed Storage' task hashed_attachments: :environment do - relation_summary('attachments using Hashed Storage', hashed_attachments_relation) + helper = Gitlab::HashedStorage::RakeHelper + helper.relation_summary('attachments using Hashed Storage', helper.hashed_attachments_relation) end desc 'Gitlab | Storage | List existing project attachments using Hashed Storage' task list_hashed_attachments: :environment do - attachments_list('attachments using Hashed Storage', hashed_attachments_relation) - end - - def batch_size - ENV.fetch('BATCH', 200).to_i - end - - def project_id_batches(&block) - Project.with_unmigrated_storage.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches - ids = relation.pluck(:id) - - yield ids.min, ids.max - end - end - - def legacy_attachments_relation - Upload.joins(<<~SQL).where('projects.storage_version < :version OR projects.storage_version IS NULL', version: Project::HASHED_STORAGE_FEATURES[:attachments]) - JOIN projects - ON (uploads.model_type='Project' AND uploads.model_id=projects.id) - SQL - end - - def hashed_attachments_relation - Upload.joins(<<~SQL).where('projects.storage_version >= :version', version: Project::HASHED_STORAGE_FEATURES[:attachments]) - JOIN projects - ON (uploads.model_type='Project' AND uploads.model_id=projects.id) - SQL - end - - def relation_summary(relation_name, relation) - relation_count = relation.count - puts "* Found #{relation_count} #{relation_name}".color(:green) - - relation_count - end - - def projects_list(relation_name, relation) - relation_count = relation_summary(relation_name, relation) - - projects = relation.with_route - limit = ENV.fetch('LIMIT', 500).to_i - - return unless relation_count > 0 - - puts " ! Displaying first #{limit} #{relation_name}..." if relation_count > limit - - counter = 0 - projects.find_in_batches(batch_size: batch_size) do |batch| - batch.each do |project| - counter += 1 - - puts " - #{project.full_path} (id: #{project.id})".color(:red) - - return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator, Cop/AvoidReturnFromBlocks - end - end - end - - def attachments_list(relation_name, relation) - relation_count = relation_summary(relation_name, relation) - - limit = ENV.fetch('LIMIT', 500).to_i - - return unless relation_count > 0 - - puts " ! Displaying first #{limit} #{relation_name}..." if relation_count > limit - - counter = 0 - relation.find_in_batches(batch_size: batch_size) do |batch| - batch.each do |upload| - counter += 1 - - puts " - #{upload.path} (id: #{upload.id})".color(:red) - - return if counter >= limit # rubocop:disable Lint/NonLocalExitFromIterator, Cop/AvoidReturnFromBlocks - end - end + helper = Gitlab::HashedStorage::RakeHelper + helper.attachments_list('attachments using Hashed Storage', helper.hashed_attachments_relation) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 608d2a584ba..9e34eb463ce 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-05-21 12:38-0700\n" -"PO-Revision-Date: 2018-05-21 12:38-0700\n" +"POT-Creation-Date: 2018-05-23 07:40-0500\n" +"PO-Revision-Date: 2018-05-23 07:40-0500\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -3517,7 +3517,7 @@ msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" -msgid "Secret variables" +msgid "Variables" msgstr "" msgid "Select Archive Format" @@ -3998,6 +3998,12 @@ msgstr "" msgid "There are problems accessing Git storage: " msgstr "" +msgid "There was an error loading jobs" +msgstr "" + +msgid "There was an error loading latest pipeline" +msgstr "" + msgid "There was an error loading users activity calendar." msgstr "" @@ -4241,10 +4247,10 @@ msgstr "" msgid "Timeago|in 1 year" msgstr "" -msgid "Timeago|in a while" +msgid "Timeago|less than a minute ago" msgstr "" -msgid "Timeago|less than a minute ago" +msgid "Timeago|right now" msgstr "" msgid "Time|hr" @@ -4479,7 +4485,31 @@ msgstr "" msgid "WikiEdit|There is already a page with the same title in that path." msgstr "" -msgid "WikiEmptyPageError|You are not allowed to create wiki pages" +msgid "WikiEmptyIssueMessage|Suggest wiki improvement" +msgstr "" + +msgid "WikiEmptyIssueMessage|You must be a project member in order to add wiki pages. If you have suggestions for how to improve the wiki for this project, consider opening an issue in the %{issues_link}." +msgstr "" + +msgid "WikiEmptyIssueMessage|issue tracker" +msgstr "" + +msgid "WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, it's principles, how to use it, and so on." +msgstr "" + +msgid "WikiEmpty|Create your first page" +msgstr "" + +msgid "WikiEmpty|Suggest wiki improvement" +msgstr "" + +msgid "WikiEmpty|The wiki lets you write documentation for your project" +msgstr "" + +msgid "WikiEmpty|This project has no wiki pages" +msgstr "" + +msgid "WikiEmpty|You must be a project member in order to add wiki pages." msgstr "" msgid "WikiHistoricalPage|This is an old version of this page." @@ -4548,9 +4578,6 @@ msgstr "" msgid "Wiki|Edit Page" msgstr "" -msgid "Wiki|Empty page" -msgstr "" - msgid "Wiki|More Pages" msgstr "" diff --git a/package.json b/package.json index 13be495b702..0113d49c8f2 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "babel-preset-latest": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "blackst0ne-mermaid": "^7.1.0-fixed", - "bootstrap": "4.1", + "bootstrap": "~4.1.1", "brace-expansion": "^1.1.8", "cache-loader": "^1.2.2", "chart.js": "1.0.2", @@ -100,21 +100,21 @@ }, "devDependencies": { "axios-mock-adapter": "^1.15.0", - "babel-eslint": "^8.0.2", + "babel-eslint": "^8.2.3", "babel-plugin-istanbul": "^4.1.6", "babel-plugin-rewire": "^1.1.0", "babel-template": "^6.26.0", "babel-types": "^6.26.0", "chalk": "^2.4.1", "commander": "^2.15.1", - "eslint": "^3.18.0", - "eslint-config-airbnb-base": "^10.0.1", - "eslint-import-resolver-webpack": "^0.8.3", - "eslint-plugin-filenames": "^1.1.0", - "eslint-plugin-html": "2.0.1", - "eslint-plugin-import": "^2.2.0", + "eslint": "~4.12.1", + "eslint-config-airbnb-base": "^12.1.0", + "eslint-import-resolver-webpack": "^0.10.0", + "eslint-plugin-filenames": "^1.2.0", + "eslint-plugin-html": "4.0.3", + "eslint-plugin-import": "^2.12.0", "eslint-plugin-jasmine": "^2.1.0", - "eslint-plugin-promise": "^3.5.0", + "eslint-plugin-promise": "^3.8.0", "eslint-plugin-vue": "^4.0.1", "ignore": "^3.3.7", "istanbul": "^0.4.5", diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 166861e6c4a..9507f92f4b2 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -16,6 +16,10 @@ module QA element :no_fast_forward_message, 'Fast-forward merge is not possible' end + view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue' do + element :squash_checkbox + end + def rebase! click_element :mr_rebase_button @@ -41,6 +45,14 @@ module QA has_text?('The changes were merged into') end end + + def mark_to_squash + wait(reload: true) do + has_css?(element_selector_css(:squash_checkbox)) + end + + click_element :squash_checkbox + end end end end diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index 17a1bc904e4..145c3d3ddfa 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -7,7 +7,7 @@ module QA # rubocop:disable Naming/FileName view 'app/views/projects/settings/ci_cd/show.html.haml' do element :runners_settings, 'Runners settings' - element :secret_variables, 'Secret variables' + element :secret_variables, 'Variables' end def expand_runners_settings(&block) @@ -17,7 +17,7 @@ module QA # rubocop:disable Naming/FileName end def expand_secret_variables(&block) - expand_section('Secret variables') do + expand_section('Variables') do Settings::SecretVariables.perform(&block) end end diff --git a/qa/qa/specs/features/api/users_spec.rb b/qa/qa/specs/features/api/users_spec.rb index d4ff4ebbc9a..38f4c497183 100644 --- a/qa/qa/specs/features/api/users_spec.rb +++ b/qa/qa/specs/features/api/users_spec.rb @@ -17,17 +17,16 @@ module QA get request.url, { params: { username: Runtime::User.name } } expect_status(200) - expect(json_body).to be_an Array - expect(json_body.size).to eq(1) - expect(json_body.first[:username]).to eq Runtime::User.name + expect(json_body).to contain_exactly( + a_hash_including(username: Runtime::User.name) + ) end scenario 'submit request with an invalid user name' do get request.url, { params: { username: SecureRandom.hex(10) } } expect_status(200) - expect(json_body).to be_an Array - expect(json_body.size).to eq(0) + expect(json_body).to eq([]) end end diff --git a/qa/qa/specs/features/merge_request/squash_spec.rb b/qa/qa/specs/features/merge_request/squash_spec.rb new file mode 100644 index 00000000000..dbbdf852a38 --- /dev/null +++ b/qa/qa/specs/features/merge_request/squash_spec.rb @@ -0,0 +1,48 @@ +module QA + feature 'merge request squash commits', :core do + scenario 'when squash commits is marked before merge' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + project = Factory::Resource::Project.fabricate! do |project| + project.name = "squash-before-merge" + end + + merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request| + merge_request.project = project + merge_request.title = 'Squashing commits' + end + + Factory::Repository::Push.fabricate! do |push| + push.project = project + push.commit_message = 'to be squashed' + push.branch_name = merge_request.source_branch + push.new_branch = false + push.file_name = 'other.txt' + push.file_content = "Test with unicode characters ❤✓€❄" + end + + merge_request.visit! + + Page::MergeRequest::Show.perform do |merge_request_page| + merge_request_page.mark_to_squash + merge_request_page.merge! + + merge_request.project.visit! + + Git::Repository.perform do |repository| + repository.uri = Page::Project::Show.act do + choose_repository_clone_http + repository_location.uri + end + + repository.use_default_credentials + + repository.act { clone } + + expect(repository.commits.size).to eq 3 + end + end + end + end +end diff --git a/rubocop/cop/line_break_around_conditional_block.rb b/rubocop/cop/line_break_around_conditional_block.rb index 3e7021e724e..8b6052fee1b 100644 --- a/rubocop/cop/line_break_around_conditional_block.rb +++ b/rubocop/cop/line_break_around_conditional_block.rb @@ -95,7 +95,7 @@ module RuboCop end def end_clause_line?(line) - line =~ /^\s*(rescue|else|elsif|when)/ + line =~ /^\s*(#|rescue|else|elsif|when)/ end def begin_line?(line) diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 4770e187db6..dcb0faffbd4 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -17,7 +17,7 @@ describe Boards::IssuesController do project.add_guest(guest) end - describe 'GET index' do + describe 'GET index', :request_store do let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) } context 'with invalid board id' do diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb new file mode 100644 index 00000000000..d8fa41abb18 --- /dev/null +++ b/spec/controllers/groups/shared_projects_controller_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Groups::SharedProjectsController do + def get_shared_projects(params = {}) + get :index, params.reverse_merge(format: :json, group_id: group.full_path) + end + + def share_project(project) + Projects::GroupLinks::CreateService.new( + project, + user, + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + set(:group) { create(:group) } + set(:user) { create(:user) } + set(:shared_project) do + shared_project = create(:project, namespace: user.namespace) + share_project(shared_project) + + shared_project + end + + let(:json_project_ids) { json_response.map { |project_info| project_info['id'] } } + + before do + sign_in(user) + end + + describe 'GET #index' do + it 'returns only projects shared with the group' do + create(:project, namespace: group) + + get_shared_projects + + expect(json_project_ids).to contain_exactly(shared_project.id) + end + + it 'allows filtering shared projects' do + project = create(:project, :archived, namespace: user.namespace, name: "Searching for") + share_project(project) + + get_shared_projects(filter: 'search') + + expect(json_project_ids).to contain_exactly(project.id) + end + + it 'allows sorting projects' do + shared_project.update!(name: 'bbb') + second_project = create(:project, namespace: user.namespace, name: 'aaaa') + share_project(second_project) + + get_shared_projects(sort: 'name_asc') + + expect(json_project_ids).to eq([second_project.id, shared_project.id]) + end + end +end diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb index c621eb69171..4530a301d4d 100644 --- a/spec/controllers/profiles_controller_spec.rb +++ b/spec/controllers/profiles_controller_spec.rb @@ -3,6 +3,19 @@ require('spec_helper') describe ProfilesController, :request_store do let(:user) { create(:user) } + describe 'POST update' do + it 'does not update password' do + sign_in(user) + + expect do + post :update, + user: { password: 'hello12345', password_confirmation: 'hello12345' } + end.not_to change { user.reload.encrypted_password } + + expect(response.status).to eq(302) + end + end + describe 'PUT update' do it 'allows an email update from a user without an external email address' do sign_in(user) diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 4d765229bde..509f19ed030 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -27,6 +27,20 @@ describe Projects::BoardsController do expect(response).to render_template :index expect(response.content_type).to eq 'text/html' end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end + + it 'returns a not found 404 response' do + list_boards + + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'text/html' + end + end end context 'when format is JSON' do @@ -40,18 +54,19 @@ describe Projects::BoardsController do expect(response).to match_response_schema('boards') expect(parsed_response.length).to eq 2 end - end - context 'with unauthorized user' do - before do - allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) - allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) - end + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end - it 'returns a not found 404 response' do - list_boards + it 'returns a not found 404 response' do + list_boards format: :json - expect(response).to have_gitlab_http_status(404) + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'application/json' + end end end @@ -88,6 +103,20 @@ describe Projects::BoardsController do expect(response).to render_template :show expect(response.content_type).to eq 'text/html' end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end + + it 'returns a not found 404 response' do + read_board board: board + + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'text/html' + end + end end context 'when format is JSON' do @@ -96,18 +125,19 @@ describe Projects::BoardsController do expect(response).to match_response_schema('board') end - end - context 'with unauthorized user' do - before do - allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) - allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) - end + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end - it 'returns a not found 404 response' do - read_board board: board + it 'returns a not found 404 response' do + read_board board: board, format: :json - expect(response).to have_gitlab_http_status(404) + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'application/json' + end end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 16fb377b002..4860ea5dcce 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -146,6 +146,24 @@ describe Projects::BranchesController do it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' end + + it 'redirects to autodeploy setup page' do + result = { status: :success, branch: double(name: branch) } + + create(:cluster, :provided_by_gcp, projects: [project]) + + expect_any_instance_of(CreateBranchService).to receive(:execute).and_return(result) + expect(SystemNoteService).to receive(:new_issue_branch).and_return(true) + + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + issue_iid: issue.iid + + expect(response.location).to include(project_new_blob_path(project, branch)) + expect(response).to have_gitlab_http_status(302) + end end context 'when create branch service fails' do diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 82b20e12850..380e50c8cac 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe Projects::ClustersController do include AccessMatchersForController - include GoogleApi::CloudPlatformHelpers set(:project) { create(:project) } @@ -333,7 +332,7 @@ describe Projects::ClustersController do context 'when cluster is provided by GCP' do context 'when cluster is created' do - let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } it "destroys and redirects back to clusters list" do expect { go } @@ -347,7 +346,7 @@ describe Projects::ClustersController do end context 'when cluster is being created' do - let!(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } + let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, projects: [project]) } it "destroys and redirects back to clusters list" do expect { go } @@ -361,7 +360,7 @@ describe Projects::ClustersController do end context 'when cluster is provided by user' do - let!(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } + let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, projects: [project]) } it "destroys and redirects back to clusters list" do expect { go } @@ -376,7 +375,7 @@ describe Projects::ClustersController do end describe 'security' do - set(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + set(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:owner).of(project) } diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index ff9ab53d8c3..47d4942acbd 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -21,6 +21,13 @@ describe Projects::EnvironmentsController do expect(response).to have_gitlab_http_status(:ok) end + + it 'expires etag cache to force reload environments list' do + expect_any_instance_of(Gitlab::EtagCaching::Store) + .to receive(:touch).with(project_environments_path(project, format: :json)) + + get :index, environment_params + end end context 'when requesting JSON response for folders' do diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 5bfc3d31401..72f6af112b3 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -21,6 +21,18 @@ describe Projects::GroupLinksController do end end + context 'when project is not allowed to be shared with a group' do + before do + group.update_attributes(share_with_group_lock: false) + end + + include_context 'link project to group' + + it 'responds with status 404' do + expect(response).to have_gitlab_http_status(404) + end + end + context 'when user has access to group he want to link project to' do before do group.add_developer(user) diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 7fb4c1b7425..011843baffc 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -2,16 +2,15 @@ require 'spec_helper' describe Projects::ImportsController do let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + sign_in(user) + project.add_master(user) + end describe 'GET #show' do context 'when repository does not exists' do - let(:project) { create(:project) } - - before do - sign_in(user) - project.add_master(user) - end - it 'renders template' do get :show, namespace_id: project.namespace.to_param, project_id: project @@ -28,11 +27,6 @@ describe Projects::ImportsController do context 'when repository exists' do let(:project) { create(:project_empty_repo, import_url: 'https://github.com/vim/vim.git') } - before do - sign_in(user) - project.add_master(user) - end - context 'when import is in progress' do before do project.update_attribute(:import_status, :started) diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index ca86b0bc737..106611b37c9 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1,4 +1,4 @@ -require('spec_helper') +require 'spec_helper' describe Projects::IssuesController do let(:project) { create(:project) } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index d3042be9e8b..5efaaf2bb3a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -275,6 +275,7 @@ describe Projects::MergeRequestsController do namespace_id: project.namespace, project_id: project, id: merge_request.iid, + squash: false, format: 'json' } end @@ -315,8 +316,8 @@ describe Projects::MergeRequestsController do end context 'when the sha parameter matches the source SHA' do - def merge_with_sha - post :merge, base_params.merge(sha: merge_request.diff_head_sha) + def merge_with_sha(params = {}) + post :merge, base_params.merge(sha: merge_request.diff_head_sha).merge(params) end it 'returns :success' do @@ -331,6 +332,24 @@ describe Projects::MergeRequestsController do merge_with_sha end + context 'when squash is passed as 1' do + it 'updates the squash attribute on the MR to true' do + merge_request.update(squash: false) + merge_with_sha(squash: '1') + + expect(merge_request.reload.squash).to be_truthy + end + end + + context 'when squash is passed as 0' do + it 'updates the squash attribute on the MR to false' do + merge_request.update(squash: true) + merge_with_sha(squash: '0') + + expect(merge_request.reload.squash).to be_falsey + end + end + context 'when the pipeline succeeds is passed' do let!(:head_pipeline) do create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch, head_pipeline_of: merge_request) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 46b08a03b19..d84b31ad978 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -184,7 +184,7 @@ describe Projects::ProjectMembersController do project.add_master(user) end - it 'cannot remove himself from the project' do + it 'cannot remove themselves from the project' do delete :leave, namespace_id: project.namespace, project_id: project diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb index 3ecc90b6573..00c063c49f8 100644 --- a/spec/factories/application_settings.rb +++ b/spec/factories/application_settings.rb @@ -1,4 +1,5 @@ FactoryBot.define do factory :application_setting do + default_projects_limit 42 end end diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 1ce30015e81..bf329b0bb94 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -83,7 +83,7 @@ feature 'Edit group settings' do attach_file(:group_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif')) - expect { click_button 'Save group' }.to change { group.reload.avatar? }.to(true) + expect { save_group }.to change { group.reload.avatar? }.to(true) end it 'uploads new group avatar' do @@ -97,10 +97,19 @@ feature 'Edit group settings' do expect(page).not_to have_link('Remove avatar') end end -end -def update_path(new_group_path) - visit edit_group_path(group) - fill_in 'group_path', with: new_group_path - click_button 'Save group' + def update_path(new_group_path) + visit edit_group_path(group) + + page.within('.gs-advanced') do + fill_in 'group_path', with: new_group_path + click_button 'Change group path' + end + end + + def save_group + page.within('.gs-general') do + click_button 'Save group' + end + end end diff --git a/spec/features/groups/share_lock_spec.rb b/spec/features/groups/share_lock_spec.rb index 8842d1391aa..cefbc15e068 100644 --- a/spec/features/groups/share_lock_spec.rb +++ b/spec/features/groups/share_lock_spec.rb @@ -15,9 +15,8 @@ feature 'Group share with group lock' do context 'when enabling the parent group share with group lock' do scenario 'the subgroup share with group lock becomes enabled' do visit edit_group_path(root_group) - check 'group_share_with_group_lock' - click_on 'Save group' + enable_group_lock expect(subgroup.reload.share_with_group_lock?).to be_truthy end @@ -26,16 +25,15 @@ feature 'Group share with group lock' do context 'when disabling the parent group share with group lock (which was already enabled)' do background do visit edit_group_path(root_group) - check 'group_share_with_group_lock' - click_on 'Save group' + + enable_group_lock end context 'and the subgroup share with group lock is enabled' do scenario 'the subgroup share with group lock does not change' do visit edit_group_path(root_group) - uncheck 'group_share_with_group_lock' - click_on 'Save group' + disable_group_lock expect(subgroup.reload.share_with_group_lock?).to be_truthy end @@ -44,19 +42,32 @@ feature 'Group share with group lock' do context 'but the subgroup share with group lock is disabled' do background do visit edit_group_path(subgroup) - uncheck 'group_share_with_group_lock' - click_on 'Save group' + + disable_group_lock end scenario 'the subgroup share with group lock does not change' do visit edit_group_path(root_group) - uncheck 'group_share_with_group_lock' - click_on 'Save group' + disable_group_lock expect(subgroup.reload.share_with_group_lock?).to be_falsey end end end end + + def enable_group_lock + page.within('.gs-permissions') do + check 'group_share_with_group_lock' + click_on 'Save group' + end + end + + def disable_group_lock + page.within('.gs-permissions') do + uncheck 'group_share_with_group_lock' + click_on 'Save group' + end + end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index c1f3d94bc20..236768b5d7f 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -141,8 +141,10 @@ feature 'Group' do end it 'saves new settings' do - fill_in 'group_name', with: new_name - click_button 'Save group' + page.within('.gs-general') do + fill_in 'group_name', with: new_name + click_button 'Save group' + end expect(page).to have_content 'successfully updated' expect(find('#group_name').value).to eq(new_name) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 314bd19f586..e7f2e142b2d 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -591,6 +591,20 @@ describe 'Issues' do end end + it 'clears local storage after creating a new issue', :js do + 2.times do + visit new_project_issue_path(project) + wait_for_requests + + expect(page).to have_field('Title', with: '') + + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' + + click_button 'Submit issue' + end + end + context 'dropzone upload file', :js do before do visit new_project_issue_path(project) diff --git a/spec/features/merge_requests/user_squashes_merge_request_spec.rb b/spec/features/merge_requests/user_squashes_merge_request_spec.rb new file mode 100644 index 00000000000..6c952791591 --- /dev/null +++ b/spec/features/merge_requests/user_squashes_merge_request_spec.rb @@ -0,0 +1,124 @@ +require 'spec_helper' + +feature 'User squashes a merge request', :js do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:source_branch) { 'csv' } + + let!(:original_head) { project.repository.commit('master') } + + shared_examples 'squash' do + it 'squashes the commits into a single commit, and adds a merge commit' do + expect(page).to have_content('Merged') + + latest_master_commits = project.repository.commits_between(original_head.sha, 'master').map(&:raw) + + squash_commit = an_object_having_attributes(sha: a_string_matching(/\h{40}/), + message: "Csv\n", + author_name: user.name, + committer_name: user.name) + + merge_commit = an_object_having_attributes(sha: a_string_matching(/\h{40}/), + message: a_string_starting_with("Merge branch 'csv' into 'master'"), + author_name: user.name, + committer_name: user.name) + + expect(project.repository).not_to be_merged_to_root_ref(source_branch) + expect(latest_master_commits).to match([squash_commit, merge_commit]) + end + end + + shared_examples 'no squash' do + it 'accepts the merge request without squashing' do + expect(page).to have_content('Merged') + expect(project.repository).to be_merged_to_root_ref(source_branch) + end + end + + def accept_mr + expect(page).to have_button('Merge') + + uncheck 'Remove source branch' + click_on 'Merge' + end + + before do + # Prevent source branch from being removed so we can use be_merged_to_root_ref + # method to check if squash was performed or not + allow_any_instance_of(MergeRequest).to receive(:force_remove_source_branch?).and_return(false) + project.add_master(user) + + sign_in user + end + + context 'when the MR has only one commit' do + before do + merge_request = create(:merge_request, source_project: project, target_project: project, source_branch: 'master', target_branch: 'branch-merged') + + visit project_merge_request_path(project, merge_request) + end + + it 'does not show the squash checkbox' do + expect(page).not_to have_field('squash') + end + end + + context 'when squash is enabled on merge request creation' do + before do + visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: source_branch }) + check 'merge_request[squash]' + click_on 'Submit merge request' + wait_for_requests + end + + it 'shows the squash checkbox as checked' do + expect(page).to have_checked_field('squash') + end + + context 'when accepting with squash checked' do + before do + accept_mr + end + + include_examples 'squash' + end + + context 'when accepting and unchecking squash' do + before do + uncheck 'squash' + accept_mr + end + + include_examples 'no squash' + end + end + + context 'when squash is not enabled on merge request creation' do + before do + visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: source_branch }) + click_on 'Submit merge request' + wait_for_requests + end + + it 'shows the squash checkbox as unchecked' do + expect(page).to have_unchecked_field('squash') + end + + context 'when accepting and checking squash' do + before do + check 'squash' + accept_mr + end + + include_examples 'squash' + end + + context 'when accepting with squash unchecked' do + before do + accept_mr + end + + include_examples 'no squash' + end + end +end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 2eb79c9b282..0ebe3459a65 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -31,6 +31,8 @@ feature 'Gcp Cluster', :js do end context 'when user filled form with valid parameters' do + subject { click_button 'Create Kubernetes cluster' } + before do allow_any_instance_of(GoogleApi::CloudPlatform::Client) .to receive(:projects_zones_clusters_create) do @@ -52,10 +54,15 @@ feature 'Gcp Cluster', :js do fill_in 'cluster[provider_gcp_attributes][gcp_project_id]', with: 'gcp-project-123' fill_in 'cluster[provider_gcp_attributes][zone]', with: 'us-central1-a' fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2' - click_button 'Create Kubernetes cluster' + end + + it 'users sees a form with the GCP token' do + expect(page).to have_selector(:css, 'form[data-token="token"]') end it 'user sees a cluster details page and creation status' do + subject + expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...') Clusters::Cluster.last.provider.make_created! @@ -64,6 +71,8 @@ feature 'Gcp Cluster', :js do end it 'user sees a error if something wrong during creation' do + subject + expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...') Clusters::Cluster.last.provider.make_errored!('Something wrong!') diff --git a/spec/features/projects/merge_requests/user_views_diffs_spec.rb b/spec/features/projects/merge_requests/user_views_diffs_spec.rb index 295eb02b625..d36aafdbc54 100644 --- a/spec/features/projects/merge_requests/user_views_diffs_spec.rb +++ b/spec/features/projects/merge_requests/user_views_diffs_spec.rb @@ -26,6 +26,10 @@ describe 'User views diffs', :js do expect(page).to have_css('#inline-diff-btn', count: 1) end + it 'hides loading spinner after load' do + expect(page).not_to have_selector('.mr-loading-status .loading', visible: true) + end + context 'when in the inline view' do include_examples 'unfold diffs' end diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb index b2906e315f7..fce41ce347f 100644 --- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb @@ -64,7 +64,7 @@ feature 'Setup Mattermost slash commands', :js do click_link 'Add to Mattermost' expect(page).to have_content('The team where the slash commands will be used in') - expect(page).to have_content('This is the only available team.') + expect(page).to have_content('This is the only available team that you are a member of.') end it 'shows a disabled prefilled select if user is a member of 1 team' do @@ -94,7 +94,7 @@ feature 'Setup Mattermost slash commands', :js do click_link 'Add to Mattermost' expect(page).to have_content('Select the team where the slash commands will be used in') - expect(page).to have_content('The list shows all available teams.') + expect(page).to have_content('The list shows all available teams that you are a member of.') end it 'shows a select with team options user is a member of multiple teams' do diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb index 8a9255db9e8..ee5734a9bf1 100644 --- a/spec/features/projects/user_sees_sidebar_spec.rb +++ b/spec/features/projects/user_sees_sidebar_spec.rb @@ -44,6 +44,18 @@ describe 'Projects > User sees sidebar' do expect(page).not_to have_content 'Repository' expect(page).not_to have_content 'CI / CD' expect(page).not_to have_content 'Merge Requests' + expect(page).not_to have_content 'Operations' + end + end + + it 'shows build tab if builds are public' do + project.public_builds = true + project.save + + visit project_path(project) + + within('.nav-sidebar') do + expect(page).to have_content 'CI / CD' end end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index e473739a6aa..bbdd98a7623 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -19,6 +19,7 @@ feature 'Projects > Wiki > User previews markdown changes', :js do visit project_path(project) find('.shortcuts-wiki').click + click_link "Create your first page" end context "while creating a new wiki page" do diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index 9989e1ffda7..706894f4b32 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -8,6 +8,7 @@ describe "User creates wiki page" do sign_in(user) visit(project_wikis_path(project)) + click_link "Create your first page" end context "when wiki is empty" do diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index e019e3ce5a5..272dac127dd 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -11,6 +11,7 @@ describe 'User updates wiki page' do context 'when wiki is empty' do before do visit(project_wikis_path(project)) + click_link "Create your first page" end context 'in a user namespace' do diff --git a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb new file mode 100644 index 00000000000..83ffbb4a94e --- /dev/null +++ b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +describe 'User views empty wiki' do + let(:user) { create(:user) } + + shared_examples 'empty wiki and accessible issues' do + it 'show "issue tracker" message' do + visit(project_wikis_path(project)) + + element = page.find('.row.empty-state') + + expect(element).to have_content('This project has no wiki pages') + expect(element).to have_link("issue tracker", href: project_issues_path(project)) + expect(element).to have_link("Suggest wiki improvement", href: new_project_issue_path(project)) + end + end + + shared_examples 'empty wiki and non-accessible issues' do + it 'does not show "issue tracker" message' do + visit(project_wikis_path(project)) + + element = page.find('.row.empty-state') + + expect(element).to have_content('This project has no wiki pages') + expect(element).to have_no_link('Suggest wiki improvement') + end + end + + context 'when user is logged out and issue tracker is public' do + let(:project) { create(:project, :public, :wiki_repo) } + + it_behaves_like 'empty wiki and accessible issues' + end + + context 'when user is logged in and not a member' do + let(:project) { create(:project, :public, :wiki_repo) } + + before do + sign_in(user) + end + + it_behaves_like 'empty wiki and accessible issues' + end + + context 'when issue tracker is private' do + let(:project) { create(:project, :public, :wiki_repo, :issues_private) } + + it_behaves_like 'empty wiki and non-accessible issues' + end + + context 'when issue tracker is disabled' do + let(:project) { create(:project, :public, :wiki_repo, :issues_disabled) } + + it_behaves_like 'empty wiki and non-accessible issues' + end + + context 'when user is logged in and a memeber' do + let(:project) { create(:project, :public, :wiki_repo) } + + before do + sign_in(user) + project.add_developer(user) + end + + it 'show "create first page" message' do + visit(project_wikis_path(project)) + + element = page.find('.row.empty-state') + + element.click_link 'Create your first page' + + expect(page).to have_button('Create page') + end + end +end diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb index 6661714222a..1de7d9a56a8 100644 --- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb @@ -18,6 +18,7 @@ describe 'User views a wiki page' do context 'when wiki is empty' do before do visit(project_wikis_path(project)) + click_link "Create your first page" click_on('New page') @@ -140,6 +141,7 @@ describe 'User views a wiki page' do visit(project_path(project)) find('.shortcuts-wiki').click + click_link "Create your first page" expect(page).to have_content('Home · Create Page') end diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index e8884bc1a00..c8db82a562f 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -14,7 +14,9 @@ feature 'User uploads avatar to group' do visible: false ) - click_button 'Save group' + page.within('.gs-general') do + click_button 'Save group' + end visit group_path(group) diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb index f9469adbfe3..1efa5cd5490 100644 --- a/spec/features/users/terms_spec.rb +++ b/spec/features/users/terms_spec.rb @@ -62,7 +62,8 @@ describe 'Users > Terms' do expect(current_path).to eq(project_issues_path(project)) end - it 'redirects back to the page the user was trying to save' do + # Disabled until https://gitlab.com/gitlab-org/gitlab-ce/issues/37162 is solved properly + xit 'redirects back to the page the user was trying to save' do visit new_project_issue_path(project) fill_in :issue_title, with: 'Hello world, a new issue' diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb index 7bede0b0d48..5478e38ce70 100644 --- a/spec/features/users/user_browses_projects_on_user_page_spec.rb +++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb @@ -26,18 +26,23 @@ describe 'Users > User browses projects on user page', :js do end end + it 'hides loading spinner after load', :js do + visit user_path(user) + click_nav_link('Personal projects') + + wait_for_requests + + expect(page).not_to have_selector('.loading-status .loading', visible: true) + end + it 'paginates projects', :js do project = create(:project, namespace: user.namespace, updated_at: 2.minutes.since) project2 = create(:project, namespace: user.namespace, updated_at: 1.minute.since) allow(Project).to receive(:default_per_page).and_return(1) sign_in(user) - visit user_path(user) - - page.within('.user-profile-nav') do - click_link('Personal projects') - end + click_nav_link('Personal projects') wait_for_requests @@ -92,7 +97,6 @@ describe 'Users > User browses projects on user page', :js do click_nav_link('Personal projects') expect(title).to start_with(user.name) - expect(page).to have_content(private_project.name) expect(page).to have_content(public_project.name) expect(page).to have_content(internal_project.name) diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 233102c4314..7be8c9e3e67 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -112,7 +112,8 @@ "rebase_commit_sha": { "type": ["string", "null"] }, "rebase_in_progress": { "type": "boolean" }, "can_push_to_source_branch": { "type": "boolean" }, - "rebase_path": { "type": ["string", "null"] } + "rebase_path": { "type": ["string", "null"] }, + "squash": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json index b5c74bcc26e..9af7a8c9f4f 100644 --- a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json +++ b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json @@ -73,7 +73,8 @@ "should_remove_source_branch": { "type": ["boolean", "null"] }, "force_remove_source_branch": { "type": ["boolean", "null"] }, "web_url": { "type": "uri" }, - "subscribed": { "type": ["boolean"] } + "subscribed": { "type": ["boolean"] }, + "squash": { "type": "boolean" } }, "required": [ "id", "iid", "project_id", "title", "description", @@ -83,7 +84,7 @@ "labels", "work_in_progress", "milestone", "merge_when_build_succeeds", "merge_status", "sha", "merge_commit_sha", "user_notes_count", "should_remove_source_branch", "force_remove_source_branch", - "web_url", "subscribed" + "web_url", "subscribed", "squash" ], "additionalProperties": false } diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json index 0dc2eabec5d..f97461ce9cc 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json @@ -75,6 +75,7 @@ "force_remove_source_branch": { "type": ["boolean", "null"] }, "discussion_locked": { "type": ["boolean", "null"] }, "web_url": { "type": "uri" }, + "squash": { "type": "boolean" }, "time_stats": { "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, @@ -91,7 +92,7 @@ "labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds", "merge_status", "sha", "merge_commit_sha", "user_notes_count", "should_remove_source_branch", "force_remove_source_branch", - "web_url" + "web_url", "squash" ], "additionalProperties": false } diff --git a/spec/initializers/grape_route_helpers_fix_spec.rb b/spec/initializers/grape_route_helpers_fix_spec.rb deleted file mode 100644 index 2cf5924128f..00000000000 --- a/spec/initializers/grape_route_helpers_fix_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'spec_helper' -require_relative '../../config/initializers/grape_route_helpers_fix' - -describe 'route shadowing' do - include GrapeRouteHelpers::NamedRouteMatcher - - it 'does not occur' do - path = api_v4_projects_merge_requests_path(id: 1) - expect(path).to eq('/api/v4/projects/1/merge_requests') - - path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3) - expect(path).to eq('/api/v4/projects/1/merge_requests/3') - end -end diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc deleted file mode 100644 index 9eb0e732572..00000000000 --- a/spec/javascripts/.eslintrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "env": { - "jasmine": true - }, - "extends": "plugin:jasmine/recommended", - "globals": { - "appendLoadFixtures": false, - "appendLoadStyleFixtures": false, - "appendSetFixtures": false, - "appendSetStyleFixtures": false, - "getJSONFixture": false, - "loadFixtures": false, - "loadJSONFixtures": false, - "loadStyleFixtures": false, - "preloadFixtures": false, - "preloadStyleFixtures": false, - "readFixtures": false, - "sandbox": false, - "setFixtures": false, - "setStyleFixtures": false, - "spyOnDependency": false, - "spyOnEvent": false, - "ClassSpecHelper": false - }, - "plugins": ["jasmine"], - "rules": { - "func-names": 0, - "jasmine/no-suite-dupes": [1, "branch"], - "jasmine/no-spec-dupes": [1, "branch"], - "no-console": 0, - "prefer-arrow-callback": 0 - } -} diff --git a/spec/javascripts/.eslintrc.yml b/spec/javascripts/.eslintrc.yml new file mode 100644 index 00000000000..8bceb2c50fc --- /dev/null +++ b/spec/javascripts/.eslintrc.yml @@ -0,0 +1,34 @@ +--- +env: + jasmine: true +extends: plugin:jasmine/recommended +globals: + appendLoadFixtures: false + appendLoadStyleFixtures: false + appendSetFixtures: false + appendSetStyleFixtures: false + getJSONFixture: false + loadFixtures: false + loadJSONFixtures: false + loadStyleFixtures: false + preloadFixtures: false + preloadStyleFixtures: false + readFixtures: false + sandbox: false + setFixtures: false + setStyleFixtures: false + spyOnDependency: false + spyOnEvent: false + ClassSpecHelper: false +plugins: + - jasmine +rules: + func-names: off + jasmine/no-suite-dupes: + - warn + - branch + jasmine/no-spec-dupes: + - warn + - branch + no-console: off + prefer-arrow-callback: off diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index a143fc827d5..80c09a544d6 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -84,9 +84,14 @@ describe('iPython notebook renderer', () => { describe('error in JSON response', () => { let mock; - beforeEach((done) => { + beforeEach(done => { mock = new MockAdapter(axios); - mock.onGet('/test').reply(() => Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' })); + mock + .onGet('/test') + .reply(() => + // eslint-disable-next-line prefer-promise-reject-errors + Promise.reject({ status: 200, data: '{ "cells": [{"cell_type": "markdown"} }' }), + ); renderNotebook(); diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js index 664ea202e93..89a4fae4b59 100644 --- a/spec/javascripts/boards/board_blank_state_spec.js +++ b/spec/javascripts/boards/board_blank_state_spec.js @@ -1,4 +1,3 @@ -/* global BoardService */ import Vue from 'vue'; import '~/boards/stores/boards_store'; import BoardBlankState from '~/boards/components/board_blank_state.vue'; diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 13d607a06d2..9b4db774b63 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -1,7 +1,6 @@ /* global List */ /* global ListAssignee */ /* global ListLabel */ -/* global BoardService */ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index c06b2f60813..a49b190d36a 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -1,4 +1,3 @@ -/* global BoardService */ /* global List */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js index d5fbfdeaa91..ee37821ad08 100644 --- a/spec/javascripts/boards/board_new_issue_spec.js +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -1,4 +1,3 @@ -/* global BoardService */ /* global List */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 0cf9e4c9ba1..46fa10e1789 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle, one-var, no-unused-vars */ -/* global BoardService */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index 4a11131b55c..d90f9a41231 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle */ -/* global BoardService */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index d9a1d692949..d5d1139de15 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,5 +1,4 @@ /* eslint-disable comma-dangle */ -/* global BoardService */ /* global List */ /* global ListIssue */ diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js index 421fe62a1e7..d3776d0c3cf 100644 --- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js +++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js @@ -75,10 +75,7 @@ describe('Commit pipeline status component', () => { describe('When polling data was not succesful', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet('/dummy/endpoint').reply(() => { - const res = Promise.reject([502, { }]); - return res; - }); + mock.onGet('/dummy/endpoint').reply(502, {}); vm = new Component({ props: { endpoint: '/dummy/endpoint', diff --git a/spec/javascripts/helpers/vue_mount_component_helper.js b/spec/javascripts/helpers/vue_mount_component_helper.js index a34a1add4e0..5ba17ecf5b5 100644 --- a/spec/javascripts/helpers/vue_mount_component_helper.js +++ b/spec/javascripts/helpers/vue_mount_component_helper.js @@ -1,30 +1,18 @@ -import Vue from 'vue'; - -const mountComponent = (Component, props = {}, el = null) => new Component({ - propsData: props, -}).$mount(el); - -export const createComponentWithStore = (Component, store, propsData = {}) => new Component({ - store, - propsData, -}); +const mountComponent = (Component, props = {}, el = null) => + new Component({ + propsData: props, + }).$mount(el); -export const createComponentWithMixin = (mixins = [], state = {}, props = {}, template = '<div></div>') => { - const Component = Vue.extend({ - template, - mixins, - data() { - return props; - }, +export const createComponentWithStore = (Component, store, propsData = {}) => + new Component({ + store, + propsData, }); - return mountComponent(Component, props); -}; - export const mountComponentWithStore = (Component, { el, props, store }) => new Component({ store, - propsData: props || { }, + propsData: props || {}, }).$mount(el); export default mountComponent; diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js index d13d92c94e5..d4fcb2dc8ff 100644 --- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js +++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js @@ -47,7 +47,7 @@ describe('GkeProjectIdDropdown', () => { }); it('returns project billing validation text', () => { - vm.isValidatingProjectBilling = true; + vm.setIsValidatingProjectBilling(true); expect(vm.toggleText).toBe(LABELS.VALIDATING_PROJECT_BILLING); }); diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js index 46085fe97aa..9d892b8185b 100644 --- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js +++ b/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js @@ -50,6 +50,19 @@ describe('GCP Cluster Dropdown Store Actions', () => { }); }); + describe('setIsValidatingProjectBilling', () => { + it('should set machine type', done => { + testAction( + actions.setIsValidatingProjectBilling, + true, + { isValidatingProjectBilling: null }, + [{ type: 'SET_IS_VALIDATING_PROJECT_BILLING', payload: true }], + [], + done, + ); + }); + }); + describe('async fetch methods', () => { window.gapi = gapi(); @@ -74,10 +87,16 @@ describe('GCP Cluster Dropdown Store Actions', () => { true, { selectedProject: selectedProjectMock, + selectedZone: '', + selectedMachineType: '', projectHasBillingEnabled: null, }, - [{ type: 'SET_PROJECT_BILLING_STATUS', payload: true }], - [], + [ + { type: 'SET_ZONE', payload: '' }, + { type: 'SET_MACHINE_TYPE', payload: '' }, + { type: 'SET_PROJECT_BILLING_STATUS', payload: true }, + ], + [{ type: 'setIsValidatingProjectBilling', payload: false }], done, ); }); diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js index 0c173062835..6110d5d89ac 100644 --- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js +++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js @@ -8,10 +8,7 @@ describe('Confidential Issue Sidebar Block', () => { beforeEach(() => { const Component = Vue.extend(confidentialIssueSidebar); const service = { - update: () => new Promise((resolve, reject) => { - resolve(true); - reject('failed!'); - }), + update: () => Promise.resolve(true), }; vm1 = new Component({ diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 5a1ace2b4d6..8fec6ae3fa4 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,5 +1,5 @@ /* eslint-disable prefer-rest-params, wrap-iife, -no-unused-expressions, no-return-assign, no-param-reassign*/ +no-unused-expressions, no-return-assign, no-param-reassign */ export default class MockU2FDevice { constructor() { diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index a44243ac82d..023bedaaebb 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -71,6 +71,40 @@ describe Backup::Repository do end end + describe '#delete_all_repositories', :seed_helper do + shared_examples('delete_all_repositories') do + before do + allow(FileUtils).to receive(:mkdir_p).and_call_original + allow(FileUtils).to receive(:mv).and_call_original + end + + after(:all) do + ensure_seeds + end + + it 'removes all repositories' do + # Sanity check: there should be something for us to delete + expect(list_repositories).to include(File.join(SEED_STORAGE_PATH, TEST_REPO_PATH)) + + subject.delete_all_repositories('default', Gitlab.config.repositories.storages['default']) + + expect(list_repositories).to be_empty + end + + def list_repositories + Dir[SEED_STORAGE_PATH + '/*.git'] + end + end + + context 'with gitaly' do + it_behaves_like 'delete_all_repositories' + end + + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like 'delete_all_repositories' + end + end + describe '#empty_repo?' do context 'for a wiki' do let(:wiki) { create(:project_wiki) } diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb index fa209bed74e..002ce776be9 100644 --- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb +++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb @@ -22,7 +22,8 @@ describe Gitlab::Auth::UserAccessDeniedReason do enforce_terms end - it { is_expected.to match /You must accept the Terms of Service/ } + it { is_expected.to match /must accept the Terms of Service/ } + it { is_expected.to include(user.username) } end context 'when the user is internal' do diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index c63120b0b29..05c232d22cf 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -19,6 +19,18 @@ describe Gitlab::BitbucketImport::Importer do ] end + let(:reporters) do + [ + nil, + { "username" => "reporter1" }, + nil, + { "username" => "reporter2" }, + { "username" => "reporter1" }, + nil, + { "username" => "reporter3" } + ] + end + let(:sample_issues_statuses) do issues = [] @@ -36,6 +48,10 @@ describe Gitlab::BitbucketImport::Importer do } end + reporters.map.with_index do |reporter, index| + issues[index]['reporter'] = reporter + end + issues end @@ -147,5 +163,19 @@ describe Gitlab::BitbucketImport::Importer do expect(importer.errors).to be_empty end end + + describe 'issue import' do + it 'maps reporters to anonymous if bitbucket reporter is nil' do + allow(importer).to receive(:import_wiki) + importer.execute + + expect(project.issues.size).to eq(7) + expect(project.issues.where("description LIKE ?", '%Anonymous%').size).to eq(3) + expect(project.issues.where("description LIKE ?", '%reporter1%').size).to eq(2) + expect(project.issues.where("description LIKE ?", '%reporter2%').size).to eq(1) + expect(project.issues.where("description LIKE ?", '%reporter3%').size).to eq(1) + expect(importer.errors).to be_empty + end + end end end diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb index 9d9caaabe16..407d9470785 100644 --- a/spec/lib/gitlab/database/count_spec.rb +++ b/spec/lib/gitlab/database/count_spec.rb @@ -3,59 +3,68 @@ require 'spec_helper' describe Gitlab::Database::Count do before do create_list(:project, 3) + create(:identity) end - describe '.execute_estimate_if_updated_recently', :postgresql do - context 'when reltuples have not been updated' do - before do - expect(described_class).to receive(:reltuples_updated_recently?).and_return(false) - end + let(:models) { [Project, Identity] } - it 'returns nil' do - expect(described_class.execute_estimate_if_updated_recently(Project)).to be nil - end - end + describe '.approximate_counts' do + context 'with MySQL' do + context 'when reltuples have not been updated' do + it 'counts all models the normal way' do + expect(Gitlab::Database).to receive(:postgresql?).and_return(false) - context 'when reltuples have been updated' do - before do - ActiveRecord::Base.connection.execute('ANALYZE projects') - end + expect(Project).to receive(:count).and_call_original + expect(Identity).to receive(:count).and_call_original - it 'calls postgresql_estimate_query' do - expect(described_class).to receive(:postgresql_estimate_query).with(Project).and_call_original - expect(described_class.execute_estimate_if_updated_recently(Project)).to eq(3) + expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 }) + end end end - end - describe '.approximate_count' do - context 'when reltuples have not been updated' do - it 'counts all projects the normal way' do - allow(described_class).to receive(:reltuples_updated_recently?).and_return(false) + context 'with PostgreSQL', :postgresql do + describe 'when reltuples have not been updated' do + it 'counts all models the normal way' do + expect(described_class).to receive(:reltuples_from_recently_updated).with(%w(projects identities)).and_return({}) - expect(Project).to receive(:count).and_call_original - expect(described_class.approximate_count(Project)).to eq(3) + expect(Project).to receive(:count).and_call_original + expect(Identity).to receive(:count).and_call_original + expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 }) + end end - end - context 'no permission' do - it 'falls back to standard query' do - allow(described_class).to receive(:reltuples_updated_recently?).and_raise(PG::InsufficientPrivilege) + describe 'no permission' do + it 'falls back to standard query' do + allow(described_class).to receive(:postgresql_estimate_query).and_raise(PG::InsufficientPrivilege) - expect(Project).to receive(:count).and_call_original - expect(described_class.approximate_count(Project)).to eq(3) + expect(Project).to receive(:count).and_call_original + expect(Identity).to receive(:count).and_call_original + expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 }) + end end - end - describe 'when reltuples have been updated', :postgresql do - before do - ActiveRecord::Base.connection.execute('ANALYZE projects') + describe 'when some reltuples have been updated' do + it 'counts projects in the fast way' do + expect(described_class).to receive(:reltuples_from_recently_updated).with(%w(projects identities)).and_return({ 'projects' => 3 }) + + expect(Project).not_to receive(:count).and_call_original + expect(Identity).to receive(:count).and_call_original + expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 }) + end end - it 'counts all projects in the fast way' do - expect(described_class).to receive(:postgresql_estimate_query).with(Project).and_call_original + describe 'when all reltuples have been updated' do + before do + ActiveRecord::Base.connection.execute('ANALYZE projects') + ActiveRecord::Base.connection.execute('ANALYZE identities') + end + + it 'counts models with the standard way' do + expect(Project).not_to receive(:count) + expect(Identity).not_to receive(:count) - expect(described_class.approximate_count(Project)).to eq(3) + expect(described_class.approximate_counts(models)).to eq({ Project => 3, Identity => 1 }) + end end end end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 0c2e18c268a..0588fe935c3 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -468,4 +468,58 @@ describe Gitlab::Diff::File do end end end + + describe '#diff_hunk' do + let(:raw_diff) do + <<EOS +@@ -6,12 +6,18 @@ module Popen + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) +- raise "System commands must be given as an array of strings" ++ raise RuntimeError, "System commands must be given as an array of strings" + end + + path ||= Dir.pwd +- vars = { "PWD" => path } +- options = { chdir: path } ++ ++ vars = { ++ "PWD" => path ++ } ++ ++ options = { ++ chdir: path ++ } + + unless File.directory?(path) + FileUtils.mkdir_p(path) +@@ -19,6 +25,7 @@ module Popen + + @cmd_output = "" + @cmd_status = 0 ++ + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read +EOS + end + + it 'returns raw diff up to given line index' do + allow(diff_file).to receive(:raw_diff) { raw_diff } + diff_line = instance_double(Gitlab::Diff::Line, index: 5) + + diff_hunk = <<EOS +@@ -6,12 +6,18 @@ module Popen + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) +- raise "System commands must be given as an array of strings" ++ raise RuntimeError, "System commands must be given as an array of strings" + end +EOS + + expect(diff_file.diff_hunk(diff_line)).to eq(diff_hunk) + end + end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 317a932d5a6..dfffea7797f 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1055,7 +1055,7 @@ describe Gitlab::GitAccess do it 'blocks access when the user did not accept terms', :aggregate_failures do actions.each do |action| - expect { action.call }.to raise_unauthorized(/You must accept the Terms of Service in order to perform this action/) + expect { action.call }.to raise_unauthorized(/must accept the Terms of Service in order to perform this action/) end end diff --git a/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb new file mode 100644 index 00000000000..f47b9dd3498 --- /dev/null +++ b/spec/lib/gitlab/grape_logging/loggers/queue_duration_logger_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gitlab::GrapeLogging::Loggers::QueueDurationLogger do + subject { described_class.new } + + describe ".parameters" do + let(:start_time) { Time.new(2018, 01, 01) } + + describe 'when no proxy time is available' do + let(:mock_request) { OpenStruct.new(env: {}) } + + it 'returns an empty hash' do + expect(subject.parameters(mock_request, nil)).to eq({}) + end + end + + describe 'when a proxy time is available' do + let(:mock_request) do + OpenStruct.new( + env: { + 'HTTP_GITLAB_WORKHORSE_PROXY_START' => (start_time - 1.hour).to_i * (10**9) + } + ) + end + + it 'returns the correct duration in ms' do + Timecop.freeze(start_time) do + subject.before + + expect(subject.parameters(mock_request, nil)).to eq( { 'queue_duration': 1.hour.to_f * 1000 }) + end + end + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8b46b04b8b5..fb5fd300dbb 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -35,6 +35,7 @@ notes: - todos - events - system_note_metadata +- note_diff_file label_links: - target - label diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb index cd5a1b2982b..536cc359d39 100644 --- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb @@ -15,7 +15,10 @@ describe Gitlab::ImportExport::AttributeCleaner do 'project_id' => 99, 'user_id' => 99, 'random_id_in_the_middle' => 99, - 'notid' => 99 + 'notid' => 99, + 'import_source' => 'whatever', + 'import_type' => 'whatever', + 'non_existent_attr' => 'whatever' } end @@ -28,10 +31,30 @@ describe Gitlab::ImportExport::AttributeCleaner do } end + let(:excluded_keys) { %w[import_source import_type] } + + subject { described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class, excluded_keys: excluded_keys) } + + before do + allow(relation_class).to receive(:attribute_method?).and_return(true) + allow(relation_class).to receive(:attribute_method?).with('non_existent_attr').and_return(false) + end + it 'removes unwanted attributes from the hash' do - # allow(relation_class).to receive(:attribute_method?).and_return(true) + expect(subject).to eq(post_safe_hash) + end + + it 'removes attributes not present in relation_class' do + expect(subject.keys).not_to include 'non_existent_attr' + end + + it 'removes excluded keys from the hash' do + expect(subject.keys).not_to include excluded_keys + end + + it 'does not remove excluded key if not listed' do parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class) - expect(parsed_hash).to eq(post_safe_hash) + expect(parsed_hash.keys).to eq post_safe_hash.keys + excluded_keys end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 4f64f2bd6b4..1b7fa11cb3c 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -1,5 +1,7 @@ { "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", + "import_type": "gitlab_project", + "creator_id": 123, "visibility_level": 10, "archived": false, "labels": [ diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 5dbf0ed289b..c13cf4a0507 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -1,5 +1,7 @@ { "description": "Nisi et repellendus ut enim quo accusamus vel magnam.", + "import_type": "gitlab_project", + "creator_id": 123, "visibility_level": 10, "archived": false, "milestones": [ diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 13a8c9adcee..68ddc947e02 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -23,6 +23,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch) project_tree_restorer = described_class.new(user: @user, shared: @shared, project: @project) + + expect(Gitlab::ImportExport::RelationFactory).to receive(:create).with(hash_including(excluded_keys: ['whatever'])).and_call_original.at_least(:once) + allow(project_tree_restorer).to receive(:excluded_keys_for_relation).and_return(['whatever']) + @restored_project_json = project_tree_restorer.restore end end @@ -248,6 +252,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(labels.where(type: "ProjectLabel").count).to eq(results.fetch(:first_issue_labels, 0)) expect(labels.where(type: "ProjectLabel").where.not(group_id: nil).count).to eq(0) end + + it 'does not set params that are excluded from import_export settings' do + expect(project.import_type).to be_nil + expect(project.creator_id).not_to eq 123 + end end shared_examples 'restores group correctly' do |**results| diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index 5c61a5a2044..5f0dfd64b15 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -4,12 +4,14 @@ describe Gitlab::ImportExport::RelationFactory do let(:project) { create(:project) } let(:members_mapper) { double('members_mapper').as_null_object } let(:user) { create(:admin) } + let(:excluded_keys) { [] } let(:created_object) do described_class.create(relation_sym: relation_sym, relation_hash: relation_hash, members_mapper: members_mapper, user: user, - project: project) + project: project, + excluded_keys: excluded_keys) end context 'hook object' do @@ -67,6 +69,14 @@ describe Gitlab::ImportExport::RelationFactory do expect(created_object.service_id).not_to eq(service_id) end end + + context 'excluded attributes' do + let(:excluded_keys) { %w[url] } + + it 'are removed from the imported object' do + expect(created_object.url).to be_nil + end + end end # Mocks an ActiveRecordish object with the dodgy columns diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 62da967cf96..3d5271cd030 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -165,6 +165,7 @@ MergeRequest: - approvals_before_merge - rebase_commit_sha - time_estimate +- squash - last_edited_at - last_edited_by_id - head_pipeline_id diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb index 8ba15ae0f38..7c194749dfb 100644 --- a/spec/lib/mattermost/command_spec.rb +++ b/spec/lib/mattermost/command_spec.rb @@ -21,13 +21,13 @@ describe Mattermost::Command do context 'for valid trigger word' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .with(body: { team_id: 'abc', trigger: 'gitlab' }.to_json) .to_return( - status: 200, + status: 201, headers: { 'Content-Type' => 'application/json' }, body: { token: 'token' }.to_json ) @@ -40,16 +40,16 @@ describe Mattermost::Command do context 'for error message' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .to_return( - status: 500, + status: 400, headers: { 'Content-Type' => 'application/json' }, body: { id: 'api.command.duplicate_trigger.app_error', message: 'This trigger word is already in use. Please choose another word.', detailed_error: '', request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 + status_code: 400 }.to_json ) end diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb index c855643c4d8..5410bfbeb31 100644 --- a/spec/lib/mattermost/session_spec.rb +++ b/spec/lib/mattermost/session_spec.rb @@ -22,8 +22,8 @@ describe Mattermost::Session, type: :request do let(:location) { 'http://location.tld' } let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'} let!(:stub) do - WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login") - .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 307) + WebMock.stub_request(:get, "#{mattermost_url}/oauth/gitlab/login") + .to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302) end context 'without oauth uri' do @@ -76,7 +76,7 @@ describe Mattermost::Session, type: :request do end end - WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout") + WebMock.stub_request(:post, "#{mattermost_url}/api/v4/users/logout") .to_return(headers: { Authorization: 'token thisworksnow' }, status: 200) end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb index 2cfa6802612..030aa5d06a8 100644 --- a/spec/lib/mattermost/team_spec.rb +++ b/spec/lib/mattermost/team_spec.rb @@ -12,26 +12,28 @@ describe Mattermost::Team do describe '#all' do subject { described_class.new(nil).all } + let(:test_team) do + { + "id" => "xiyro8huptfhdndadpz8r3wnbo", + "create_at" => 1482174222155, + "update_at" => 1482174222155, + "delete_at" => 0, + "display_name" => "chatops", + "name" => "chatops", + "email" => "admin@example.com", + "type" => "O", + "company_name" => "", + "allowed_domains" => "", + "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", + "allow_open_invite" => false + } + end + context 'for valid request' do - let(:response) do - { "xiyro8huptfhdndadpz8r3wnbo" => { - "id" => "xiyro8huptfhdndadpz8r3wnbo", - "create_at" => 1482174222155, - "update_at" => 1482174222155, - "delete_at" => 0, - "display_name" => "chatops", - "name" => "chatops", - "email" => "admin@example.com", - "type" => "O", - "company_name" => "", - "allowed_domains" => "", - "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro", - "allow_open_invite" => false - } } - end + let(:response) { [test_team] } before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 200, headers: { 'Content-Type' => 'application/json' }, @@ -39,14 +41,14 @@ describe Mattermost::Team do ) end - it 'returns a token' do - is_expected.to eq(response.values) + it 'returns teams' do + is_expected.to eq(response) end end context 'for error message' do before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 500, headers: { 'Content-Type' => 'application/json' }, @@ -89,9 +91,9 @@ describe Mattermost::Team do end before do - stub_request(:post, "http://mattermost.example.com/api/v3/teams/create") + stub_request(:post, "http://mattermost.example.com/api/v4/teams") .to_return( - status: 200, + status: 201, body: response.to_json, headers: { 'Content-Type' => 'application/json' } ) @@ -104,7 +106,7 @@ describe Mattermost::Team do context 'for existing team' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/teams') .to_return( status: 400, headers: { 'Content-Type' => 'application/json' }, diff --git a/spec/migrations/fill_file_store_spec.rb b/spec/migrations/fill_file_store_spec.rb new file mode 100644 index 00000000000..5ff7aa56ce2 --- /dev/null +++ b/spec/migrations/fill_file_store_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180424151928_fill_file_store') + +describe FillFileStore, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:builds) { table(:ci_builds) } + let(:job_artifacts) { table(:ci_job_artifacts) } + let(:lfs_objects) { table(:lfs_objects) } + let(:uploads) { table(:uploads) } + + before do + namespaces.create!(id: 123, name: 'gitlab1', path: 'gitlab1') + projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 123) + builds.create!(id: 1) + + ## + # Create rows that have nullfied `file_store` column + job_artifacts.create!(project_id: 123, job_id: 1, file_type: 1, file_store: nil) + lfs_objects.create!(oid: 123, size: 10, file: 'file_name', file_store: nil) + uploads.create!(size: 10, path: 'path', uploader: 'uploader', mount_point: 'file_name', store: nil) + end + + it 'correctly migrates nullified file_store/store column' do + expect(job_artifacts.where(file_store: nil).count).to eq(1) + expect(lfs_objects.where(file_store: nil).count).to eq(1) + expect(uploads.where(store: nil).count).to eq(1) + + expect(job_artifacts.where(file_store: 1).count).to eq(0) + expect(lfs_objects.where(file_store: 1).count).to eq(0) + expect(uploads.where(store: 1).count).to eq(0) + + migrate! + + expect(job_artifacts.where(file_store: nil).count).to eq(0) + expect(lfs_objects.where(file_store: nil).count).to eq(0) + expect(uploads.where(store: nil).count).to eq(0) + + expect(job_artifacts.where(file_store: 1).count).to eq(1) + expect(lfs_objects.where(file_store: 1).count).to eq(1) + expect(uploads.where(store: 1).count).to eq(1) + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 7e47043a1cb..f8f07205623 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -391,68 +391,6 @@ describe ApplicationSetting do end describe 'performance bar settings' do - describe 'performance_bar_allowed_group_id=' do - context 'with a blank path' do - before do - setting.performance_bar_allowed_group_id = create(:group).full_path - end - - it 'persists nil for a "" path and clears allowed user IDs cache' do - expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_allowed_group_id = '' - - expect(setting.performance_bar_allowed_group_id).to be_nil - end - end - - context 'with an invalid path' do - it 'does not persist an invalid group path' do - setting.performance_bar_allowed_group_id = 'foo' - - expect(setting.performance_bar_allowed_group_id).to be_nil - end - end - - context 'with a path to an existing group' do - let(:group) { create(:group) } - - it 'persists a valid group path and clears allowed user IDs cache' do - expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_allowed_group_id = group.full_path - - expect(setting.performance_bar_allowed_group_id).to eq(group.id) - end - - context 'when the given path is the same' do - context 'with a blank path' do - before do - setting.performance_bar_allowed_group_id = nil - end - - it 'clears the cached allowed user IDs' do - expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_allowed_group_id = '' - end - end - - context 'with a valid path' do - before do - setting.performance_bar_allowed_group_id = group.full_path - end - - it 'clears the cached allowed user IDs' do - expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_allowed_group_id = group.full_path - end - end - end - end - end - describe 'performance_bar_allowed_group' do context 'with no performance_bar_allowed_group_id saved' do it 'returns nil' do @@ -464,11 +402,11 @@ describe ApplicationSetting do let(:group) { create(:group) } before do - setting.performance_bar_allowed_group_id = group.full_path + setting.update!(performance_bar_allowed_group_id: group.id) end it 'returns the group' do - expect(setting.performance_bar_allowed_group).to eq(group) + expect(setting.reload.performance_bar_allowed_group).to eq(group) end end end @@ -478,67 +416,11 @@ describe ApplicationSetting do let(:group) { create(:group) } before do - setting.performance_bar_allowed_group_id = group.full_path + setting.update!(performance_bar_allowed_group_id: group.id) end it 'returns true' do - expect(setting.performance_bar_enabled).to be_truthy - end - end - end - - describe 'performance_bar_enabled=' do - context 'when the performance bar is enabled' do - let(:group) { create(:group) } - - before do - setting.performance_bar_allowed_group_id = group.full_path - end - - context 'when passing true' do - it 'does not clear allowed user IDs cache' do - expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_enabled = true - - expect(setting.performance_bar_allowed_group_id).to eq(group.id) - expect(setting.performance_bar_enabled).to be_truthy - end - end - - context 'when passing false' do - it 'disables the performance bar and clears allowed user IDs cache' do - expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_enabled = false - - expect(setting.performance_bar_allowed_group_id).to be_nil - expect(setting.performance_bar_enabled).to be_falsey - end - end - end - - context 'when the performance bar is disabled' do - context 'when passing true' do - it 'does nothing and does not clear allowed user IDs cache' do - expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_enabled = true - - expect(setting.performance_bar_allowed_group_id).to be_nil - expect(setting.performance_bar_enabled).to be_falsey - end - end - - context 'when passing false' do - it 'does nothing and does not clear allowed user IDs cache' do - expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache) - - setting.performance_bar_enabled = false - - expect(setting.performance_bar_allowed_group_id).to be_nil - expect(setting.performance_bar_enabled).to be_falsey - end + expect(setting.reload.performance_bar_enabled).to be_truthy end end end diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb new file mode 100644 index 00000000000..c16b245bea8 --- /dev/null +++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe BatchDestroyDependentAssociations do + class TestProject < ActiveRecord::Base + self.table_name = 'projects' + + has_many :builds, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + has_many :pages_domains + has_many :todos + + include BatchDestroyDependentAssociations + end + + describe '#dependent_associations_to_destroy' do + set(:project) { TestProject.new } + + it 'returns the right associations' do + expect(project.dependent_associations_to_destroy.map(&:name)).to match_array([:builds]) + end + end + + describe '#destroy_dependent_associations_in_batches' do + set(:project) { create(:project) } + set(:build) { create(:ci_build, project: project) } + set(:notification_setting) { create(:notification_setting, project: project) } + let!(:todos) { create(:todo, project: project) } + + it 'destroys multiple builds' do + create(:ci_build, project: project) + + expect(Ci::Build.count).to eq(2) + + project.destroy_dependent_associations_in_batches + + expect(Ci::Build.count).to eq(0) + end + + it 'destroys builds in batches' do + expect(project).to receive_message_chain(:builds, :find_each).and_yield(build) + expect(build).to receive(:destroy).and_call_original + + project.destroy_dependent_associations_in_batches + + expect(Ci::Build.count).to eq(0) + expect(Todo.count).to eq(1) + expect(User.count).to be > 0 + expect(NotificationSetting.count).to eq(User.count) + end + + it 'excludes associations' do + project.destroy_dependent_associations_in_batches(exclude: [:builds]) + + expect(Ci::Build.count).to eq(1) + expect(Todo.count).to eq(1) + expect(User.count).to be > 0 + expect(NotificationSetting.count).to eq(User.count) + end + end +end diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb index 30572ce9332..8cd129dc851 100644 --- a/spec/models/concerns/discussion_on_diff_spec.rb +++ b/spec/models/concerns/discussion_on_diff_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe DiscussionOnDiff do - subject { create(:diff_note_on_merge_request).to_discussion } + subject { create(:diff_note_on_merge_request, line_number: 18).to_discussion } describe "#truncated_diff_lines" do let(:truncated_lines) { subject.truncated_diff_lines } diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index fb51c0172ab..8624f0daa4d 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -84,7 +84,47 @@ describe DiffNote do end end - describe "#diff_file" do + describe '#create_diff_file callback' do + let(:noteable) { create(:merge_request) } + let(:project) { noteable.project } + + context 'merge request' do + let!(:diff_note) { create(:diff_note_on_merge_request, project: project, noteable: noteable) } + + it 'creates a diff note file' do + expect(diff_note.reload.note_diff_file).to be_present + end + + it 'does not create diff note file if it is a reply' do + expect { create(:diff_note_on_merge_request, noteable: noteable, in_reply_to: diff_note) } + .not_to change(NoteDiffFile, :count) + end + end + + context 'commit' do + let!(:diff_note) { create(:diff_note_on_commit, project: project) } + + it 'creates a diff note file' do + expect(diff_note.reload.note_diff_file).to be_present + end + + it 'does not create diff note file if it is a reply' do + expect { create(:diff_note_on_commit, in_reply_to: diff_note) } + .not_to change(NoteDiffFile, :count) + end + end + end + + describe '#diff_file', :clean_gitlab_redis_shared_state do + context 'when note_diff_file association exists' do + it 'returns persisted diff file data' do + diff_file = subject.diff_file + + expect(diff_file.diff.to_hash.with_indifferent_access) + .to include(subject.note_diff_file.to_hash) + end + end + context 'when the discussion was created in the diff' do it 'returns correct diff file' do diff_file = subject.diff_file @@ -115,6 +155,26 @@ describe DiffNote do expect(diff_file.diff_refs).to eq(position.diff_refs) end end + + context 'note diff file creation enqueuing' do + it 'enqueues CreateNoteDiffFileWorker if it is the first note of a discussion' do + subject.note_diff_file.destroy! + + expect(CreateNoteDiffFileWorker).to receive(:perform_async).with(subject.id) + + subject.reload.diff_file + end + + it 'does not enqueues CreateNoteDiffFileWorker if not first note of a discussion' do + mr = create(:merge_request) + diff_note = create(:diff_note_on_merge_request, project: mr.project, noteable: mr) + reply_diff_note = create(:diff_note_on_merge_request, in_reply_to: diff_note) + + expect(CreateNoteDiffFileWorker).not_to receive(:perform_async).with(reply_diff_note.id) + + reply_diff_note.reload.diff_file + end + end end describe "#diff_line" do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 8ea92410022..c1eac4fa489 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -50,6 +50,19 @@ describe Event do end end + describe '#set_last_repository_updated_at' do + it 'only updates once every Event::REPOSITORY_UPDATED_AT_INTERVAL minutes' do + last_known_timestamp = (Event::REPOSITORY_UPDATED_AT_INTERVAL - 1.minute).ago + project.update(last_repository_updated_at: last_known_timestamp) + project.reload # a reload removes fractions of seconds + + expect do + create_push_event(project, project.owner) + project.reload + end.not_to change { project.last_repository_updated_at } + end + end + describe 'after_create :track_user_interacted_projects' do let(:event) { build(:push_event, project: project, author: project.owner) } diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 8ef91e8fab5..581fd0293cc 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -5,7 +5,7 @@ describe InternalId do let(:usage) { :issues } let(:issue) { build(:issue, project: project) } let(:scope) { { project: project } } - let(:init) { ->(s) { s.project.issues.maximum(:iid) } } + let(:init) { ->(s) { s.project.issues.size } } context 'validations' do it { is_expected.to validate_presence_of(:usage) } @@ -39,29 +39,6 @@ describe InternalId do end end - context 'with an InternalId record present and existing issues with a higher internal id' do - # This can happen if the old NonatomicInternalId is still in use - before do - issues = Array.new(rand(1..10)).map { create(:issue, project: project) } - - issue = issues.last - issue.iid = issues.map { |i| i.iid }.max + 1 - issue.save - end - - let(:maximum_iid) { project.issues.map { |i| i.iid }.max } - - it 'updates last_value to the maximum internal id present' do - subject - - expect(described_class.find_by(project: project, usage: described_class.usages[usage.to_s]).last_value).to eq(maximum_iid + 1) - end - - it 'returns next internal id correctly' do - expect(subject).to eq(maximum_iid + 1) - end - end - context 'with concurrent inserts on table' do it 'looks up the record if it was created concurrently' do args = { **scope, usage: described_class.usages[usage.to_s] } @@ -104,8 +81,7 @@ describe InternalId do describe '#increment_and_save!' do let(:id) { create(:internal_id) } - let(:maximum_iid) { nil } - subject { id.increment_and_save!(maximum_iid) } + subject { id.increment_and_save! } it 'returns incremented iid' do value = id.last_value @@ -126,14 +102,5 @@ describe InternalId do expect(subject).to eq(1) end end - - context 'with maximum_iid given' do - let(:id) { create(:internal_id, last_value: 1) } - let(:maximum_iid) { id.last_value + 10 } - - it 'returns maximum_iid instead' do - expect(subject).to eq(12) - end - end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 92e33a64d26..9ffa91fc265 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -14,6 +14,65 @@ describe MergeRequest do it { is_expected.to have_many(:merge_request_diffs) } end + describe '#squash_in_progress?' do + shared_examples 'checking whether a squash is in progress' do + let(:repo_path) { subject.source_project.repository.path } + let(:squash_path) { File.join(repo_path, "gitlab-worktree", "squash-#{subject.id}") } + + before do + system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{squash_path} master)) + end + + it 'returns true when there is a current squash directory' do + expect(subject.squash_in_progress?).to be_truthy + end + + it 'returns false when there is no squash directory' do + FileUtils.rm_rf(squash_path) + + expect(subject.squash_in_progress?).to be_falsey + end + + it 'returns false when the squash directory has expired' do + time = 20.minutes.ago.to_time + File.utime(time, time, squash_path) + + expect(subject.squash_in_progress?).to be_falsey + end + + it 'returns false when the source project has been removed' do + allow(subject).to receive(:source_project).and_return(nil) + + expect(subject.squash_in_progress?).to be_falsey + end + end + + context 'when Gitaly squash_in_progress is enabled' do + it_behaves_like 'checking whether a squash is in progress' + end + + context 'when Gitaly squash_in_progress is disabled', :disable_gitaly do + it_behaves_like 'checking whether a squash is in progress' + end + end + + describe '#squash?' do + let(:merge_request) { build(:merge_request, squash: squash) } + subject { merge_request.squash? } + + context 'disabled in database' do + let(:squash) { false } + + it { is_expected.to be_falsy } + end + + context 'enabled in database' do + let(:squash) { true } + + it { is_expected.to be_truthy } + end + end + describe 'modules' do subject { described_class } diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb new file mode 100644 index 00000000000..591c1a89748 --- /dev/null +++ b/spec/models/note_diff_file_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +describe NoteDiffFile do + describe 'associations' do + it { is_expected.to belong_to(:diff_note) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:diff_note) } + end +end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 05d33cd3874..1983e0cc967 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -25,7 +25,7 @@ describe MattermostSlashCommandsService do context 'the requests succeeds' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .with(body: { team_id: 'abc', trigger: 'gitlab', @@ -59,7 +59,7 @@ describe MattermostSlashCommandsService do context 'an error is received' do before do - stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create') + stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .to_return( status: 500, headers: { 'Content-Type' => 'application/json' }, @@ -89,11 +89,11 @@ describe MattermostSlashCommandsService do context 'the requests succeeds' do before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 200, headers: { 'Content-Type' => 'application/json' }, - body: { 'list' => true }.to_json + body: [{ id: 'test_team_id' }].to_json ) end @@ -104,7 +104,7 @@ describe MattermostSlashCommandsService do context 'an error is received' do before do - stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all') + stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( status: 500, headers: { 'Content-Type' => 'application/json' }, diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index ae9c0e9c304..32fc704a79b 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -171,7 +171,7 @@ describe API::DeployKeys do deploy_key end - it 'deletes existing key' do + it 'removes existing key from project' do expect do delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) @@ -179,6 +179,44 @@ describe API::DeployKeys do end.to change { project.deploy_keys.count }.by(-1) end + context 'when the deploy key is public' do + it 'does not delete the deploy key' do + expect do + delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) + + expect(response).to have_gitlab_http_status(204) + end.not_to change { DeployKey.count } + end + end + + context 'when the deploy key is not public' do + let!(:deploy_key) { create(:deploy_key, public: false) } + + context 'when the deploy key is only used by this project' do + it 'deletes the deploy key' do + expect do + delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) + + expect(response).to have_gitlab_http_status(204) + end.to change { DeployKey.count }.by(-1) + end + end + + context 'when the deploy key is used by other projects' do + before do + create(:deploy_keys_project, project: project2, deploy_key: deploy_key) + end + + it 'does not delete the deploy key' do + expect do + delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin) + + expect(response).to have_gitlab_http_status(204) + end.not_to change { DeployKey.count } + end + end + end + it 'returns 404 Not Found with invalid ID' do delete api("/projects/#{project.id}/deploy_keys/404", admin) diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index d3ab44c0d7e..d8a51f36dba 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -171,7 +171,7 @@ describe API::Helpers do end it 'returns a 403 when a user has not accepted the terms' do - expect { current_user }.to raise_error /You must accept the Terms of Service/ + expect { current_user }.to raise_error /must accept the Terms of Service/ end it 'sets the current user when the user accepted the terms' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index db8c5f963d6..5dc3ddd4b36 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe API::Internal do - let(:user) { create(:user) } + set(:user) { create(:user) } let(:key) { create(:key, user: user) } - let(:project) { create(:project, :repository, :wiki_repo) } + set(:project) { create(:project, :repository, :wiki_repo) } let(:secret_token) { Gitlab::Shell.secret_token } let(:gl_repository) { "project-#{project.id}" } let(:reference_counter) { double('ReferenceCounter') } @@ -277,7 +277,7 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) + expect(json_response["repository_path"]).to eq('/') expect(json_response["gl_repository"]).to eq("wiki-#{project.id}") expect(user).not_to have_an_activity_record end @@ -289,7 +289,7 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo) + expect(json_response["repository_path"]).to eq('/') expect(json_response["gl_repository"]).to eq("wiki-#{project.id}") expect(user).to have_an_activity_record end @@ -301,7 +301,7 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["repository_path"]).to eq('/') expect(json_response["gl_repository"]).to eq("project-#{project.id}") expect(json_response["gitaly"]).not_to be_nil expect(json_response["gitaly"]["repository"]).not_to be_nil @@ -320,7 +320,7 @@ describe API::Internal do expect(response).to have_gitlab_http_status(200) expect(json_response["status"]).to be_truthy - expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["repository_path"]).to eq('/') expect(json_response["gl_repository"]).to eq("project-#{project.id}") expect(json_response["gitaly"]).not_to be_nil expect(json_response["gitaly"]["repository"]).not_to be_nil diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 0a2963452e4..45082e644ca 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -13,7 +13,10 @@ describe API::Jobs do ref: project.default_branch) end - let!(:job) { create(:ci_build, :success, pipeline: pipeline) } + let!(:job) do + create(:ci_build, :success, pipeline: pipeline, + artifacts_expire_at: 1.day.since) + end let(:user) { create(:user) } let(:api_user) { user } @@ -43,6 +46,7 @@ describe API::Jobs do it 'returns correct values' do expect(json_response).not_to be_empty expect(json_response.first['commit']['id']).to eq project.commit.id + expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) end it 'returns pipeline data' do @@ -128,6 +132,7 @@ describe API::Jobs do it 'returns correct values' do expect(json_response).not_to be_empty expect(json_response.first['commit']['id']).to eq project.commit.id + expect(Time.parse(json_response.first['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) end it 'returns pipeline data' do @@ -201,6 +206,7 @@ describe API::Jobs do expect(Time.parse(json_response['created_at'])).to be_like_time(job.created_at) expect(Time.parse(json_response['started_at'])).to be_like_time(job.started_at) expect(Time.parse(json_response['finished_at'])).to be_like_time(job.finished_at) + expect(Time.parse(json_response['artifacts_expire_at'])).to be_like_time(job.artifacts_expire_at) expect(json_response['duration']).to eq(job.duration) end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 1eeeb4f1045..8b168816d6c 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -29,6 +29,18 @@ describe API::MergeRequests do project.add_reporter(user) end + describe 'route shadowing' do + include GrapePathHelpers::NamedRouteMatcher + + it 'does not occur' do + path = api_v4_projects_merge_requests_path(id: 1) + expect(path).to eq('/api/v4/projects/1/merge_requests') + + path = api_v4_projects_merge_requests_path(id: 1, merge_request_iid: 3) + expect(path).to eq('/api/v4/projects/1/merge_requests/3') + end + end + describe 'GET /merge_requests' do context 'when unauthenticated' do it 'returns an array of all merge requests' do @@ -263,6 +275,7 @@ describe API::MergeRequests do expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) expect(json_response.first['merge_commit_sha']).not_to be_nil expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) + expect(json_response.first['squash']).to eq(merge_request_merged.squash) end it "returns an array of all merge_requests using simple mode" do @@ -671,12 +684,14 @@ describe API::MergeRequests do target_branch: 'master', author: user, labels: 'label, label2', - milestone_id: milestone.id + milestone_id: milestone.id, + squash: true expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(%w(label label2)) expect(json_response['milestone']['id']).to eq(milestone.id) + expect(json_response['squash']).to be_truthy expect(json_response['force_remove_source_branch']).to be_falsy end @@ -965,6 +980,14 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(200) end + it "updates the MR's squash attribute" do + expect do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), squash: true + end.to change { merge_request.reload.squash } + + expect(response).to have_gitlab_http_status(200) + end + it "enables merge when pipeline succeeds if the pipeline is active" do allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) @@ -1029,6 +1052,13 @@ describe API::MergeRequests do expect(json_response['milestone']['id']).to eq(milestone.id) end + it "updates squash and returns merge_request" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), squash: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['squash']).to be_truthy + end + it "returns merge_request with renamed target_branch" do put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki" expect(response).to have_gitlab_http_status(200) diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index efb9bddde44..6aadf839dbd 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -830,6 +830,21 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end end + context 'when job has already been finished' do + before do + job.trace.set('Job failed') + job.drop!(:script_failure) + end + + it 'does not update job status and job trace' do + update_job(state: 'success', trace: 'BUILD TRACE UPDATED') + + expect(response).to have_gitlab_http_status(403) + expect(job.trace.raw).to eq 'Job failed' + expect(job).to be_failed + end + end + def update_job(token = job.token, **params) new_params = params.merge(token: token) put api("/jobs/#{job.id}"), new_params @@ -1210,7 +1225,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do before do fog_connection.directories.get('artifacts').files.create( - key: 'tmp/upload/12312300', + key: 'tmp/uploads/12312300', body: 'content' ) diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 8b22d1e72f3..aead8978dd4 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -24,10 +24,15 @@ describe API::Settings, 'Settings' do expect(json_response['ecdsa_key_restriction']).to eq(0) expect(json_response['ed25519_key_restriction']).to eq(0) expect(json_response['circuitbreaker_failure_count_threshold']).not_to be_nil + expect(json_response['performance_bar_allowed_group_id']).to be_nil + expect(json_response).not_to have_key('performance_bar_allowed_group_path') + expect(json_response).not_to have_key('performance_bar_enabled') end end describe "PUT /application/settings" do + let(:group) { create(:group) } + context "custom repository storage type set in the config" do before do storages = { 'custom' => 'tmp/tests/custom_repositories' } @@ -56,7 +61,8 @@ describe API::Settings, 'Settings' do ed25519_key_restriction: 256, circuitbreaker_check_interval: 2, enforce_terms: true, - terms: 'Hello world!' + terms: 'Hello world!', + performance_bar_allowed_group_path: group.full_path expect(response).to have_gitlab_http_status(200) expect(json_response['default_projects_limit']).to eq(3) @@ -80,9 +86,27 @@ describe API::Settings, 'Settings' do expect(json_response['circuitbreaker_check_interval']).to eq(2) expect(json_response['enforce_terms']).to be(true) expect(json_response['terms']).to eq('Hello world!') + expect(json_response['performance_bar_allowed_group_id']).to eq(group.id) end end + it "supports legacy performance_bar_allowed_group_id" do + put api("/application/settings", admin), + performance_bar_allowed_group_id: group.full_path + + expect(response).to have_gitlab_http_status(200) + expect(json_response['performance_bar_allowed_group_id']).to eq(group.id) + end + + it "supports legacy performance_bar_enabled" do + put api("/application/settings", admin), + performance_bar_enabled: false, + performance_bar_allowed_group_id: group.full_path + + expect(response).to have_gitlab_http_status(200) + expect(json_response['performance_bar_allowed_group_id']).to be_nil + end + context "missing koding_url value when koding_enabled is true" do it "returns a blank parameter error message" do put api("/application/settings", admin), koding_enabled: true diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb index be70cb24dce..79a16fbd1b0 100644 --- a/spec/requests/api/v3/merge_requests_spec.rb +++ b/spec/requests/api/v3/merge_requests_spec.rb @@ -40,6 +40,7 @@ describe API::MergeRequests do expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha) expect(json_response.first['merge_commit_sha']).not_to be_nil expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha) + expect(json_response.first['squash']).to eq(merge_request_merged.squash) end it "returns an array of all merge_requests" do @@ -241,13 +242,15 @@ describe API::MergeRequests do author: user, labels: 'label, label2', milestone_id: milestone.id, - remove_source_branch: true + remove_source_branch: true, + squash: true expect(response).to have_gitlab_http_status(201) expect(json_response['title']).to eq('Test merge_request') expect(json_response['labels']).to eq(%w(label label2)) expect(json_response['milestone']['id']).to eq(milestone.id) expect(json_response['force_remove_source_branch']).to be_truthy + expect(json_response['squash']).to be_truthy end it "returns 422 when source_branch equals target_branch" do @@ -489,6 +492,14 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(200) end + it "updates the MR's squash attribute" do + expect do + put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user), squash: true + end.to change { merge_request.reload.squash } + + expect(response).to have_gitlab_http_status(200) + end + it "enables merge when pipeline succeeds if the pipeline is active" do allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline) allow(pipeline).to receive(:active?).and_return(true) @@ -529,6 +540,13 @@ describe API::MergeRequests do expect(json_response['milestone']['id']).to eq(milestone.id) end + it "updates squash and returns merge_request" do + put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), squash: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['squash']).to be_truthy + end + it "returns merge_request with renamed target_branch" do put v3_api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki" expect(response).to have_gitlab_http_status(200) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index f80abb06fca..79672fe1cc5 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1089,7 +1089,7 @@ describe 'Git LFS API and storage' do context 'with valid remote_id' do before do fog_connection.directories.get('lfs-objects').files.create( - key: 'tmp/upload/12312300', + key: 'tmp/uploads/12312300', body: 'content' ) end diff --git a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb index 7ddf9141fcd..03eeffe6483 100644 --- a/spec/rubocop/cop/line_break_around_conditional_block_spec.rb +++ b/spec/rubocop/cop/line_break_around_conditional_block_spec.rb @@ -256,6 +256,18 @@ describe RuboCop::Cop::LineBreakAroundConditionalBlock do expect(cop.offenses).to be_empty end + it "doesn't flag violation for #{conditional} followed by a comment" do + source = <<~RUBY + #{conditional} condition + do_something + end + # a short comment + RUBY + inspect_source(source) + + expect(cop.offenses).to be_empty + end + it "doesn't flag violation for #{conditional} followed by an end" do source = <<~RUBY class Foo diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb index fb07ecc6ae8..6337ee7d724 100644 --- a/spec/services/application_settings/update_service_spec.rb +++ b/spec/services/application_settings/update_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ApplicationSettings::UpdateService do - let(:application_settings) { Gitlab::CurrentSettings.current_application_settings } + let(:application_settings) { create(:application_setting) } let(:admin) { create(:user, :admin) } let(:params) { {} } @@ -54,4 +54,90 @@ describe ApplicationSettings::UpdateService do end end end + + describe 'performance bar settings' do + using RSpec::Parameterized::TableSyntax + + where(:params_performance_bar_enabled, + :params_performance_bar_allowed_group_path, + :previous_performance_bar_allowed_group_id, + :expected_performance_bar_allowed_group_id) do + true | '' | nil | nil + true | '' | 42_000_000 | nil + true | nil | nil | nil + true | nil | 42_000_000 | nil + true | 'foo' | nil | nil + true | 'foo' | 42_000_000 | nil + true | 'group_a' | nil | 42_000_000 + true | 'group_b' | 42_000_000 | 43_000_000 + true | 'group_a' | 42_000_000 | 42_000_000 + false | '' | nil | nil + false | '' | 42_000_000 | nil + false | nil | nil | nil + false | nil | 42_000_000 | nil + false | 'foo' | nil | nil + false | 'foo' | 42_000_000 | nil + false | 'group_a' | nil | nil + false | 'group_b' | 42_000_000 | nil + false | 'group_a' | 42_000_000 | nil + end + + with_them do + let(:params) do + { + performance_bar_enabled: params_performance_bar_enabled, + performance_bar_allowed_group_path: params_performance_bar_allowed_group_path + } + end + + before do + if previous_performance_bar_allowed_group_id == 42_000_000 || params_performance_bar_allowed_group_path == 'group_a' + create(:group, id: 42_000_000, path: 'group_a') + end + + if expected_performance_bar_allowed_group_id == 43_000_000 || params_performance_bar_allowed_group_path == 'group_b' + create(:group, id: 43_000_000, path: 'group_b') + end + + application_settings.update!(performance_bar_allowed_group_id: previous_performance_bar_allowed_group_id) + end + + it 'sets performance_bar_allowed_group_id when present and performance_bar_enabled == true' do + expect(application_settings.performance_bar_allowed_group_id).to eq(previous_performance_bar_allowed_group_id) + + if previous_performance_bar_allowed_group_id != expected_performance_bar_allowed_group_id + expect { subject.execute } + .to change(application_settings, :performance_bar_allowed_group_id) + .from(previous_performance_bar_allowed_group_id).to(expected_performance_bar_allowed_group_id) + else + expect { subject.execute } + .not_to change(application_settings, :performance_bar_allowed_group_id) + end + end + end + + context 'when :performance_bar_allowed_group_path is not present' do + let(:group) { create(:group) } + + before do + application_settings.update!(performance_bar_allowed_group_id: group.id) + end + + it 'does not change the performance bar settings' do + expect { subject.execute } + .not_to change(application_settings, :performance_bar_allowed_group_id) + end + end + + context 'when :performance_bar_enabled is not present' do + let(:group) { create(:group) } + let(:params) { { performance_bar_allowed_group_path: group.full_path } } + + it 'implicitely defaults to true' do + expect { subject.execute } + .to change(application_settings, :performance_bar_allowed_group_id) + .from(nil).to(group.id) + end + end + end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index e8568bf8bb3..dc30a9bccc1 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -249,24 +249,58 @@ describe MergeRequests::MergeService do expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) end - context "when fast-forward merge is not allowed" do + context 'when squashing' do before do - allow_any_instance_of(Repository).to receive(:ancestor?).and_return(nil) + merge_request.update!(source_branch: 'master', target_branch: 'feature') end - %w(semi-linear ff).each do |merge_method| - it "logs and saves error if merge is #{merge_method} only" do - merge_method = 'rebase_merge' if merge_method == 'semi-linear' - merge_request.project.update(merge_method: merge_method) - error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch' - allow(service).to receive(:execute_hooks) + it 'logs and saves error if there is an error when squashing' do + error_message = 'Failed to squash. Should be done manually' - service.execute(merge_request) + allow_any_instance_of(MergeRequests::SquashService).to receive(:squash).and_return(nil) + merge_request.update(squash: true) + + service.execute(merge_request) + + expect(merge_request).to be_open + expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.merge_error).to include(error_message) + expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) + end + + it 'logs and saves error if there is a squash in progress' do + error_message = 'another squash is already in progress' + + allow_any_instance_of(MergeRequest).to receive(:squash_in_progress?).and_return(true) + merge_request.update(squash: true) + + service.execute(merge_request) - expect(merge_request).to be_open - expect(merge_request.merge_commit_sha).to be_nil - expect(merge_request.merge_error).to include(error_message) - expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) + expect(merge_request).to be_open + expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.merge_error).to include(error_message) + expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) + end + + context "when fast-forward merge is not allowed" do + before do + allow_any_instance_of(Repository).to receive(:ancestor?).and_return(nil) + end + + %w(semi-linear ff).each do |merge_method| + it "logs and saves error if merge is #{merge_method} only" do + merge_method = 'rebase_merge' if merge_method == 'semi-linear' + merge_request.project.update(merge_method: merge_method) + error_message = 'Only fast-forward merge is allowed for your project. Please update your source branch' + allow(service).to receive(:execute_hooks) + + service.execute(merge_request) + + expect(merge_request).to be_open + expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.merge_error).to include(error_message) + expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message)) + end end end end diff --git a/spec/services/merge_requests/squash_service_spec.rb b/spec/services/merge_requests/squash_service_spec.rb new file mode 100644 index 00000000000..bd884787425 --- /dev/null +++ b/spec/services/merge_requests/squash_service_spec.rb @@ -0,0 +1,199 @@ +require 'spec_helper' + +describe MergeRequests::SquashService do + let(:service) { described_class.new(project, user, {}) } + let(:user) { project.owner } + let(:project) { create(:project, :repository) } + let(:repository) { project.repository.raw } + let(:log_error) { "Failed to squash merge request #{merge_request.to_reference(full: true)}:" } + let(:squash_dir_path) do + File.join(Gitlab.config.shared.path, 'tmp/squash', repository.gl_repository, merge_request.id.to_s) + end + let(:merge_request_with_one_commit) do + create(:merge_request, + source_branch: 'feature', source_project: project, + target_branch: 'master', target_project: project) + end + + let(:merge_request_with_only_new_files) do + create(:merge_request, + source_branch: 'video', source_project: project, + target_branch: 'master', target_project: project) + end + + let(:merge_request_with_large_files) do + create(:merge_request, + source_branch: 'squash-large-files', source_project: project, + target_branch: 'master', target_project: project) + end + + shared_examples 'the squash succeeds' do + it 'returns the squashed commit SHA' do + result = service.execute(merge_request) + + expect(result).to match(status: :success, squash_sha: a_string_matching(/\h{40}/)) + expect(result[:squash_sha]).not_to eq(merge_request.diff_head_sha) + end + + it 'cleans up the temporary directory' do + service.execute(merge_request) + + expect(File.exist?(squash_dir_path)).to be(false) + end + + it 'does not keep the branch push event' do + expect { service.execute(merge_request) }.not_to change { Event.count } + end + + context 'the squashed commit' do + let(:squash_sha) { service.execute(merge_request)[:squash_sha] } + let(:squash_commit) { project.repository.commit(squash_sha) } + + it 'copies the author info and message from the merge request' do + expect(squash_commit.author_name).to eq(merge_request.author.name) + expect(squash_commit.author_email).to eq(merge_request.author.email) + + # Commit messages have a trailing newline, but titles don't. + expect(squash_commit.message.chomp).to eq(merge_request.title) + end + + it 'sets the current user as the committer' do + expect(squash_commit.committer_name).to eq(user.name.chomp('.')) + expect(squash_commit.committer_email).to eq(user.email) + end + + it 'has the same diff as the merge request, but a different SHA' do + rugged = project.repository.rugged + mr_diff = rugged.diff(merge_request.diff_base_sha, merge_request.diff_head_sha) + squash_diff = rugged.diff(merge_request.diff_start_sha, squash_sha) + + expect(squash_diff.patch.length).to eq(mr_diff.patch.length) + expect(squash_commit.sha).not_to eq(merge_request.diff_head_sha) + end + end + end + + describe '#execute' do + context 'when there is only one commit in the merge request' do + it 'returns that commit SHA' do + result = service.execute(merge_request_with_one_commit) + + expect(result).to match(status: :success, squash_sha: merge_request_with_one_commit.diff_head_sha) + end + + it 'does not perform any git actions' do + expect(repository).not_to receive(:popen) + + service.execute(merge_request_with_one_commit) + end + end + + context 'when squashing only new files' do + let(:merge_request) { merge_request_with_only_new_files } + + include_examples 'the squash succeeds' + end + + context 'when squashing with files too large to display' do + let(:merge_request) { merge_request_with_large_files } + + include_examples 'the squash succeeds' + end + + context 'git errors' do + let(:merge_request) { merge_request_with_only_new_files } + let(:error) { 'A test error' } + + context 'with gitaly enabled' do + before do + allow(repository.gitaly_operation_client).to receive(:user_squash) + .and_raise(Gitlab::Git::Repository::GitError, error) + end + + it 'logs the stage and output' do + expect(service).to receive(:log_error).with(log_error) + expect(service).to receive(:log_error).with(error) + + service.execute(merge_request) + end + + it 'returns an error' do + expect(service.execute(merge_request)).to match(status: :error, + message: a_string_including('squash')) + end + end + + context 'with Gitaly disabled', :skip_gitaly_mock do + stages = { + 'add worktree for squash' => 'worktree', + 'configure sparse checkout' => 'config', + 'get files in diff' => 'diff --name-only', + 'check out target branch' => 'checkout', + 'apply patch' => 'diff --binary', + 'commit squashed changes' => 'commit', + 'get SHA of squashed commit' => 'rev-parse' + } + + stages.each do |stage, command| + context "when the #{stage} stage fails" do + before do + git_command = a_collection_containing_exactly( + a_string_starting_with("#{Gitlab.config.git.bin_path} #{command}") + ).or( + a_collection_starting_with([Gitlab.config.git.bin_path] + command.split) + ) + + allow(repository).to receive(:popen).and_return(['', 0]) + allow(repository).to receive(:popen).with(git_command, anything, anything, anything).and_return([error, 1]) + end + + it 'logs the stage and output' do + expect(service).to receive(:log_error).with(log_error) + expect(service).to receive(:log_error).with(error) + + service.execute(merge_request) + end + + it 'returns an error' do + expect(service.execute(merge_request)).to match(status: :error, + message: a_string_including('squash')) + end + + it 'cleans up the temporary directory' do + expect(File.exist?(squash_dir_path)).to be(false) + + service.execute(merge_request) + end + end + end + end + end + + context 'when any other exception is thrown' do + let(:merge_request) { merge_request_with_only_new_files } + let(:error) { 'A test error' } + + before do + allow(merge_request).to receive(:commits_count).and_raise(error) + end + + it 'logs the MR reference and exception' do + expect(service).to receive(:log_error).with(a_string_including("#{project.full_path}#{merge_request.to_reference}")) + expect(service).to receive(:log_error).with(error) + + service.execute(merge_request) + end + + it 'returns an error' do + expect(service.execute(merge_request)).to match(status: :error, + message: a_string_including('squash')) + end + + it 'cleans up the temporary directory' do + service.execute(merge_request) + + expect(File.exist?(squash_dir_path)).to be(false) + end + end + end +end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index f5cff66de6d..2b2b983494f 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -57,6 +57,88 @@ describe Notes::CreateService do end end + context 'note diff file' do + let(:project_with_repo) { create(:project, :repository) } + let(:merge_request) do + create(:merge_request, + source_project: project_with_repo, + target_project: project_with_repo) + end + let(:line_number) { 14 } + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: line_number, + diff_refs: merge_request.diff_refs) + end + let(:previous_note) do + create(:diff_note_on_merge_request, noteable: merge_request, project: project_with_repo) + end + + context 'when eligible to have a note diff file' do + let(:new_opts) do + opts.merge(in_reply_to_discussion_id: nil, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: position.to_h) + end + + it 'note is associated with a note diff file' do + note = described_class.new(project_with_repo, user, new_opts).execute + + expect(note).to be_persisted + expect(note.note_diff_file).to be_present + end + end + + context 'when DiffNote is a reply' do + let(:new_opts) do + opts.merge(in_reply_to_discussion_id: previous_note.discussion_id, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: position.to_h) + end + + it 'note is not associated with a note diff file' do + note = described_class.new(project_with_repo, user, new_opts).execute + + expect(note).to be_persisted + expect(note.note_diff_file).to be_nil + end + + context 'when DiffNote from an image' do + let(:image_position) do + Gitlab::Diff::Position.new(old_path: "files/images/6049019_460s.jpg", + new_path: "files/images/6049019_460s.jpg", + width: 100, + height: 100, + x: 1, + y: 100, + diff_refs: merge_request.diff_refs, + position_type: 'image') + end + + let(:new_opts) do + opts.merge(in_reply_to_discussion_id: nil, + type: 'DiffNote', + noteable_type: 'MergeRequest', + noteable_id: merge_request.id, + position: image_position.to_h) + end + + it 'note is not associated with a note diff file' do + note = described_class.new(project_with_repo, user, new_opts).execute + + expect(note).to be_persisted + expect(note.note_diff_file).to be_nil + end + end + end + end + context 'note with commands' do context 'as a user who can update the target' do context '/close, /label, /assign & /milestone' do diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 57aa07cf4fa..1fef50a52ec 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -47,6 +47,7 @@ module TestEnv 'v1.1.0' => 'b83d6e3', 'add-ipython-files' => '93ee732', 'add-pdf-file' => 'e774ebd', + 'squash-large-files' => '54cec52', 'add-pdf-text-binary' => '79faa7b', 'add_images_and_changes' => '010d106' }.freeze diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb index f59792c3d36..35e451b2f9a 100644 --- a/spec/tasks/gitlab/storage_rake_spec.rb +++ b/spec/tasks/gitlab/storage_rake_spec.rb @@ -1,13 +1,49 @@ require 'rake_helper' -describe 'gitlab:storage rake tasks' do +describe 'gitlab:storage:*' do before do Rake.application.rake_require 'tasks/gitlab/storage' stub_warn_user_is_not_gitlab end - describe 'migrate_to_hashed rake task' do + shared_examples "rake listing entities" do |entity_name, storage_type| + context 'limiting to 2' do + before do + stub_env('LIMIT' => 2) + end + + it "lists 2 out of 3 #{storage_type.downcase} #{entity_name}" do + create_collection + + expect { run_rake_task(task) }.to output(/Found 3 #{entity_name} using #{storage_type} Storage.*Displaying first 2 #{entity_name}/m).to_stdout + end + end + + context "without any #{storage_type.downcase} #{entity_name.singularize}" do + it 'displays message for empty results' do + expect { run_rake_task(task) }.to output(/Found 0 #{entity_name} using #{storage_type} Storage/).to_stdout + end + end + end + + shared_examples "rake entities summary" do |entity_name, storage_type| + context "with existing 3 #{storage_type.downcase} #{entity_name}" do + it "reports 3 #{storage_type.downcase} #{entity_name}" do + create_collection + + expect { run_rake_task(task) }.to output(/Found 3 #{entity_name} using #{storage_type} Storage/).to_stdout + end + end + + context "without any #{storage_type.downcase} #{entity_name.singularize}" do + it 'displays message for empty results' do + expect { run_rake_task(task) }.to output(/Found 0 #{entity_name} using #{storage_type} Storage/).to_stdout + end + end + end + + describe 'gitlab:storage:migrate_to_hashed' do context '0 legacy projects' do it 'does nothing' do expect(StorageMigratorWorker).not_to receive(:perform_async) @@ -16,8 +52,8 @@ describe 'gitlab:storage rake tasks' do end end - context '5 legacy projects' do - let(:projects) { create_list(:project, 5, storage_version: 0) } + context '3 legacy projects' do + let(:projects) { create_list(:project, 3, storage_version: 0) } context 'in batches of 1' do before do @@ -49,4 +85,64 @@ describe 'gitlab:storage rake tasks' do end end end + + describe 'gitlab:storage:legacy_projects' do + it_behaves_like 'rake entities summary', 'projects', 'Legacy' do + let(:task) { 'gitlab:storage:legacy_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 0) } + end + end + + describe 'gitlab:storage:list_legacy_projects' do + it_behaves_like 'rake listing entities', 'projects', 'Legacy' do + let(:task) { 'gitlab:storage:list_legacy_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 0) } + end + end + + describe 'gitlab:storage:hashed_projects' do + it_behaves_like 'rake entities summary', 'projects', 'Hashed' do + let(:task) { 'gitlab:storage:hashed_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 1) } + end + end + + describe 'gitlab:storage:list_hashed_projects' do + it_behaves_like 'rake listing entities', 'projects', 'Hashed' do + let(:task) { 'gitlab:storage:list_hashed_projects' } + let(:create_collection) { create_list(:project, 3, storage_version: 1) } + end + end + + describe 'gitlab:storage:legacy_attachments' do + it_behaves_like 'rake entities summary', 'attachments', 'Legacy' do + let(:task) { 'gitlab:storage:legacy_attachments' } + let(:project) { create(:project, storage_version: 1) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end + + describe 'gitlab:storage:list_legacy_attachments' do + it_behaves_like 'rake listing entities', 'attachments', 'Legacy' do + let(:task) { 'gitlab:storage:list_legacy_attachments' } + let(:project) { create(:project, storage_version: 1) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end + + describe 'gitlab:storage:hashed_attachments' do + it_behaves_like 'rake entities summary', 'attachments', 'Hashed' do + let(:task) { 'gitlab:storage:hashed_attachments' } + let(:project) { create(:project, storage_version: 2) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end + + describe 'gitlab:storage:list_hashed_attachments' do + it_behaves_like 'rake listing entities', 'attachments', 'Hashed' do + let(:task) { 'gitlab:storage:list_hashed_attachments' } + let(:project) { create(:project, storage_version: 2) } + let(:create_collection) { create_list(:upload, 3, model: project) } + end + end end diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index e7277b337f6..2dd0925a8e6 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -382,6 +382,8 @@ describe ObjectStorage do is_expected.to have_key(:RemoteObject) expect(subject[:RemoteObject]).to have_key(:ID) + expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer)) + expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DIRECT_UPLOAD_TIMEOUT) expect(subject[:RemoteObject]).to have_key(:GetURL) expect(subject[:RemoteObject]).to have_key(:DeleteURL) expect(subject[:RemoteObject]).to have_key(:StoreURL) @@ -618,7 +620,7 @@ describe ObjectStorage do let!(:fog_file) do fog_connection.directories.get('uploads').files.create( - key: 'tmp/upload/test/123123', + key: 'tmp/uploads/test/123123', body: 'content' ) end diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb index 59c777ea338..0e8b7c82d3a 100644 --- a/spec/views/admin/dashboard/index.html.haml_spec.rb +++ b/spec/views/admin/dashboard/index.html.haml_spec.rb @@ -4,6 +4,11 @@ describe 'admin/dashboard/index.html.haml' do include Devise::Test::ControllerHelpers before do + counts = Admin::DashboardController::COUNTED_ITEMS.each_with_object({}) do |item, hash| + hash[item] = 100 + end + + assign(:counts, counts) assign(:projects, create_list(:project, 1)) assign(:users, create_list(:user, 1)) assign(:groups, create_list(:group, 1)) diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb new file mode 100644 index 00000000000..0ac946a1232 --- /dev/null +++ b/spec/workers/create_note_diff_file_worker_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe CreateNoteDiffFileWorker do + describe '#perform' do + let(:diff_note) { create(:diff_note_on_merge_request) } + + it 'creates diff file' do + diff_note.note_diff_file.destroy! + + expect_any_instance_of(DiffNote).to receive(:create_diff_file) + .and_call_original + + described_class.new.perform(diff_note.id) + end + end +end diff --git a/yarn.lock b/yarn.lock index af8785bbc66..76d59b8ff52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,53 +2,77 @@ # yarn lockfile v1 -"@babel/code-frame@7.0.0-beta.32", "@babel/code-frame@^7.0.0-beta.31": - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.32.tgz#04f231b8ec70370df830d9926ce0f5add074ec4c" +"@babel/code-frame@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^3.0.0" + "@babel/highlight" "7.0.0-beta.44" -"@babel/helper-function-name@7.0.0-beta.32": - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.32.tgz#6161af4419f1b4e3ed2d28c0c79c160e218be1f3" +"@babel/generator@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" dependencies: - "@babel/helper-get-function-arity" "7.0.0-beta.32" - "@babel/template" "7.0.0-beta.32" - "@babel/types" "7.0.0-beta.32" + "@babel/types" "7.0.0-beta.44" + jsesc "^2.5.1" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" -"@babel/helper-get-function-arity@7.0.0-beta.32": - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.32.tgz#93721a99db3757de575a83bab7c453299abca568" +"@babel/helper-function-name@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" dependencies: - "@babel/types" "7.0.0-beta.32" + "@babel/helper-get-function-arity" "7.0.0-beta.44" + "@babel/template" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" -"@babel/template@7.0.0-beta.32": - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.32.tgz#e1d9fdbd2a7bcf128f2f920744a67dab18072495" +"@babel/helper-get-function-arity@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" dependencies: - "@babel/code-frame" "7.0.0-beta.32" - "@babel/types" "7.0.0-beta.32" - babylon "7.0.0-beta.32" - lodash "^4.2.0" + "@babel/types" "7.0.0-beta.44" -"@babel/traverse@^7.0.0-beta.31": - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.32.tgz#b78b754c6e1af3360626183738e4c7a05951bc99" +"@babel/helper-split-export-declaration@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" dependencies: - "@babel/code-frame" "7.0.0-beta.32" - "@babel/helper-function-name" "7.0.0-beta.32" - "@babel/types" "7.0.0-beta.32" - babylon "7.0.0-beta.32" - debug "^3.0.1" - globals "^10.0.0" + "@babel/types" "7.0.0-beta.44" + +"@babel/highlight@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +"@babel/template@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + lodash "^4.2.0" + +"@babel/traverse@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/generator" "7.0.0-beta.44" + "@babel/helper-function-name" "7.0.0-beta.44" + "@babel/helper-split-export-declaration" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + debug "^3.1.0" + globals "^11.1.0" invariant "^2.2.0" lodash "^4.2.0" -"@babel/types@7.0.0-beta.32", "@babel/types@^7.0.0-beta.31": - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.32.tgz#c317d0ecc89297b80bbcb2f50608e31f6452a5ff" +"@babel/types@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" dependencies: esutils "^2.0.2" lodash "^4.2.0" @@ -118,9 +142,9 @@ acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" -acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" +acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0: + version "5.5.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" addressparser@1.0.1: version "1.0.1" @@ -137,22 +161,22 @@ agent-base@2: extend "~3.0.0" semver "~5.0.1" -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" ajv-keywords@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be" -ajv@^4.7.0, ajv@^4.9.1: +ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.1.0: +ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -201,7 +225,7 @@ ansi-align@^2.0.0: dependencies: string-width "^2.0.0" -ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: +ansi-escapes@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" @@ -385,14 +409,10 @@ ast-types@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.10.1.tgz#f52fca9715579a14f841d67d7f8d25432ab6a3dd" -ast-types@0.11.3: +ast-types@0.11.3, ast-types@0.x.x: version "0.11.3" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.3.tgz#c20757fe72ee71278ea0ff3d87e5c2ca30d9edf8" -ast-types@0.x.x: - version "0.11.1" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.1.tgz#5bb3a8d5ba292c3f4ae94d46df37afc30300b990" - async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -405,18 +425,12 @@ async@1.x, async@^1.4.0, async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.0.0, async@^2.6.0: +async@^2.0.0, async@^2.1.4, async@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" dependencies: lodash "^4.14.0" -async@^2.1.4: - version "2.4.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" - dependencies: - lodash "^4.14.0" - async@~2.1.2: version "2.1.5" resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" @@ -477,7 +491,7 @@ axios@^0.17.1: follow-redirects "^1.2.5" is-buffer "^1.1.5" -babel-code-frame@^6.16.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -509,14 +523,16 @@ babel-core@^6.26.0, babel-core@^6.26.3: slash "^1.0.0" source-map "^0.5.7" -babel-eslint@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.0.2.tgz#e44fb9a037d749486071d52d65312f5c20aa7530" +babel-eslint@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.3.tgz#1a2e6681cc9bc4473c32899e59915e19cd6733cf" dependencies: - "@babel/code-frame" "^7.0.0-beta.31" - "@babel/traverse" "^7.0.0-beta.31" - "@babel/types" "^7.0.0-beta.31" - babylon "^7.0.0-beta.31" + "@babel/code-frame" "7.0.0-beta.44" + "@babel/traverse" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + eslint-scope "~3.7.1" + eslint-visitor-keys "^1.0.0" babel-generator@^6.18.0, babel-generator@^6.26.0: version "6.26.0" @@ -1115,18 +1131,14 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26 lodash "^4.17.4" to-fast-properties "^1.0.3" -babylon@7.0.0-beta.32, babylon@^7.0.0-beta.31: - version "7.0.0-beta.32" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.32.tgz#e9033cb077f64d6895f4125968b37dc0a8c3bc6e" +babylon@7.0.0-beta.44, babylon@^7.0.0-beta.30: + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" babylon@^6.17.3, babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" -babylon@^7.0.0-beta.30: - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" - backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -1284,9 +1296,9 @@ boom@5.x.x: dependencies: hoek "4.x.x" -bootstrap@4.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.0.tgz#110b05c31a236d56dbc9adcda6dd16f53738a28a" +bootstrap@~4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.1.tgz#3aec85000fa619085da8d2e4983dfd67cf2114cb" boxen@^1.2.1: version "1.3.0" @@ -1388,11 +1400,11 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" dependencies: - pako "~0.2.0" + pako "~1.0.5" browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: version "1.7.7" @@ -1401,6 +1413,10 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" +buffer-from@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + buffer-indexof@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982" @@ -1433,7 +1449,7 @@ buildmail@4.0.1: nodemailer-shared "1.1.0" punycode "1.4.1" -builtin-modules@^1.0.0, builtin-modules@^1.1.1: +builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1581,7 +1597,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -1589,14 +1605,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4 escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" @@ -1700,7 +1708,7 @@ cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" -cli-cursor@^1.0.1, cli-cursor@^1.0.2: +cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" dependencies: @@ -1775,14 +1783,10 @@ clone-stats@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" -clone@^1.0.0: +clone@^1.0.0, clone@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" -clone@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" - clone@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" @@ -1928,10 +1932,11 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.0, concat-stream@^1.5.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" +concat-stream@^1.5.0, concat-stream@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" @@ -2083,7 +2088,7 @@ cropper@^2.3.0: dependencies: jquery ">= 1.9.1" -cross-spawn@^5.0.1: +cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" dependencies: @@ -2336,18 +2341,6 @@ d3@3.5.17: version "3.5.17" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.17.tgz#bc46748004378b21a360c9fc7cf5231790762fb8" -d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - dependencies: - es5-ext "^0.10.9" - -d@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" - dependencies: - es5-ext "~0.10.2" - dagre-d3-renderer@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/dagre-d3-renderer/-/dagre-d3-renderer-0.4.24.tgz#b36ce2fe4ea20de43e7698627c6ede2a9f15ec45" @@ -2398,18 +2391,12 @@ de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" -debug@2, debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6: +debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: ms "2.0.0" -debug@2.2.0, debug@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - debug@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" @@ -2422,6 +2409,12 @@ debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" +debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2572,11 +2565,7 @@ di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" -diff@^3.3.1, diff@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" - -diff@^3.5.0: +diff@^3.3.1, diff@^3.4.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -2619,12 +2608,11 @@ doctrine@1.5.0: esutils "^2.0.2" isarray "^1.0.0" -doctrine@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" +doctrine@^2.0.2: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" dependencies: esutils "^2.0.2" - isarray "^1.0.0" document-register-element@1.3.0: version "1.3.0" @@ -2716,11 +2704,7 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -ejs@^2.5.7: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" - -ejs@^2.5.9: +ejs@^2.5.7, ejs@^2.5.9: version "2.5.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5" @@ -2878,62 +2862,10 @@ es-to-primitive@^1.1.1: is-date-object "^1.0.1" is-symbol "^1.0.1" -es5-ext@^0.10.14, es5-ext@^0.10.8, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: - version "0.10.24" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14" - dependencies: - es6-iterator "2" - es6-symbol "~3.1" - -es6-iterator@2, es6-iterator@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" - dependencies: - d "1" - es5-ext "^0.10.14" - es6-symbol "^3.1" - -es6-map@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - es6-promise@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3, es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@~3.1, es6-symbol@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - dependencies: - d "1" - es5-ext "~0.10.14" - -es6-weak-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.1.tgz#0d2bbd8827eb5fb4ba8f97fbfea50d43db21ea81" - dependencies: - d "^0.1.1" - es5-ext "^0.10.8" - es6-iterator "2" - es6-symbol "3" - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -2964,86 +2896,78 @@ escodegen@1.x.x: optionalDependencies: source-map "~0.5.6" -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" +eslint-config-airbnb-base@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944" dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" + eslint-restricted-globals "^0.1.1" -eslint-config-airbnb-base@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-10.0.1.tgz#f17d4e52992c1d45d1b7713efbcd5ecd0e7e0506" - -eslint-import-resolver-node@^0.2.0: - version "0.2.3" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c" +eslint-import-resolver-node@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" dependencies: - debug "^2.2.0" - object-assign "^4.0.1" - resolve "^1.1.6" + debug "^2.6.9" + resolve "^1.5.0" -eslint-import-resolver-webpack@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.3.tgz#ad61e28df378a474459d953f246fd43f92675385" +eslint-import-resolver-webpack@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.10.0.tgz#b6f2468dc3e8b4ea076e5d75bece8da932789b07" dependencies: array-find "^1.0.0" debug "^2.6.8" enhanced-resolve "~0.9.0" - find-root "^0.1.1" + find-root "^1.1.0" has "^1.0.1" interpret "^1.0.0" - is-absolute "^0.2.3" - lodash.get "^3.7.0" - node-libs-browser "^1.0.0" - resolve "^1.2.0" + lodash "^4.17.4" + node-libs-browser "^1.0.0 || ^2.0.0" + resolve "^1.4.0" semver "^5.3.0" -eslint-module-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz#a6f8c21d901358759cdc35dbac1982ae1ee58bce" +eslint-module-utils@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" dependencies: - debug "2.2.0" + debug "^2.6.8" pkg-dir "^1.0.0" -eslint-plugin-filenames@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-filenames/-/eslint-plugin-filenames-1.1.0.tgz#bb925218ab25b1aad1c622cfa9cb8f43cc03a4ff" +eslint-plugin-filenames@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-filenames/-/eslint-plugin-filenames-1.2.0.tgz#aee9c1c90189c95d2e49902c160eceefecd99f53" dependencies: - lodash.camelcase "4.1.1" - lodash.kebabcase "4.0.1" - lodash.snakecase "4.0.1" + lodash.camelcase "4.3.0" + lodash.kebabcase "4.1.1" + lodash.snakecase "4.1.1" + lodash.upperfirst "4.3.1" -eslint-plugin-html@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-2.0.1.tgz#3a829510e82522f1e2e44d55d7661a176121fce1" +eslint-plugin-html@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-4.0.3.tgz#97d52dcf9e22724505d02719fbd02754013c8a17" dependencies: htmlparser2 "^3.8.2" -eslint-plugin-import@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e" +eslint-plugin-import@^2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz#dad31781292d6664b25317fd049d2e2b2f02205d" dependencies: - builtin-modules "^1.1.1" contains-path "^0.1.0" - debug "^2.2.0" + debug "^2.6.8" doctrine "1.5.0" - eslint-import-resolver-node "^0.2.0" - eslint-module-utils "^2.0.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.2.0" has "^1.0.1" - lodash.cond "^4.3.0" + lodash "^4.17.4" minimatch "^3.0.3" - pkg-up "^1.0.0" + read-pkg-up "^2.0.0" + resolve "^1.6.0" eslint-plugin-jasmine@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-2.2.0.tgz#7135879383c39a667c721d302b9f20f0389543de" -eslint-plugin-promise@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca" +eslint-plugin-promise@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz#65ebf27a845e3c1e9d6f6a5622ddd3801694b621" eslint-plugin-vue@^4.0.1: version "4.0.1" @@ -3052,7 +2976,11 @@ eslint-plugin-vue@^4.0.1: require-all "^2.2.0" vue-eslint-parser "^2.0.1" -eslint-scope@^3.7.1: +eslint-restricted-globals@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" + +eslint-scope@^3.7.1, eslint-scope@~3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" dependencies: @@ -3063,51 +2991,53 @@ eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" -eslint@^3.18.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" +eslint@~4.12.1: + version "4.12.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.12.1.tgz#5ec1973822b4a066b353770c3c6d69a2a188e880" dependencies: - babel-code-frame "^6.16.0" - chalk "^1.1.3" - concat-stream "^1.5.2" - debug "^2.1.1" - doctrine "^2.0.0" - escope "^3.6.0" - espree "^3.4.0" + ajv "^5.3.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.0.1" + doctrine "^2.0.2" + eslint-scope "^3.7.1" + espree "^3.5.2" esquery "^1.0.0" estraverse "^4.2.0" esutils "^2.0.2" file-entry-cache "^2.0.0" - glob "^7.0.3" - globals "^9.14.0" - ignore "^3.2.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.0.1" + ignore "^3.3.3" imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" + inquirer "^3.0.6" is-resolvable "^1.0.0" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" natural-compare "^1.4.0" optionator "^0.8.2" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.7.5" - strip-bom "^3.0.0" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" strip-json-comments "~2.0.1" - table "^3.7.8" + table "^4.0.1" text-table "~0.2.0" - user-home "^2.0.0" -espree@^3.4.0, espree@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" +espree@^3.5.2: + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" dependencies: - acorn "^5.2.1" + acorn "^5.5.0" acorn-jsx "^3.0.0" esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1: @@ -3159,13 +3089,6 @@ eve-raphael@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30" -event-emitter@~0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - dependencies: - d "1" - es5-ext "~0.10.14" - event-stream@~3.3.0: version "3.3.4" resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" @@ -3319,15 +3242,7 @@ extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - -external-editor@^2.1.0: +external-editor@^2.0.4, external-editor@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" dependencies: @@ -3400,7 +3315,7 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" -figures@^1.3.5, figures@^1.7.0: +figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" dependencies: @@ -3485,9 +3400,9 @@ find-cache-dir@^1.0.0: make-dir "^1.0.0" pkg-dir "^2.0.0" -find-root@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-0.1.2.tgz#98d2267cff1916ccaf2743b3a0eea81d79d7dcd1" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" find-up@^1.0.0: version "1.1.2" @@ -3665,6 +3580,10 @@ function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + fuzzaldrin-plus@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/fuzzaldrin-plus/-/fuzzaldrin-plus-0.5.0.tgz#ef5f26f0c2fc7e9e9a16ea149a802d6cb4804b1e" @@ -3779,18 +3698,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -3825,11 +3733,11 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -globals@^10.0.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-10.4.0.tgz#5c477388b128a9e4c5c5d01c7a2aca68c68b2da7" +globals@^11.0.1, globals@^11.1.0: + version "11.5.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.5.0.tgz#6bc840de6771173b191f13d3a9c94d441ee92642" -globals@^9.14.0, globals@^9.18.0: +globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -3865,7 +3773,7 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -globby@^8.0.0: +globby@^8.0.0, globby@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50" dependencies: @@ -4032,10 +3940,6 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -4269,9 +4173,9 @@ httpreq@>=0.4.22: version "0.4.24" resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-0.4.24.tgz#4335ffd82cd969668a39465c929ac61d6393627f" -https-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" https-proxy-agent@1: version "1.0.0" @@ -4311,9 +4215,9 @@ ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" -ignore@^3.2.0, ignore@^3.3.5, ignore@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" +ignore@^3.3.3, ignore@^3.3.5, ignore@^3.3.7: + version "3.3.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b" immediate@~3.0.5: version "3.0.6" @@ -4386,25 +4290,7 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inquirer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" - dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - figures "^1.3.5" - lodash "^4.3.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - -inquirer@^3.3.0: +inquirer@^3.0.6: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" dependencies: @@ -4423,7 +4309,7 @@ inquirer@^3.3.0: strip-ansi "^4.0.0" through "^2.3.6" -inquirer@^5.1.0: +inquirer@^5.1.0, inquirer@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726" dependencies: @@ -4447,11 +4333,7 @@ internal-ip@1.2.0: dependencies: meow "^3.3.0" -interpret@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" - -interpret@^1.0.4: +interpret@^1.0.0, interpret@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -4488,13 +4370,6 @@ is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" -is-absolute@^0.2.3: - version "0.2.6" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.2.6.tgz#20de69f3db942ef2d87b9c2da36f172235b1b5eb" - dependencies: - is-relative "^0.2.1" - is-windows "^0.2.0" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -4636,7 +4511,7 @@ is-my-ip-valid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" -is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: +is-my-json-valid@^2.12.4: version "2.17.2" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" dependencies: @@ -4742,12 +4617,6 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" -is-relative@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.2.1.tgz#d27f4c7d516d175fb610db84bbeef23c3bc97aa5" - dependencies: - is-unc-path "^0.1.1" - is-resolvable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" @@ -4782,20 +4651,10 @@ is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" -is-unc-path@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-0.1.2.tgz#6ab053a72573c10250ff416a3814c35178af39b9" - dependencies: - unc-path-regex "^0.1.0" - is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -is-windows@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" - is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -4868,7 +4727,7 @@ istanbul-lib-hook@^1.1.0: dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.10.1: +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.9.1: version "1.10.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" dependencies: @@ -4880,18 +4739,6 @@ istanbul-lib-instrument@^1.10.1: istanbul-lib-coverage "^1.2.0" semver "^5.3.0" -istanbul-lib-instrument@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" - dependencies: - babel-generator "^6.18.0" - babel-template "^6.16.0" - babel-traverse "^6.18.0" - babel-types "^6.18.0" - babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" - semver "^5.3.0" - istanbul-lib-report@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" @@ -4989,9 +4836,9 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@3.x, js-yaml@^3.5.1, js-yaml@^3.7.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" +js-yaml@3.x, js-yaml@^3.7.0, js-yaml@^3.9.1: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -5051,6 +4898,10 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" +jsesc@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" @@ -5071,7 +4922,11 @@ json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" -json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + +json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" dependencies: @@ -5340,6 +5195,15 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -5368,65 +5232,21 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash._baseget@^3.0.0: - version "3.7.2" - resolved "https://registry.yarnpkg.com/lodash._baseget/-/lodash._baseget-3.7.2.tgz#1b6ae1d5facf3c25532350a13c1197cb8bb674f4" - -lodash._topath@^3.0.0: - version "3.8.1" - resolved "https://registry.yarnpkg.com/lodash._topath/-/lodash._topath-3.8.1.tgz#3ec5e2606014f4cb97f755fe6914edd8bfc00eac" - dependencies: - lodash.isarray "^3.0.0" - -lodash.camelcase@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.1.1.tgz#065b3ff08f0b7662f389934c46a5504c90e0b2d8" - dependencies: - lodash.capitalize "^4.0.0" - lodash.deburr "^4.0.0" - lodash.words "^4.0.0" - -lodash.camelcase@^4.3.0: +lodash.camelcase@4.3.0, lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" -lodash.capitalize@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" - lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" -lodash.cond@^4.3.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" - -lodash.deburr@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b" - lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" -lodash.get@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f" - dependencies: - lodash._baseget "^3.0.0" - lodash._topath "^3.0.0" - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - -lodash.kebabcase@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.0.1.tgz#5e63bc9aa2a5562ff3b97ca7af2f803de1bcb90e" - dependencies: - lodash.deburr "^4.0.0" - lodash.words "^4.0.0" +lodash.kebabcase@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" lodash.memoize@^4.1.2: version "4.1.2" @@ -5436,30 +5256,23 @@ lodash.mergewith@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" -lodash.snakecase@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.0.1.tgz#bd012e5d2f93f7b58b9303e9a7fbfd5db13d6281" - dependencies: - lodash.deburr "^4.0.0" - lodash.words "^4.0.0" +lodash.snakecase@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash.words@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.words/-/lodash.words-4.2.0.tgz#5ecfeaf8ecf8acaa8e0c8386295f1993c9cf4036" +lodash.upperfirst@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" lodash@4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@^4.0.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - -lodash@^4.17.10: +lodash@^4.0.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -5469,13 +5282,7 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.1.0.tgz#f35fa60e278832b538dc4dddcbb478a45d3e3be6" - dependencies: - chalk "^2.0.1" - -log-symbols@^2.2.0: +log-symbols@^2.1.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" dependencies: @@ -5548,14 +5355,7 @@ lru-cache@2.2.x: version "2.2.4" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" -lru-cache@^4.0.1, lru-cache@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^4.1.2: +lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" dependencies: @@ -5591,13 +5391,7 @@ mailgun-js@^0.7.0: q "~1.4.0" tsscmp "~1.0.0" -make-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" - dependencies: - pify "^2.3.0" - -make-dir@^1.1.0: +make-dir@^1.0.0, make-dir@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b" dependencies: @@ -5736,7 +5530,7 @@ micromatch@^2.1.5, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.9: +micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: @@ -5754,42 +5548,6 @@ micromatch@^3.1.10, micromatch@^3.1.9: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^3.1.4: - version "3.1.6" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.6.tgz#8d7c043b48156f408ca07a4715182b79b99420bf" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -micromatch@^3.1.8: - version "3.1.9" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -5815,14 +5573,10 @@ mime@^1.3.4: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" -mime@^2.0.3: +mime@^2.0.3, mime@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" -mime@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.2.0.tgz#161e541965551d3b549fa1114391e3a3d55b923b" - mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -5940,10 +5694,6 @@ multimatch@^2.0.0: arrify "^1.0.0" minimatch "^3.0.0" -mute-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -5997,57 +5747,29 @@ node-forge@0.6.33: version "0.6.33" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" -node-libs-browser@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-1.1.1.tgz#2a38243abedd7dffcd07a97c9aca5668975a6fea" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.1.4" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^1.0.0" - https-browserify "0.0.1" - os-browserify "^0.2.0" - path-browserify "0.0.0" - process "^0.11.0" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.0.5" - stream-browserify "^2.0.1" - stream-http "^2.3.1" - string_decoder "^0.10.25" - timers-browserify "^1.4.2" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.10.3" - vm-browserify "0.0.4" - -node-libs-browser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646" +"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" dependencies: assert "^1.1.1" - browserify-zlib "^0.1.4" + browserify-zlib "^0.2.0" buffer "^4.3.0" console-browserify "^1.1.0" constants-browserify "^1.0.0" crypto-browserify "^3.11.0" domain-browser "^1.1.1" events "^1.0.0" - https-browserify "0.0.1" - os-browserify "^0.2.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" path-browserify "0.0.0" - process "^0.11.0" + process "^0.11.10" punycode "^1.2.4" querystring-es3 "^0.2.0" - readable-stream "^2.0.5" + readable-stream "^2.3.3" stream-browserify "^2.0.1" - stream-http "^2.3.1" - string_decoder "^0.10.25" - timers-browserify "^2.0.2" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" tty-browserify "0.0.0" url "^0.11.0" util "^0.10.3" @@ -6342,9 +6064,9 @@ original@>=0.0.5: dependencies: url-parse "1.0.x" -os-browserify@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" os-homedir@^1.0.0: version "1.0.2" @@ -6464,11 +6186,7 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" -pako@~0.2.0: - version "0.2.9" - resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - -pako@~1.0.2: +pako@~1.0.2, pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -6558,7 +6276,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-is-inside@^1.0.1: +path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" @@ -6588,6 +6306,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -6654,15 +6378,9 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" -pkg-up@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26" - dependencies: - find-up "^1.0.0" - -pluralize@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" popper.js@^1.14.3: version "1.14.3" @@ -6926,15 +6644,7 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.1, postcss@^6.0.14: - version "6.0.19" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.19.tgz#76a78386f670b9d9494a655bf23ac012effd1555" - dependencies: - chalk "^2.3.1" - source-map "^0.6.1" - supports-color "^5.2.0" - -postcss@^6.0.20: +postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20: version "6.0.22" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" dependencies: @@ -6962,14 +6672,10 @@ prettier@1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75" -prettier@^1.11.1: +prettier@^1.11.1, prettier@^1.5.3: version "1.12.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" -prettier@^1.5.3: - version "1.10.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.10.2.tgz#1af8356d1842276a99a5b5529c82dd9e9ad3cc93" - pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" @@ -6992,13 +6698,13 @@ process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" -process@^0.11.0, process@~0.11.0: +process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" promise-inflight@^1.0.1: version "1.0.1" @@ -7206,6 +6912,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -7221,6 +6934,14 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -7229,7 +6950,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3: +"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" dependencies: @@ -7250,6 +6971,18 @@ readable-stream@1.1.x, "readable-stream@1.x >=1.1.9": isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@~2.0.5, readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -7270,14 +7003,6 @@ readdirp@^2.0.0: readable-stream "^2.0.2" set-immediate-shim "^1.0.1" -readline2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" - recast@^0.12.5: version "0.12.9" resolved "https://registry.yarnpkg.com/recast/-/recast-0.12.9.tgz#e8e52bdb9691af462ccbd7c15d5a5113647a15f1" @@ -7539,7 +7264,7 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" -require-uncached@^1.0.2: +require-uncached@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" dependencies: @@ -7579,9 +7304,9 @@ resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.6, resolve@^1.2.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" +resolve@^1.1.6, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" dependencies: path-parse "^1.0.5" @@ -7615,18 +7340,12 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: glob "^7.0.5" -rimraf@^2.2.8: - version "2.6.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" - dependencies: - glob "^7.0.5" - rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -7638,12 +7357,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" -run-async@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" - dependencies: - once "^1.3.0" - run-async@^2.0.0, run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -7666,10 +7379,6 @@ rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" -rx-lite@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" - rxjs@^5.4.2, rxjs@^5.5.2: version "5.5.10" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045" @@ -7737,11 +7446,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - -semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -7860,14 +7565,6 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shelljs@^0.7.5: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shelljs@^0.8.0: version "0.8.1" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.1.tgz#729e038c413a2254c4078b95ed46e0397154a9f1" @@ -7894,6 +7591,12 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + slide@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -8075,14 +7778,14 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" +source-map@^0.5.0, source-map@^0.5.7, source-map@~0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" -source-map@^0.5.7, source-map@~0.5.3, source-map@~0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -8216,13 +7919,13 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-http@^2.3.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" +stream-http@^2.7.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.2.tgz#4126e8c6b107004465918aa2fc35549e77402c87" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" - readable-stream "^2.3.3" + readable-stream "^2.3.6" to-arraybuffer "^1.0.0" xtend "^4.0.0" @@ -8268,7 +7971,13 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^0.10.25, string_decoder@~0.10.x: +string_decoder@^1.0.0, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -8346,19 +8055,7 @@ supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -supports-color@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5" - dependencies: - has-flag "^2.0.0" - -supports-color@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" - dependencies: - has-flag "^3.0.0" - -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" dependencies: @@ -8388,16 +8085,16 @@ symbol-observable@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" +table@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" tapable@^0.1.8: version "0.1.10" @@ -8500,15 +8197,9 @@ timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" -timers-browserify@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" - dependencies: - process "~0.11.0" - -timers-browserify@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" +timers-browserify@^2.0.4: + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" dependencies: setimmediate "^1.0.4" @@ -8555,15 +8246,7 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" - dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^1.0.0" - -to-regex@^3.0.2: +to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" dependencies: @@ -8684,10 +8367,6 @@ ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" -unc-path-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - undefsafe@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" @@ -8849,12 +8528,6 @@ use@^2.0.0: isobject "^3.0.0" lazy-cache "^2.0.2" -user-home@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" - dependencies: - os-homedir "^1.0.0" - useragent@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e" @@ -8960,8 +8633,8 @@ void-elements@^2.0.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" vue-eslint-parser@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-2.0.1.tgz#30135771c4fad00fdbac4542a2d59f3b1d776834" + version "2.0.3" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz#c268c96c6d94cfe3d938a5f7593959b0ca3360d1" dependencies: debug "^3.1.0" eslint-scope "^3.7.1" @@ -9149,7 +8822,7 @@ webpack-dev-server@^3.1.4: webpack-log "^1.1.2" yargs "11.0.0" -webpack-log@^1.0.1: +webpack-log@^1.0.1, webpack-log@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-1.2.0.tgz#a4b34cda6b22b518dbb0ab32e567962d5c72a43d" dependencies: @@ -9158,23 +8831,7 @@ webpack-log@^1.0.1: loglevelnext "^1.0.1" uuid "^3.1.0" -webpack-log@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-1.1.2.tgz#cdc76016537eed24708dc6aa3d1e52189efee107" - dependencies: - chalk "^2.1.0" - log-symbols "^2.1.0" - loglevelnext "^1.0.1" - uuid "^3.1.0" - -webpack-sources@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" - dependencies: - source-list-map "^2.0.0" - source-map "~0.5.3" - -webpack-sources@^1.1.0: +webpack-sources@^1.0.1, webpack-sources@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" dependencies: @@ -9415,39 +9072,23 @@ yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" -yeoman-environment@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.0.5.tgz#84f22bafa84088971fe99ea85f654a3a3dd2b693" - dependencies: - chalk "^2.1.0" - debug "^3.1.0" - diff "^3.3.1" - escape-string-regexp "^1.0.2" - globby "^6.1.0" - grouped-queue "^0.3.3" - inquirer "^3.3.0" - is-scoped "^1.0.0" - lodash "^4.17.4" - log-symbols "^2.1.0" - mem-fs "^1.1.0" - text-table "^0.2.0" - untildify "^3.0.2" - -yeoman-environment@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.0.6.tgz#ae1b21d826b363f3d637f88a7fc9ea7414cb5377" +yeoman-environment@^2.0.0, yeoman-environment@^2.0.5: + version "2.1.1" + resolved "https://registry.yarnpkg.com/yeoman-environment/-/yeoman-environment-2.1.1.tgz#10a045f7fc4397873764882eae055a33e56ee1c5" dependencies: chalk "^2.1.0" + cross-spawn "^6.0.5" debug "^3.1.0" diff "^3.3.1" escape-string-regexp "^1.0.2" - globby "^6.1.0" + globby "^8.0.1" grouped-queue "^0.3.3" - inquirer "^3.3.0" + inquirer "^5.2.0" is-scoped "^1.0.0" - lodash "^4.17.4" + lodash "^4.17.10" log-symbols "^2.1.0" mem-fs "^1.1.0" + strip-ansi "^4.0.0" text-table "^0.2.0" untildify "^3.0.2" |