diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2018-05-01 14:24:39 +0200 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2018-05-01 14:24:39 +0200 |
commit | b337a086d5118e80518945abfc2e88008d9fc1ec (patch) | |
tree | fc6dafd174170773190eb9ed6f7b098a68611695 | |
parent | 3c5cac7655985583ddd2ed64a84bbae2f3d5d703 (diff) | |
parent | 3fcb9c115d776feba3f71fb58359a3935edfda9b (diff) | |
download | gitlab-ce-b337a086d5118e80518945abfc2e88008d9fc1ec.tar.gz |
Merge branch 'master' into backstage/gb/migrate-pipeline-stages-index
* master: (106 commits)
281 files changed, 2783 insertions, 2001 deletions
diff --git a/.gitignore b/.gitignore index e9ff0048c1c..e1561c9db9a 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ eslint-report.html /locale/**/*.time_stamp /.rspec /plugins/* +/.gitlab_pages_secret diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 87c3944dcce..05487134cb1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -110,7 +110,7 @@ stages: # Jobs that only need to pull cache .dedicated-no-docs-pull-cache-job: &dedicated-no-docs-pull-cache-job <<: *dedicated-runner - <<: *except-docs-and-qa + <<: *except-docs <<: *pull-cache dependencies: - setup-test-env @@ -122,6 +122,10 @@ stages: variables: SETUP_DB: "false" +.dedicated-no-docs-and-no-qa-pull-cache-job: &dedicated-no-docs-and-no-qa-pull-cache-job + <<: *dedicated-no-docs-pull-cache-job + <<: *except-docs-and-qa + .rake-exec: &rake-exec <<: *dedicated-no-docs-no-db-pull-cache-job script: @@ -222,7 +226,7 @@ stages: - master@gitlab/gitlab-ee .gitlab-setup: &gitlab-setup - <<: *dedicated-no-docs-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *use-pg variables: CREATE_DB_USER: "true" @@ -262,12 +266,12 @@ stages: # DB migration, rollback, and seed jobs .db-migrate-reset: &db-migrate-reset - <<: *dedicated-no-docs-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job script: - bundle exec rake db:migrate:reset .migration-paths: &migration-paths - <<: *dedicated-no-docs-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job variables: CREATE_DB_USER: "true" script: @@ -289,7 +293,6 @@ stages: # Trigger a package build in omnibus-gitlab repository # package-and-qa: - <<: *dedicated-runner image: ruby:2.4-alpine before_script: [] stage: build @@ -648,7 +651,7 @@ migration:path-mysql: <<: *use-mysql .db-rollback: &db-rollback - <<: *dedicated-no-docs-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job script: - bundle exec rake db:migrate VERSION=20170523121229 - bundle exec rake db:migrate @@ -671,7 +674,7 @@ gitlab:setup-mysql: # Frontend-related jobs gitlab:assets:compile: - <<: *dedicated-no-docs-no-db-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job dependencies: [] variables: NODE_ENV: "production" @@ -692,7 +695,7 @@ gitlab:assets:compile: - webpack-report/ karma: - <<: *dedicated-no-docs-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *use-pg dependencies: - compile-assets @@ -816,7 +819,7 @@ coverage: - coverage/assets/ lint:javascript:report: - <<: *dedicated-no-docs-no-db-pull-cache-job + <<: *dedicated-no-docs-and-no-qa-pull-cache-job stage: post-test dependencies: - compile-assets diff --git a/CHANGELOG.md b/CHANGELOG.md index 8278119cf10..e7db98858e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.7.2 (2018-04-25) + +### Security (2 changes) + +- Serve archive requests with the correct file in all cases. +- Sanitizes user name to avoid XSS attacks. + + ## 10.7.1 (2018-04-23) ### Fixed (11 changes) @@ -237,6 +245,13 @@ entry. - Upgrade Gitaly to upgrade its charlock_holmes. +## 10.6.5 (2018-04-24) + +### Security (1 change) + +- Sanitizes user name to avoid XSS attacks. + + ## 10.6.4 (2018-04-09) ### Fixed (8 changes, 1 of them is from the community) @@ -478,6 +493,13 @@ entry. - Use host URL to build JIRA remote link icon. +## 10.5.8 (2018-04-24) + +### Security (1 change) + +- Sanitizes user name to avoid XSS attacks. + + ## 10.5.7 (2018-04-03) ### Security (2 changes) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 29009010f0e..0f97e779129 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,12 +25,12 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ - [Workflow labels](#workflow-labels) - [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc) - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc) - - [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc) + - [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc) - [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release) - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc) - [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc) - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) -- [Implement design & UI elements](#implement-design-ui-elements) +- [Implement design & UI elements](#implement-design--ui-elements) - [Issue tracker](#issue-tracker) - [Issue triaging](#issue-triaging) - [Feature proposals](#feature-proposals) @@ -114,7 +114,7 @@ is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners. These issues will be of reasonable size and challenge, for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel -please consider we favor +please consider we favor [asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution! ## Workflow labels @@ -127,7 +127,7 @@ Most issues will have labels for at least one of the following: - Type: ~"feature proposal", ~bug, ~customer, etc. - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. -- Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc. +- Team: ~"CI/CD", ~Discussion, ~Quality, ~Platform, etc. - Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release" - Priority: ~P1, ~P2, ~P3, ~P4 - Severity: ~S1, ~S2, ~S3, ~S4 @@ -171,13 +171,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, Subject labels are always all-lowercase. -### Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.) +### Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.) 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, ~Edge, +The current team labels are ~Build, ~"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 @@ -199,12 +199,12 @@ release. There are three levels of Milestone labels: - ~Stretch: Issues that are a stretch goal for delivering in the current milestone. If these issues are not done in the current release, they will strongly be considered for the next release. -- ~"Next Patch Release": Issues to put in the next patch release. Work on these +- ~"Next Patch Release": Issues to put in the next patch release. Work on these first, and add the "Pick Into X" label to the merge request, along with the appropriate milestone. Each issue scheduled for the current milestone should be labeled ~Deliverable -or ~"Stretch". Any open issue for a previous milestone should be labeled +or ~"Stretch". Any open issue for a previous milestone should be labeled ~"Next Patch Release", or otherwise rescheduled to a different milestone. ### Bug Priority labels (~P1, ~P2, ~P3 & etc.) @@ -221,16 +221,16 @@ This label documents the planned timeline & urgency which is used to measure aga #### Specific Priority guidance -| Label | Availability / Performance | +| Label | Availability / Performance | |-------|--------------------------------------------------------------| -| ~P1 | | +| ~P1 | | | ~P2 | The issue is (almost) guaranteed to occur in the near future | | ~P3 | The issue is likely to occur in the near future | | ~P4 | The issue _may_ occur but it's not likely | ### Bug Severity labels (~S1, ~S2, ~S3 & etc.) -Severity labels help us clearly communicate the impact of a ~bug on users. +Severity labels help us clearly communicate the impact of a ~bug on users. | Label | Meaning | Impact of the defect | Example | |-------|-------------------|-------------------------------------------------------|---------| @@ -241,9 +241,9 @@ Severity labels help us clearly communicate the impact of a ~bug on users. #### Specific Severity guidance -| Label | Security Impact | +| Label | Security Impact | |-------|-------------------------------------------------------------------| -| ~S1 | >50% customers impacted (possible company extinction level event) | +| ~S1 | >50% customers impacted (possible company extinction level event) | | ~S2 | Multiple customers impacted (but not apocalyptic) | | ~S3 | A single customer impacted | | ~S4 | No customer impact, or expected impact within 30 days | @@ -727,4 +727,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [^1]: Please note that specs other than JavaScript specs are considered backend code. -
\ No newline at end of file diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index a3df0a6959e..f374f6662e9 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -0.8.0 +0.9.1 @@ -51,7 +51,6 @@ gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.4' gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth-authentiq', '~> 0.3.1' -gem 'omniauth-jwt', '~> 0.0.2' gem 'rack-oauth2', '~> 1.2.1' gem 'jwt', '~> 1.5.6' @@ -283,7 +282,6 @@ gem 'batch-loader', '~> 1.2.1' gem 'peek', '~> 1.0.1' gem 'peek-gc', '~> 0.0.2' gem 'peek-mysql2', '~> 1.1.0', group: :mysql -gem 'peek-performance_bar', '~> 1.3.0' gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' @@ -416,7 +414,7 @@ end # Gitaly GRPC client gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly' -gem 'grpc', '~> 1.10.0' +gem 'grpc', '~> 1.11.0' # Locked until https://github.com/google/protobuf/issues/4210 is closed gem 'google-protobuf', '= 3.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index d5e1c428e25..9b2c47587ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -374,7 +374,7 @@ GEM rake grape_logging (1.7.0) grape - grpc (1.10.0) + grpc (1.11.0) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) googleauth (>= 0.5.1, < 0.7) @@ -555,9 +555,6 @@ GEM jwt (>= 1.5) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.5) - omniauth-jwt (0.0.2) - jwt - omniauth (~> 1.1) omniauth-kerberos (0.3.0) omniauth-multipassword timfel-krb5-auth (~> 0.8) @@ -603,8 +600,6 @@ GEM atomic (>= 1.0.0) mysql2 peek - peek-performance_bar (1.3.1) - peek (>= 0.1.0) peek-pg (1.3.0) concurrent-ruby concurrent-ruby-ext @@ -1078,7 +1073,7 @@ DEPENDENCIES grape-entity (~> 0.6.0) grape-route-helpers (~> 2.1.0) grape_logging (~> 1.7) - grpc (~> 1.10.0) + grpc (~> 1.11.0) haml_lint (~> 0.26.0) hamlit (~> 2.6.1) hashie-forbidden_attributes @@ -1119,7 +1114,6 @@ DEPENDENCIES omniauth-github (~> 1.1.1) omniauth-gitlab (~> 1.0.2) omniauth-google-oauth2 (~> 0.5.3) - omniauth-jwt (~> 0.0.2) omniauth-kerberos (~> 0.3.0) omniauth-oauth2-generic (~> 0.2.2) omniauth-saml (~> 1.10) @@ -1130,7 +1124,6 @@ DEPENDENCIES peek (~> 1.0.1) peek-gc (~> 0.0.2) peek-mysql2 (~> 1.1.0) - peek-performance_bar (~> 1.3.0) peek-pg (~> 1.3.0) peek-rblineprof (~> 0.2.0) peek-redis (~> 1.2.0) diff --git a/app/assets/javascripts/ide/components/changed_file_icon.vue b/app/assets/javascripts/ide/components/changed_file_icon.vue index 1fc11c84639..fdbc14a4b8f 100644 --- a/app/assets/javascripts/ide/components/changed_file_icon.vue +++ b/app/assets/javascripts/ide/components/changed_file_icon.vue @@ -26,11 +26,18 @@ export default { required: false, default: false, }, + forceModifiedIcon: { + type: Boolean, + required: false, + default: false, + }, }, computed: { changedIcon() { const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : ''; - return this.file.tempFile ? `file-addition${suffix}` : `file-modified${suffix}`; + return this.file.tempFile && !this.forceModifiedIcon + ? `file-addition${suffix}` + : `file-modified${suffix}`; }, stagedIcon() { return `${this.changedIcon}-solid`; diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index 769e9b79cad..b1b5c0d4a28 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -1,49 +1,54 @@ <script> - import { mapActions } from 'vuex'; - import icon from '~/vue_shared/components/icon.vue'; - import newModal from './modal.vue'; - import upload from './upload.vue'; +import { mapActions } from 'vuex'; +import icon from '~/vue_shared/components/icon.vue'; +import newModal from './modal.vue'; +import upload from './upload.vue'; - export default { - components: { - icon, - newModal, - upload, +export default { + components: { + icon, + newModal, + upload, + }, + props: { + branch: { + type: String, + required: true, }, - props: { - branch: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, + path: { + type: String, + required: true, }, - data() { - return { - openModal: false, - modalType: '', - dropdownOpen: false, - }; + }, + data() { + return { + openModal: false, + modalType: '', + dropdownOpen: false, + }; + }, + watch: { + dropdownOpen() { + this.$nextTick(() => { + this.$refs.dropdownMenu.scrollIntoView(); + }); }, - methods: { - ...mapActions([ - 'createTempEntry', - ]), - createNewItem(type) { - this.modalType = type; - this.openModal = true; - this.dropdownOpen = false; - }, - hideModal() { - this.openModal = false; - }, - openDropdown() { - this.dropdownOpen = !this.dropdownOpen; - }, + }, + methods: { + ...mapActions(['createTempEntry']), + createNewItem(type) { + this.modalType = type; + this.openModal = true; + this.dropdownOpen = false; }, - }; + hideModal() { + this.openModal = false; + }, + openDropdown() { + this.dropdownOpen = !this.dropdownOpen; + }, + }, +}; </script> <template> @@ -71,7 +76,10 @@ css-classes="pull-left" /> </button> - <ul class="dropdown-menu dropdown-menu-right"> + <ul + class="dropdown-menu dropdown-menu-right" + ref="dropdownMenu" + > <li> <a href="#" diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 4b5a50785b6..a95a0225950 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -40,13 +40,6 @@ export default { return __('Create file'); }, - formLabelName() { - if (this.type === 'tree') { - return __('Directory name'); - } - - return __('File name'); - }, }, mounted() { this.$refs.fieldName.focus(); @@ -82,8 +75,8 @@ export default { @submit.prevent="createEntryInStore" > <fieldset class="form-group append-bottom-0"> - <label class="label-light col-sm-3"> - {{ formLabelName }} + <label class="label-light col-sm-3 ide-new-modal-label"> + {{ __('Name') }} </label> <div class="col-sm-9"> <input diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 8b18c7d28b4..e86db2da4a6 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -97,7 +97,7 @@ export default { :file="file" /> </span> - <span class="pull-right"> + <span class="pull-right ide-file-icon-holder"> <mr-file-icon v-if="file.mrChange" /> @@ -106,7 +106,8 @@ export default { :file="file" :show-tooltip="true" :show-staged-icon="true" - class="prepend-top-5 pull-right" + :force-modified-icon="true" + class="pull-right" /> </span> <new-dropdown diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue index 35a362b01e0..a3ee3184c19 100644 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ b/app/assets/javascripts/ide/components/repo_tab.vue @@ -84,6 +84,7 @@ export default { <changed-file-icon v-else :file="tab" + :force-modified-icon="true" /> </button> diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index cbe43f5f7f2..4c8c997e376 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { } }; -export const toggleRightPanelCollapsed = ( - { dispatch, state }, - e = undefined, -) => { +export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => { if (e) { $(e.currentTarget) .tooltip('hide') @@ -77,7 +74,7 @@ export const createTempEntry = ( } worker.addEventListener('message', ({ data }) => { - const { file } = data; + const { file, parentPath } = data; worker.terminate(); @@ -93,6 +90,10 @@ export const createTempEntry = ( dispatch('setFileActive', file.path); } + if (parentPath && !state.entries[parentPath].opened) { + commit(types.TOGGLE_TREE_OPEN, parentPath); + } + resolve(file); }); @@ -137,6 +138,14 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); }; +export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, tempFile }) => { + commit(types.UPDATE_TEMP_FLAG, { path: file.path, tempFile }); + + if (file.parentPath) { + dispatch('updateTempFlagForEntry', { file: state.entries[file.parentPath], tempFile }); + } +}; + export const toggleFileFinder = ({ commit }, fileFindVisible) => commit(types.TOGGLE_FILE_FINDER, fileFindVisible); diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index d782e0a84d2..fcdb3b753b2 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive const file = state.entries[path]; commit(types.TOGGLE_LOADING, { entry: file }); return service - .getFileData(file.url) + .getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url}`) .then(res => { const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); setPageTitle(pageTitle); diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index 119debaf5f3..349ff68f1e3 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -110,6 +110,17 @@ export const updateFilesAfterCommit = ( { root: true }, ); + commit( + rootTypes.TOGGLE_FILE_CHANGED, + { + file, + changed: false, + }, + { root: true }, + ); + + dispatch('updateTempFlagForEntry', { file, tempFile: false }, { root: true }); + eventHub.$emit(`editor.update.model.content.${file.key}`, { content: file.content, changed: !!changedFile, diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index c7f08449d03..f5c12db6db0 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -59,4 +59,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; +export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 2a6c136aeed..0c1d720df09 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request'; import fileMutations from './mutations/file'; import treeMutations from './mutations/tree'; import branchMutations from './mutations/branch'; +import { sortTree } from './utils'; export default { [types.SET_INITIAL_DATA](state, data) { @@ -73,7 +74,7 @@ export default { f => foundEntry.tree.find(e => e.path === f.path) === undefined, ); Object.assign(foundEntry, { - tree: foundEntry.tree.concat(tree), + tree: sortTree(foundEntry.tree.concat(tree)), }); } @@ -86,10 +87,16 @@ export default { if (!foundEntry) { Object.assign(state.trees[`${projectId}/${branchId}`], { - tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList), + tree: sortTree(state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList)), }); } }, + [types.UPDATE_TEMP_FLAG](state, { path, tempFile }) { + Object.assign(state.entries[path], { + tempFile, + changed: tempFile, + }); + }, [types.UPDATE_VIEWER](state, viewer) { Object.assign(state, { viewer, diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js index 86612d845e0..59185f8f0ad 100644 --- a/app/assets/javascripts/ide/stores/utils.js +++ b/app/assets/javascripts/ide/stores/utils.js @@ -33,6 +33,7 @@ export const dataStructure = () => ({ raw: '', content: '', parentTreeUrl: '', + parentPath: '', renderError: false, base64: false, editorRow: 1, @@ -65,6 +66,7 @@ export const decorateData = entity => { previewMode, file_lock, html, + parentPath = '', } = entity; return { @@ -81,6 +83,7 @@ export const decorateData = entity => { opened, active, parentTreeUrl, + parentPath, changed, renderError, content, @@ -121,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => { } else if (a.type === 'blob' && b.type === 'tree') { return 1; } - if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; - if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; return 0; }; diff --git a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js index a1673276900..d249b05f47c 100644 --- a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js +++ b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js @@ -6,6 +6,7 @@ self.addEventListener('message', e => { const treeList = []; let file; + let parentPath; const entries = data.reduce((acc, path) => { const pathSplit = path.split('/'); const blobName = pathSplit.pop().trim(); @@ -17,6 +18,8 @@ self.addEventListener('message', e => { const foundEntry = acc[folderPath]; if (!foundEntry) { + parentPath = parentFolder ? parentFolder.path : null; + const tree = decorateData({ projectId, branchId, @@ -29,6 +32,7 @@ self.addEventListener('message', e => { tempFile, changed: tempFile, opened: tempFile, + parentPath, }); Object.assign(acc, { @@ -52,6 +56,8 @@ self.addEventListener('message', e => { if (blobName !== '') { const fileFolder = acc[pathSplit.join('/')]; + parentPath = fileFolder ? fileFolder.path : null; + file = decorateData({ projectId, branchId, @@ -66,6 +72,7 @@ self.addEventListener('message', e => { content, base64, previewMode: viewerInformationForPath(blobName), + parentPath, }); Object.assign(acc, { @@ -86,5 +93,6 @@ self.addEventListener('message', e => { entries, treeList: sortTree(treeList), file, + parentPath, }); }); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 7e9a50a885d..f8b3d3061f0 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store'; export default class MilestoneSelect { constructor(currentProject, els, options = {}) { if (currentProject !== null) { - this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; + this.currentProject = + typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; } this.init(els, options); @@ -26,7 +27,10 @@ export default class MilestoneSelect { } $els.each((i, dropdown) => { - let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; + let milestoneLinkNoneTemplate, + milestoneLinkTemplate, + selectedMilestone, + selectedMilestoneDefault; const $dropdown = $(dropdown); const projectId = $dropdown.data('projectId'); const milestonesUrl = $dropdown.data('milestones'); @@ -46,45 +50,47 @@ export default class MilestoneSelect { const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $value = $block.find('.value'); const $loading = $block.find('.block-loading').fadeOut(); - selectedMilestoneDefault = (showAny ? '' : null); - selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); + selectedMilestoneDefault = showAny ? '' : null; + selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { - milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); + milestoneLinkTemplate = _.template( + '<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>', + ); milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; } return $dropdown.glDropdown({ showMenuAbove: showMenuAbove, - data: (term, callback) => axios.get(milestonesUrl) - .then(({ data }) => { + data: (term, callback) => + axios.get(milestonesUrl).then(({ data }) => { const extraOptions = []; if (showAny) { extraOptions.push({ - id: 0, - name: '', - title: 'Any Milestone' + id: null, + name: null, + title: 'Any Milestone', }); } if (showNo) { extraOptions.push({ id: -1, name: 'No Milestone', - title: 'No Milestone' + title: 'No Milestone', }); } if (showUpcoming) { extraOptions.push({ id: -2, name: '#upcoming', - title: 'Upcoming' + title: 'Upcoming', }); } if (showStarted) { extraOptions.push({ id: -3, name: '#started', - title: 'Started' + title: 'Started', }); } if (extraOptions.length) { @@ -106,7 +112,7 @@ export default class MilestoneSelect { `, filterable: true, search: { - fields: ['title'] + fields: ['title'], }, selectable: true, toggleLabel: (selected, el, e) => { @@ -119,7 +125,7 @@ export default class MilestoneSelect { defaultLabel: defaultLabel, fieldName: $dropdown.data('fieldName'), text: milestone => _.escape(milestone.title), - id: (milestone) => { + id: milestone => { if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { return milestone.name; } else { @@ -131,7 +137,7 @@ export default class MilestoneSelect { // display:block overrides the hide-collapse rule return $value.css('display', ''); }, - opened: (e) => { + opened: e => { const $el = $(e.currentTarget); if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; @@ -140,7 +146,7 @@ export default class MilestoneSelect { $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active'); }, vue: $dropdown.hasClass('js-issue-board-sidebar'), - clicked: (clickEvent) => { + clicked: clickEvent => { const { $el, e } = clickEvent; let selected = clickEvent.selectedObj; @@ -155,11 +161,14 @@ export default class MilestoneSelect { const page = $('body').attr('data-page'); const isIssueIndex = page === 'projects:issues:index'; - const isMRIndex = (page === page && page === 'projects:merge_requests:index'); - const isSelecting = (selected.name !== selectedMilestone); + const isMRIndex = page === page && page === 'projects:merge_requests:index'; + const isSelecting = selected.name !== selectedMilestone; selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { + if ( + $dropdown.hasClass('js-filter-bulk-update') || + $dropdown.hasClass('js-issuable-form-dropdown') + ) { e.preventDefault(); return; } @@ -177,10 +186,13 @@ export default class MilestoneSelect { return $dropdown.closest('form').submit(); } else if ($dropdown.hasClass('js-issue-board-sidebar')) { if (selected.id !== -1 && isSelecting) { - gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ - id: selected.id, - title: selected.name - })); + gl.issueBoards.boardStoreIssueSet( + 'milestone', + new ListMilestone({ + id: selected.id, + title: selected.name, + }), + ); } else { gl.issueBoards.boardStoreIssueDelete('milestone'); } @@ -188,7 +200,8 @@ export default class MilestoneSelect { $dropdown.trigger('loading.gl.dropdown'); $loading.removeClass('hidden').fadeIn(); - gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) + gl.issueBoards.BoardsStore.detail.issue + .update($dropdown.attr('data-issue-update')) .then(() => { $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); @@ -203,7 +216,8 @@ export default class MilestoneSelect { data[abilityName].milestone_id = selected != null ? selected : null; $loading.removeClass('hidden').fadeIn(); $dropdown.trigger('loading.gl.dropdown'); - return axios.put(issueUpdateURL, data) + return axios + .put(issueUpdateURL, data) .then(({ data }) => { $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); @@ -215,7 +229,10 @@ export default class MilestoneSelect { data.milestone.name = data.milestone.title; $value.html(milestoneLinkTemplate(data.milestone)); return $sidebarCollapsedValue - .attr('data-original-title', `${data.milestone.name}<br />${data.milestone.remaining}`) + .attr( + 'data-original-title', + `${data.milestone.name}<br />${data.milestone.remaining}`, + ) .find('span') .text(data.milestone.title); } else { @@ -230,7 +247,7 @@ export default class MilestoneSelect { $loading.fadeOut(); }); } - } + }, }); }); } diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index d492d1cd001..cbe4774a360 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -86,7 +86,7 @@ export default { v-html="resolveSvg" ></span> </span> - <span class=".line-resolve-text"> + <span class="line-resolve-text"> {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved </span> </div> diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index 0e3ac636661..9ce176744ba 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -52,16 +52,15 @@ text() { const keepContributionsText = s__(`AdminArea| You are about to permanently delete the user %{username}. - This will delete all of the issues, merge requests, and groups linked to them. + Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); const deleteContributionsText = s__(`AdminArea| You are about to permanently delete the user %{username}. - Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". + This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); - return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText, { username: `<strong>${_.escape(this.username)}</strong>`, diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index cbc2d80ee18..50d042fef29 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -188,11 +188,11 @@ export default class ActivityCalendar { }, { text: 'W', - y: 29 + this.dayYPos(2), + y: 29 + this.dayYPos(3), }, { text: 'F', - y: 29 + this.dayYPos(3), + y: 29 + this.dayYPos(5), }, ]; this.svg diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index 2fd1715ee79..8ffaa52d9e8 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service'; import detailedMetric from './detailed_metric.vue'; import requestSelector from './request_selector.vue'; import simpleMetric from './simple_metric.vue'; -import upstreamPerformanceBar from './upstream_performance_bar.vue'; import Flash from '../../flash'; @@ -14,7 +13,6 @@ export default { detailedMetric, requestSelector, simpleMetric, - upstreamPerformanceBar, }, props: { store: { @@ -128,9 +126,6 @@ export default { {{ currentRequest.details.host.hostname }} </span> </div> - <upstream-performance-bar - v-if="initialRequest && currentRequest.details" - /> <detailed-metric v-for="metric in $options.detailedMetrics" :key="metric.metric" diff --git a/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue b/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue deleted file mode 100644 index 2b5915f381f..00000000000 --- a/app/assets/javascripts/performance_bar/components/upstream_performance_bar.vue +++ /dev/null @@ -1,20 +0,0 @@ -<script> -export default { - mounted() { - const upstreamPerformanceBar = document - .getElementById('peek-view-performance-bar') - .cloneNode(true); - - upstreamPerformanceBar.classList.remove('hidden'); - - this.$refs.wrapper.appendChild(upstreamPerformanceBar); - }, -}; -</script> -<template> - <div - id="peek-view-performance-bar-vue" - class="view" - ref="wrapper" - ></div> -</template> diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index a0ddf36a672..4a98aed7679 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -1,5 +1,3 @@ -import 'vendor/peek.performance_bar'; - import Vue from 'vue'; import performanceBarApp from './components/performance_bar_app.vue'; import PerformanceBarStore from './stores/performance_bar_store'; diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js index 1eadebc7004..b267422cd97 100644 --- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js +++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import _ from 'underscore'; function isValidProjectId(id) { return id > 0; @@ -43,7 +44,7 @@ class SidebarMoveIssue { renderRow: project => ` <li> <a href="#" class="js-move-issue-dropdown-item"> - ${project.name_with_namespace} + ${_.escape(project.name_with_namespace)} </a> </li> `, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue index 602b68ea572..7d366c495f0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue @@ -1,66 +1,70 @@ <script> - import { n__ } from '~/locale'; - import statusIcon from '../mr_widget_status_icon.vue'; - import eventHub from '../../event_hub'; +import { n__ } from '~/locale'; +import statusIcon from '../mr_widget_status_icon.vue'; +import eventHub from '../../event_hub'; - export default { - name: 'MRWidgetFailedToMerge', +export default { + name: 'MRWidgetFailedToMerge', - components: { - statusIcon, - }, + components: { + statusIcon, + }, - props: { - mr: { - type: Object, - required: true, - default: () => ({}), - }, + props: { + mr: { + type: Object, + required: true, + default: () => ({}), }, + }, - data() { - return { - timer: 10, - isRefreshing: false, - }; - }, + data() { + return { + timer: 10, + isRefreshing: false, + intervalId: null, + }; + }, - computed: { - timerText() { - return n__( - 'Refreshing in a second to show the updated status...', - 'Refreshing in %d seconds to show the updated status...', - this.timer, - ); - }, + computed: { + timerText() { + return n__( + 'Refreshing in a second to show the updated status...', + 'Refreshing in %d seconds to show the updated status...', + this.timer, + ); }, + }, - mounted() { - setInterval(() => { - this.updateTimer(); - }, 1000); - }, + mounted() { + this.intervalId = setInterval(this.updateTimer, 1000); + }, - created() { - eventHub.$emit('DisablePolling'); - }, + created() { + eventHub.$emit('DisablePolling'); + }, - methods: { - refresh() { - this.isRefreshing = true; - eventHub.$emit('MRWidgetUpdateRequested'); - eventHub.$emit('EnablePolling'); - }, - updateTimer() { - this.timer = this.timer - 1; + beforeDestroy() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + }, - if (this.timer === 0) { - this.refresh(); - } - }, + methods: { + refresh() { + this.isRefreshing = true; + eventHub.$emit('MRWidgetUpdateRequested'); + eventHub.$emit('EnablePolling'); }, + updateTimer() { + this.timer = this.timer - 1; - }; + if (this.timer === 0) { + this.refresh(); + } + }, + }, +}; </script> <template> <div class="mr-widget-body media"> diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue index ee1c3498748..be2755452e2 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/file_icon.vue @@ -1,9 +1,9 @@ <script> - import getIconForFile from './file_icon/file_icon_map'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - import icon from '../../vue_shared/components/icon.vue'; +import getIconForFile from './file_icon/file_icon_map'; +import loadingIcon from '../../vue_shared/components/loading_icon.vue'; +import icon from '../../vue_shared/components/icon.vue'; - /* This is a re-usable vue component for rendering a svg sprite +/* This is a re-usable vue component for rendering a svg sprite icon Sample configuration: @@ -15,60 +15,60 @@ /> */ - export default { - components: { - loadingIcon, - icon, +export default { + components: { + loadingIcon, + icon, + }, + props: { + fileName: { + type: String, + required: true, }, - props: { - fileName: { - type: String, - required: true, - }, - folder: { - type: Boolean, - required: false, - default: false, - }, + folder: { + type: Boolean, + required: false, + default: false, + }, - opened: { - type: Boolean, - required: false, - default: false, - }, + opened: { + type: Boolean, + required: false, + default: false, + }, - loading: { - type: Boolean, - required: false, - default: false, - }, + loading: { + type: Boolean, + required: false, + default: false, + }, - size: { - type: Number, - required: false, - default: 16, - }, + size: { + type: Number, + required: false, + default: 16, + }, - cssClasses: { - type: String, - required: false, - default: '', - }, + cssClasses: { + type: String, + required: false, + default: '', + }, + }, + computed: { + spriteHref() { + const iconName = getIconForFile(this.fileName) || 'file'; + return `${gon.sprite_file_icons}#${iconName}`; + }, + folderIconName() { + return this.opened ? 'folder-open' : 'folder'; }, - computed: { - spriteHref() { - const iconName = getIconForFile(this.fileName) || 'file'; - return `${gon.sprite_file_icons}#${iconName}`; - }, - folderIconName() { - return this.opened ? 'folder-open' : 'folder'; - }, - iconSizeClass() { - return this.size ? `s${this.size}` : ''; - }, + iconSizeClass() { + return this.size ? `s${this.size}` : ''; }, - }; + }, +}; </script> <template> <span> @@ -82,6 +82,7 @@ v-if="!loading && folder" :name="folderIconName" :size="size" + css-classes="folder-icon" /> <loading-icon v-if="loading" diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js index 70b9efe0c68..d29c7fe973a 100644 --- a/app/assets/javascripts/vue_shared/models/label.js +++ b/app/assets/javascripts/vue_shared/models/label.js @@ -1,4 +1,4 @@ -class ListLabel { +export default class ListLabel { constructor(obj) { this.id = obj.id; this.title = obj.title; diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index 2f3a80daa90..3fa7a260017 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -19,6 +19,7 @@ .fork-svg { margin-right: 4px; + vertical-align: bottom; } } diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index e9384d41e00..1aca3c5cf1a 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -70,7 +70,7 @@ } .branch-info .commit-icon { - margin-right: 3px; + margin-right: 8px; svg { top: 3px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 81e98f358a8..6d5c6cb136f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -772,7 +772,3 @@ ul.notes { height: auto; } } - -.line-resolve-text { - vertical-align: middle; -} diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 6d77260b658..6342042374f 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -55,6 +55,7 @@ white-space: nowrap; text-overflow: ellipsis; max-width: inherit; + line-height: 22px; svg { vertical-align: middle; @@ -67,6 +68,11 @@ } } + .ide-file-icon-holder { + display: flex; + align-items: center; + } + .ide-file-changed-icon { margin-left: auto; @@ -77,7 +83,6 @@ .ide-new-btn { display: none; - margin-bottom: -4px; margin-right: -8px; } @@ -90,10 +95,8 @@ } } - &.folder { - svg { - fill: $gl-text-color-secondary; - } + .folder-icon { + fill: $gl-text-color-secondary; } } @@ -111,6 +114,7 @@ .file-col-commit-message { display: flex; overflow: visible; + align-items: center; padding: 6px 12px; } @@ -438,7 +442,7 @@ .projects-sidebar { display: flex; flex-direction: column; - height: 100%; + flex: 1; .context-header { width: auto; @@ -967,3 +971,7 @@ background: transparent; resize: none; } + +.ide-new-modal-label { + line-height: 34px; +} diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 45ae94abaff..06ef58531d7 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -1,5 +1,4 @@ @import 'framework/variables'; -@import 'peek/views/performance_bar'; @import 'peek/views/rblineprof'; #js-peek { diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 9137bc92810..40d9fa18a10 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController omniauth_flow(Gitlab::Auth::OAuth) end - Gitlab.config.omniauth.providers.each do |provider| - alias_method provider['name'], :handle_omniauth + AuthHelper.providers_for_base_controller.each do |provider| + alias_method provider, :handle_omniauth end # Extend the standard implementation to also increment diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index 0282b378d88..0754123a3cf 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder def all_groups return [owned_groups] if params[:owned] - return [Group.all] if current_user&.full_private_access? + return [Group.all] if current_user&.full_private_access? && all_available? groups = [] groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user @@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder end def include_public_groups? - current_user.nil? || params.fetch(:all_available, true) + current_user.nil? || all_available? + end + + def all_available? + params.fetch(:all_available, true) end end diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index f187a3b61fe..0a487839aff 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -14,6 +14,7 @@ class PipelinesFinder items = by_scope(items) items = by_status(items) items = by_ref(items) + items = by_sha(items) items = by_name(items) items = by_username(items) items = by_yaml_errors(items) @@ -69,6 +70,14 @@ class PipelinesFinder end end + def by_sha(items) + if params[:sha].present? + items.where(sha: params[:sha]) + else + items + end + end + def by_name(items) if params[:name].present? items.joins(:user).where(users: { name: params[:name] }) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index c109954f3a3..d2daee22aba 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,6 +1,6 @@ module AuthHelper PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze - FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze + LDAP_PROVIDER = /\Aldap/ def ldap_enabled? Gitlab::Auth::LDAP::Config.enabled? @@ -23,7 +23,7 @@ module AuthHelper end def form_based_provider?(name) - FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s } + [LDAP_PROVIDER, 'crowd'].any? { |pattern| pattern === name.to_s } end def form_based_providers @@ -38,6 +38,10 @@ module AuthHelper auth_providers.reject { |provider| form_based_provider?(provider) } end + def providers_for_base_controller + auth_providers.reject { |provider| LDAP_PROVIDER === provider } + end + def enabled_button_based_providers disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || [] diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index fef29789832..e7a36e20050 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -17,7 +17,7 @@ module BlobHelper end def ide_edit_path(project = @project, ref = @ref, path = @path, options = {}) - "#{ide_path}/project#{edit_blob_path(project, ref, path, options)}" + "#{ide_path}/project#{url_for([project, "edit", "blob", id: [ref, path], script_name: "/"])}" end def edit_blob_button(project = @project, ref = @ref, path = @path, options = {}) diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 98894b86551..e594a1d0ba3 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -63,7 +63,7 @@ module CommitsHelper # Returns a link formatted as a commit branch link def commit_branch_link(url, text) link_to(url, class: 'label label-gray ref-name branch-link') do - sprite_icon('fork', size: 16, css_class: 'fork-svg') + "#{text}" + sprite_icon('fork', size: 12, css_class: 'fork-svg') + "#{text}" end end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 5089da519df..5a2360b4661 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -41,7 +41,7 @@ module DropdownsHelper def dropdown_toggle(toggle_text, data_attr, options = {}) default_label = data_attr[:default_label] - content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do + content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') output.html_safe diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a64b2acdd77..801e624e1de 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -400,7 +400,8 @@ module ProjectsHelper exports_path = File.join(Settings.shared['path'], 'tmp/project_exports') filtered_message = message.strip.gsub(exports_path, "[REPO EXPORT PATH]") - filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") + disk_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path + filtered_message.gsub(disk_path.chomp('/'), "[REPOS PATH]") end def project_child_container_class(view_path) diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 00fe67d6ffb..5b4a141dbcf 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -1,14 +1,14 @@ module SystemNoteHelper ICON_NAMES_BY_ACTION = { 'commit' => 'commit', - 'description' => 'pencil', + 'description' => 'pencil-square', 'merge' => 'git-merge', 'merged' => 'git-merge', 'opened' => 'issue-open', 'closed' => 'issue-close', 'time_tracking' => 'timer', 'assignee' => 'user', - 'title' => 'pencil', + 'title' => 'pencil-square', 'task' => 'task-done', 'label' => 'label', 'cross_reference' => 'comment-dots', @@ -18,7 +18,7 @@ module SystemNoteHelper 'milestone' => 'clock', 'discussion' => 'comment', 'moved' => 'arrow-right', - 'outdated' => 'pencil', + 'outdated' => 'pencil-square', 'duplicate' => 'issue-duplicate', 'locked' => 'lock', 'unlocked' => 'lock-open' diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index f05e606995d..f66bdd529f1 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -45,25 +45,25 @@ module Storage # Hooks - # Save the storage paths before the projects are destroyed to use them on after destroy + # Save the storages before the projects are destroyed to use them on after destroy def prepare_for_destroy - old_repository_storage_paths + old_repository_storages end private def move_repositories - # Move the namespace directory in all storage paths used by member projects - repository_storage_paths.each do |repository_storage_path| + # Move the namespace directory in all storages used by member projects + repository_storages.each do |repository_storage| # Ensure old directory exists before moving it - gitlab_shell.add_namespace(repository_storage_path, full_path_was) + gitlab_shell.add_namespace(repository_storage, full_path_was) # Ensure new directory exists before moving it (if there's a parent) - gitlab_shell.add_namespace(repository_storage_path, parent.full_path) if parent + gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent - unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path) + unless gitlab_shell.mv_namespace(repository_storage, full_path_was, full_path) - Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}" + Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_was} to #{full_path}" # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs @@ -72,33 +72,33 @@ module Storage end end - def old_repository_storage_paths - @old_repository_storage_paths ||= repository_storage_paths + def old_repository_storages + @old_repository_storage_paths ||= repository_storages end - def repository_storage_paths + def repository_storages # We need to get the storage paths for all the projects, even the ones that are # pending delete. Unscoping also get rids of the default order, which causes # problems with SELECT DISTINCT. Project.unscoped do - all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path) + all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage) end end def rm_dir # Remove the namespace directory in all storages paths used by member projects - old_repository_storage_paths.each do |repository_storage_path| + old_repository_storages.each do |repository_storage| # Move namespace directory into trash. # We will remove it later async new_path = "#{full_path}+#{id}+deleted" - if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path) + if gitlab_shell.mv_namespace(repository_storage, full_path, new_path) Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}") # Remove namespace directroy async with delay so # GitLab has time to remove all projects first run_after_commit do - GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path) + GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path) end end end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 661e668dbf9..5da739f9618 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -37,20 +37,20 @@ class GroupMember < Member private def send_invite - notification_service.invite_group_member(self, @raw_invite_token) + run_after_commit_or_now { notification_service.invite_group_member(self, @raw_invite_token) } super end def post_create_hook - notification_service.new_group_member(self) + run_after_commit_or_now { notification_service.new_group_member(self) } super end def post_update_hook if access_level_changed? - notification_service.update_group_member(self) + run_after_commit { notification_service.update_group_member(self) } end super diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 1c7ed4a96df..024106056b4 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -92,7 +92,7 @@ class ProjectMember < Member private def send_invite - notification_service.invite_project_member(self, @raw_invite_token) + run_after_commit_or_now { notification_service.invite_project_member(self, @raw_invite_token) } super end @@ -100,7 +100,7 @@ class ProjectMember < Member def post_create_hook unless owner? event_service.join_project(self.project, self.user) - notification_service.new_project_member(self) + run_after_commit_or_now { notification_service.new_project_member(self) } end super @@ -108,7 +108,7 @@ class ProjectMember < Member def post_update_hook if access_level_changed? - notification_service.update_project_member(self) + run_after_commit { notification_service.update_project_member(self) } end super diff --git a/app/models/project.rb b/app/models/project.rb index 172980b0ad4..d4e9e51c7be 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -518,10 +518,6 @@ class Project < ActiveRecord::Base repository.empty? end - def repository_storage_path - Gitlab.config.repositories.storages[repository_storage]&.legacy_disk_path - end - def team @team ||= ProjectTeam.new(self) end @@ -1105,7 +1101,7 @@ class Project < ActiveRecord::Base # Check if repository already exists on disk def check_repository_path_availability return true if skip_disk_validation - return false unless repository_storage_path + return false unless repository_storage expires_full_path_cache # we need to clear cache to validate renames correctly @@ -1910,14 +1906,14 @@ class Project < ActiveRecord::Base def check_repository_absence! return if skip_disk_validation - if repository_storage_path.blank? || repository_with_same_path_already_exists? + if repository_storage.blank? || repository_with_same_path_already_exists? errors.add(:base, 'There is already a repository with that name on disk') throw :abort end end def repository_with_same_path_already_exists? - gitlab_shell.exists?(repository_storage_path, "#{disk_path}.git") + gitlab_shell.exists?(repository_storage, "#{disk_path}.git") end # set last_activity_at to the same as created_at diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index b7e38ceb502..f799a0b4227 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -21,7 +21,7 @@ class ProjectWiki end delegate :empty?, to: :pages - delegate :repository_storage_path, :hashed_storage?, to: :project + delegate :repository_storage, :hashed_storage?, to: :project def path @project.path + '.wiki' diff --git a/app/models/repository.rb b/app/models/repository.rb index 5bdaa7f0720..6831305fb93 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -84,9 +84,14 @@ class Repository # Return absolute path to repository def path_to_repo - @path_to_repo ||= File.expand_path( - File.join(repository_storage_path, disk_path + '.git') - ) + @path_to_repo ||= + begin + storage = Gitlab.config.repositories.storages[@project.repository_storage] + + File.expand_path( + File.join(storage.legacy_disk_path, disk_path + '.git') + ) + end end def inspect @@ -915,10 +920,6 @@ class Repository raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref) end - def repository_storage_path - @project.repository_storage_path - end - def rebase(user, merge_request) raw.rebase(user, merge_request.id, branch: merge_request.source_branch, branch_sha: merge_request.source_branch_sha, diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb index fae1b64961a..26b4b78ac64 100644 --- a/app/models/storage/hashed_project.rb +++ b/app/models/storage/hashed_project.rb @@ -1,7 +1,7 @@ module Storage class HashedProject attr_accessor :project - delegate :gitlab_shell, :repository_storage_path, to: :project + delegate :gitlab_shell, :repository_storage, to: :project ROOT_PATH_PREFIX = '@hashed'.freeze @@ -24,7 +24,7 @@ module Storage end def ensure_storage_path_exists - gitlab_shell.add_namespace(repository_storage_path, base_dir) + gitlab_shell.add_namespace(repository_storage, base_dir) end def rename_repo diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb index 9d9e5e1d352..27cb388c702 100644 --- a/app/models/storage/legacy_project.rb +++ b/app/models/storage/legacy_project.rb @@ -1,7 +1,7 @@ module Storage class LegacyProject attr_accessor :project - delegate :namespace, :gitlab_shell, :repository_storage_path, to: :project + delegate :namespace, :gitlab_shell, :repository_storage, to: :project def initialize(project) @project = project @@ -24,18 +24,18 @@ module Storage def ensure_storage_path_exists return unless namespace - gitlab_shell.add_namespace(repository_storage_path, base_dir) + gitlab_shell.add_namespace(repository_storage, base_dir) end def rename_repo new_full_path = project.build_full_path - if gitlab_shell.mv_repository(repository_storage_path, project.full_path_was, new_full_path) + if gitlab_shell.mv_repository(repository_storage, project.full_path_was, new_full_path) # If repository moved successfully we need to send update instructions to users. # However we cannot allow rollback since we moved repository # So we basically we mute exceptions in next actions begin - gitlab_shell.mv_repository(repository_storage_path, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki") + gitlab_shell.mv_repository(repository_storage, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki") return true rescue => e Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}" diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index fee5bc38f7b..4a99367c575 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -26,7 +26,7 @@ module Issues issue.update(closed_by: current_user) event_service.close_issue(issue, current_user) create_note(issue, commit) if system_note - notification_service.close_issue(issue, current_user) if notifications + notification_service.async.close_issue(issue, current_user) if notifications todo_service.close_issue(issue, current_user) execute_hooks(issue, 'close') invalidate_cache_counts(issue, users: issue.assignees) diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index 7140890d201..78e79344c99 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -139,7 +139,7 @@ module Issues end def notify_participants - notification_service.issue_moved(@old_issue, @new_issue, @current_user) + notification_service.async.issue_moved(@old_issue, @new_issue, @current_user) end end end diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index 62b4b4b6a1e..02224f3357a 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -6,7 +6,7 @@ module Issues if issue.reopen event_service.reopen_issue(issue, current_user) create_note(issue, 'reopened') - notification_service.reopen_issue(issue, current_user) + notification_service.async.reopen_issue(issue, current_user) execute_hooks(issue, 'reopen') invalidate_cache_counts(issue, users: issue.assignees) issue.update_project_counter_caches diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 1374f10c586..1000e1842b6 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -30,7 +30,7 @@ module Issues if issue.assignees != old_assignees create_assignee_note(issue, old_assignees) - notification_service.reassigned_issue(issue, current_user, old_assignees) + notification_service.async.reassigned_issue(issue, current_user, old_assignees) todo_service.reassigned_issue(issue, current_user, old_assignees) end @@ -41,13 +41,13 @@ module Issues added_labels = issue.labels - old_labels if added_labels.present? - notification_service.relabeled_issue(issue, added_labels, current_user) + notification_service.async.relabeled_issue(issue, added_labels, current_user) end added_mentions = issue.mentioned_users - old_mentioned_users if added_mentions.present? - notification_service.new_mentions_in_issue(issue, added_mentions, current_user) + notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user) end end diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index f727ec002e7..db701c1145d 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -10,7 +10,7 @@ module MergeRequests if merge_request.close create_event(merge_request) create_note(merge_request) - notification_service.close_mr(merge_request, current_user) + notification_service.async.close_mr(merge_request, current_user) todo_service.close_merge_request(merge_request, current_user) execute_hooks(merge_request, 'close') invalidate_cache_counts(merge_request, users: merge_request.assignees) diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index 120677a7149..8f1c95ac1b7 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -6,7 +6,7 @@ module MergeRequests if merge_request.reopen create_event(merge_request) create_note(merge_request, 'reopened') - notification_service.reopen_mr(merge_request, current_user) + notification_service.async.reopen_mr(merge_request, current_user) execute_hooks(merge_request, 'reopen') merge_request.reload_diff(current_user) merge_request.mark_as_unchecked diff --git a/app/services/merge_requests/resolved_discussion_notification_service.rb b/app/services/merge_requests/resolved_discussion_notification_service.rb index 3a09350c847..66a0cbc81d4 100644 --- a/app/services/merge_requests/resolved_discussion_notification_service.rb +++ b/app/services/merge_requests/resolved_discussion_notification_service.rb @@ -4,7 +4,7 @@ module MergeRequests return unless merge_request.discussions_resolved? SystemNoteService.resolve_all_discussions(merge_request, project, current_user) - notification_service.resolve_all_discussions(merge_request, current_user) + notification_service.async.resolve_all_discussions(merge_request, current_user) end end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 8a40ad88182..7350725e223 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -21,6 +21,7 @@ module MergeRequests update(merge_request) end + # rubocop:disable Metrics/AbcSize def handle_changes(merge_request, options) old_associations = options.fetch(:old_associations, {}) old_labels = old_associations.fetch(:labels, []) @@ -42,8 +43,11 @@ module MergeRequests end if merge_request.previous_changes.include?('assignee_id') + old_assignee_id = merge_request.previous_changes['assignee_id'].first + old_assignee = User.find(old_assignee_id) if old_assignee_id + create_assignee_note(merge_request) - notification_service.reassigned_merge_request(merge_request, current_user) + notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignee) todo_service.reassigned_merge_request(merge_request, current_user) end @@ -54,7 +58,7 @@ module MergeRequests added_labels = merge_request.labels - old_labels if added_labels.present? - notification_service.relabeled_merge_request( + notification_service.async.relabeled_merge_request( merge_request, added_labels, current_user @@ -63,13 +67,14 @@ module MergeRequests added_mentions = merge_request.mentioned_users - old_mentioned_users if added_mentions.present? - notification_service.new_mentions_in_merge_request( + notification_service.async.new_mentions_in_merge_request( merge_request, added_mentions, current_user ) end end + # rubocop:enable Metrics/AbcSize def merge_from_quick_action(merge_request) last_diff_sha = params.delete(:merge) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 274161df946..55a1735e54b 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -7,7 +7,32 @@ # Ex. # NotificationService.new.new_issue(issue, current_user) # +# When calculating the recipients of a notification is expensive (for instance, +# in the new issue case), `#async` will make that calculation happen in Sidekiq +# instead: +# +# NotificationService.new.async.new_issue(issue, current_user) +# class NotificationService + class Async + attr_reader :parent + delegate :respond_to_missing, to: :parent + + def initialize(parent) + @parent = parent + end + + def method_missing(meth, *args) + return super unless parent.respond_to?(meth) + + MailScheduler::NotificationServiceWorker.perform_async(meth.to_s, *args) + end + end + + def async + @async ||= Async.new(self) + end + # Always notify user about ssh key added # only if ssh key is not deploy key # @@ -142,8 +167,23 @@ class NotificationService # * merge_request assignee if their notification level is not Disabled # * users with custom level checked with "reassign merge request" # - def reassigned_merge_request(merge_request, current_user) - reassign_resource_email(merge_request, current_user, :reassigned_merge_request_email) + def reassigned_merge_request(merge_request, current_user, previous_assignee) + recipients = NotificationRecipientService.build_recipients( + merge_request, + current_user, + action: "reassign", + previous_assignee: previous_assignee + ) + + recipients.each do |recipient| + mailer.reassigned_merge_request_email( + recipient.user.id, + merge_request.id, + previous_assignee&.id, + current_user.id, + recipient.reason + ).deliver_later + end end # When we add labels to a merge request we should send an email to: @@ -421,29 +461,6 @@ class NotificationService end end - def reassign_resource_email(target, current_user, method) - previous_assignee_id = previous_record(target, 'assignee_id') - previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id - - recipients = NotificationRecipientService.build_recipients( - target, - current_user, - action: "reassign", - previous_assignee: previous_assignee - ) - - recipients.each do |recipient| - mailer.send( - method, - recipient.user.id, - target.id, - previous_assignee_id, - current_user.id, - recipient.reason - ).deliver_later - end - end - def relabeled_resource_email(target, labels, current_user, method) recipients = labels.flat_map { |l| l.subscribers(target.project) }.uniq recipients = notifiable_users( @@ -471,14 +488,6 @@ class NotificationService Notify end - def previous_record(object, attribute) - return unless object && attribute - - if object.previous_changes.include?(attribute) - object.previous_changes[attribute].first - end - end - private def recipients_for_pages_domain(domain) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 44e869851ca..71c93660b4b 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -91,7 +91,7 @@ module Projects project.run_after_commit do # self is now project - GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage_path, new_path) + GitlabShellWorker.perform_in(5.minutes, :remove_repository, self.repository_storage, new_path) end else false @@ -100,9 +100,9 @@ module Projects def mv_repository(from_path, to_path) # There is a possibility project does not have repository or wiki - return true unless gitlab_shell.exists?(project.repository_storage_path, from_path + '.git') + return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git') - gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path) + gitlab_shell.mv_repository(project.repository_storage, from_path, to_path) end def attempt_rollback(project, message) diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb index 67178de75de..68c1af2396b 100644 --- a/app/services/projects/hashed_storage/migrate_repository_service.rb +++ b/app/services/projects/hashed_storage/migrate_repository_service.rb @@ -47,8 +47,8 @@ module Projects private def move_repository(from_name, to_name) - from_exists = gitlab_shell.exists?(project.repository_storage_path, "#{from_name}.git") - to_exists = gitlab_shell.exists?(project.repository_storage_path, "#{to_name}.git") + from_exists = gitlab_shell.exists?(project.repository_storage, "#{from_name}.git") + to_exists = gitlab_shell.exists?(project.repository_storage, "#{to_name}.git") # If we don't find the repository on either original or target we should log that as it could be an issue if the # project was not originally empty. @@ -60,7 +60,7 @@ module Projects return true end - gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) + gitlab_shell.mv_repository(project.repository_storage, from_name, to_name) end def rollback_folder_move diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 5a23f0f0a62..61acdd58021 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -127,7 +127,7 @@ module Projects end def move_repo_folder(from_name, to_name) - gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) + gitlab_shell.mv_repository(project.repository_storage, from_name, to_name) end def execute_system_hooks diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index de77f6bf585..1d8caec9c6f 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,6 +1,6 @@ module Projects class UpdatePagesService < BaseService - InvaildStateError = Class.new(StandardError) + InvalidStateError = Class.new(StandardError) FailedToExtractError = Class.new(StandardError) BLOCK_SIZE = 32.kilobytes @@ -21,8 +21,8 @@ module Projects @status.enqueue! @status.run! - raise InvaildStateError, 'missing pages artifacts' unless build.artifacts? - raise InvaildStateError, 'pages are outdated' unless latest? + raise InvalidStateError, 'missing pages artifacts' unless build.artifacts? + raise InvalidStateError, 'pages are outdated' unless latest? # Create temporary directory in which we will extract the artifacts FileUtils.mkdir_p(tmp_path) @@ -31,16 +31,16 @@ module Projects # Check if we did extract public directory archive_public_path = File.join(archive_path, 'public') - raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) - raise InvaildStateError, 'pages are outdated' unless latest? + raise InvalidStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) + raise InvalidStateError, 'pages are outdated' unless latest? deploy_page!(archive_public_path) success end - rescue InvaildStateError => e + rescue InvalidStateError => e error(e.message) rescue => e - error(e.message, false) + error(e.message) raise e end @@ -48,17 +48,15 @@ module Projects def success @status.success - delete_artifact! super end - def error(message, allow_delete_artifact = true) + def error(message) register_failure log_error("Projects::UpdatePagesService: #{message}") @status.allow_failure = !latest? @status.description = message @status.drop(:script_failure) - delete_artifact! if allow_delete_artifact super end @@ -77,18 +75,18 @@ module Projects if artifacts.ends_with?('.zip') extract_zip_archive!(temp_path) else - raise InvaildStateError, 'unsupported artifacts format' + raise InvalidStateError, 'unsupported artifacts format' end end def extract_zip_archive!(temp_path) - raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata? + raise InvalidStateError, 'missing artifacts metadata' unless build.artifacts_metadata? # Calculate page size after extract public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) if public_entry.total_size > max_size - raise InvaildStateError, "artifacts for pages are too large: #{public_entry.total_size}" + raise InvalidStateError, "artifacts for pages are too large: #{public_entry.total_size}" end # Requires UnZip at least 6.00 Info-ZIP. @@ -162,11 +160,6 @@ module Projects build.artifacts_file.path end - def delete_artifact! - build.reload # Reload stable object to prevent erase artifacts with old state - build.erase_artifacts! unless build.has_expiring_artifacts? - end - def latest_sha project.commit(build.ref).try(:sha).to_s ensure diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb index 9a88459b511..ba7be4b3f89 100644 --- a/app/services/repository_archive_clean_up_service.rb +++ b/app/services/repository_archive_clean_up_service.rb @@ -20,11 +20,12 @@ class RepositoryArchiveCleanUpService private def clean_up_old_archives - run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete)) + run(%W(find #{path} -mindepth 1 -maxdepth 3 -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -mmin +#{mmin} -delete)) end def clean_up_empty_directories - run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete)) + run(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -empty -delete)) + run(%W(find #{path} -mindepth 1 -maxdepth 1 -type d -empty -delete)) end def run(cmd) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 958ef065012..00bf5434b7f 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -159,7 +159,7 @@ module SystemNoteService body = if noteable.time_estimate == 0 "removed time estimate" else - "changed time estimate to #{parsed_time}," + "changed time estimate to #{parsed_time}" end create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index 2ff4221efbd..badf3dd74b3 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -43,7 +43,7 @@ delete_user_url: admin_user_path(user), block_user_url: block_admin_user_path(user), username: user.name, - delete_contributions: 'false' }, type: 'button' } + delete_contributions: false }, type: 'button' } = s_('AdminUsers|Delete user') %li @@ -52,5 +52,5 @@ delete_user_url: admin_user_path(user, hard_delete: true), block_user_url: block_admin_user_path(user), username: user.name, - delete_contributions: 'true' }, type: 'button' } + delete_contributions: true }, type: 'button' } = s_('AdminUsers|Delete user and contributions') diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index ec3be869797..814ccdae8f3 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -183,7 +183,7 @@ delete_user_url: admin_user_path(@user), block_user_url: block_admin_user_path(@user), username: @user.name, - delete_contributions: 'false' }, type: 'button' } + delete_contributions: false }, type: 'button' } = s_('AdminUsers|Delete user') - else - if @user.solo_owned_groups.present? @@ -215,7 +215,7 @@ delete_user_url: admin_user_path(@user, hard_delete: true), block_user_url: block_admin_user_path(@user), username: @user.name, - delete_contributions: 'true' }, type: 'button' } + delete_contributions: true }, type: 'button' } = s_('AdminUsers|Delete user and contributions') - else %p diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml index 440623b34f5..571eb28f195 100644 --- a/app/views/ci/variables/_variable_row.html.haml +++ b/app/views/ci/variables/_variable_row.html.haml @@ -17,14 +17,14 @@ .ci-variable-row-body %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id } %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name } - %input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text", + %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control{ type: "text", name: key_input_name, value: key, placeholder: s_('CiVariables|Input variable key') } .ci-variable-body-item - .form-control.js-secret-value-placeholder{ class: ('hide' unless id) } + .form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) } = '*' * 20 - %textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id), + %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id), rows: 1, name: value_input_name, placeholder: s_('CiVariables|Input variable value') } diff --git a/app/views/peek/_bar.html.haml b/app/views/peek/_bar.html.haml index b4d86e1601c..a911449672b 100644 --- a/app/views/peek/_bar.html.haml +++ b/app/views/peek/_bar.html.haml @@ -5,8 +5,3 @@ peek_url: peek_routes.results_url, profile_url: url_for(params.merge(lineprofiler: 'true')) }, class: Peek.env } - -#peek-view-performance-bar.hidden - = render_server_response_time - %span#serverstats - %ul.performance-bar diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index d0c01f95cb7..0e012b5a216 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -8,18 +8,17 @@ %li{ class: "branch-item js-branch-#{branch.name}" } .branch-info .branch-title - = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name' do - = sprite_icon('fork', size: 12) + = sprite_icon('fork', size: 12) + = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do = branch.name - - if branch.name == @repository.root_ref - %span.label.label-primary default + %span.label.label-primary.prepend-left-5 default - elsif merged - %span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } + %span.label.label-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } = s_('Branches|merged') - if protected_branch?(@project, branch) - %span.label.label-success + %span.label.label-success.prepend-left-5 = s_('Branches|protected') .block-truncated diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 5377d745371..24d2b971472 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -1,4 +1,4 @@ - can_admin_project = can?(current_user, :admin_project, @project) = render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do - = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} + = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml index 12ccae10260..24b53555cdc 100644 --- a/app/views/projects/protected_branches/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml @@ -1,8 +1,8 @@ - content_for :merge_access_levels do .merge_access_levels-container = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-merge wide', - dropdown_class: 'dropdown-menu-selectable capitalize-header', + options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge-select wide', + dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown capitalize-header', data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }}) - content_for :push_access_levels do .push_access_levels-container diff --git a/app/views/projects/protected_branches/_update_protected_branch.html.haml b/app/views/projects/protected_branches/_update_protected_branch.html.haml index 98363f2018a..f242459f69b 100644 --- a/app/views/projects/protected_branches/_update_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_update_protected_branch.html.haml @@ -1,7 +1,7 @@ %td = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_levels.first.access_level = dropdown_tag( (protected_branch.merge_access_levels.first.humanize || 'Select') , - options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header', + options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header', data: { field_name: "allowed_to_merge_#{protected_branch.id}", access_level_id: protected_branch.merge_access_levels.first.id }}) %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml index f5b21f0e887..2d3b2af00c2 100644 --- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -21,4 +21,4 @@ - if can_admin_project %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning" 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 7b410101c05..71e77dae69e 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -36,5 +36,6 @@ = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' .help-block = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank' = f.submit 'Save changes', class: "btn btn-success prepend-top-15" diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 9aea3bad27b..c469aea7052 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -41,6 +41,7 @@ - github_importer:github_import_stage_import_repository - mail_scheduler:mail_scheduler_issue_due +- mail_scheduler:mail_scheduler_notification_service - object_storage_upload - object_storage:object_storage_background_move diff --git a/app/workers/concerns/mail_scheduler_queue.rb b/app/workers/concerns/mail_scheduler_queue.rb index 9df55ad9522..f3e9680d756 100644 --- a/app/workers/concerns/mail_scheduler_queue.rb +++ b/app/workers/concerns/mail_scheduler_queue.rb @@ -4,4 +4,8 @@ module MailSchedulerQueue included do queue_namespace :mail_scheduler end + + def notification_service + @notification_service ||= NotificationService.new + end end diff --git a/app/workers/mail_scheduler/issue_due_worker.rb b/app/workers/mail_scheduler/issue_due_worker.rb index b06079d68ca..54285884a52 100644 --- a/app/workers/mail_scheduler/issue_due_worker.rb +++ b/app/workers/mail_scheduler/issue_due_worker.rb @@ -4,8 +4,6 @@ module MailScheduler include MailSchedulerQueue def perform(project_id) - notification_service = NotificationService.new - Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue| notification_service.issue_due(issue) end diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb new file mode 100644 index 00000000000..7cfe0aa0df1 --- /dev/null +++ b/app/workers/mail_scheduler/notification_service_worker.rb @@ -0,0 +1,19 @@ +require 'active_job/arguments' + +module MailScheduler + class NotificationServiceWorker + include ApplicationWorker + include MailSchedulerQueue + + def perform(meth, *args) + deserialized_args = ActiveJob::Arguments.deserialize(args) + + notification_service.public_send(meth, *deserialized_args) # rubocop:disable GitlabSecurity/PublicSend + rescue ActiveJob::DeserializationError + end + + def self.perform_async(*args) + super(*ActiveJob::Arguments.serialize(args)) + end + end +end diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index 51fad4faf36..08b1c3a7d7a 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -13,7 +13,9 @@ class RepositoryForkWorker # See https://gitlab.com/gitlab-org/gitaly/issues/1110 if args.empty? source_project = target_project.forked_from_project - return target_project.mark_import_as_failed('Source project cannot be found.') unless source_project + unless source_project + return target_project.mark_import_as_failed('Source project cannot be found.') + end fork_repository(target_project, source_project.repository_storage, source_project.disk_path) else diff --git a/changelogs/unreleased/45451-user-deletion-modal-with-same-info-for-delete-user-or-delete-user-and-contributions.yml b/changelogs/unreleased/45451-user-deletion-modal-with-same-info-for-delete-user-or-delete-user-and-contributions.yml new file mode 100644 index 00000000000..707a18745c8 --- /dev/null +++ b/changelogs/unreleased/45451-user-deletion-modal-with-same-info-for-delete-user-or-delete-user-and-contributions.yml @@ -0,0 +1,6 @@ +--- +title: Correct text and functionality for delete user / delete user and contributions + modal. +merge_request: 18463 +author: Marc Schwede +type: fixed diff --git a/changelogs/unreleased/45481-sane-pages-artifacts.yml b/changelogs/unreleased/45481-sane-pages-artifacts.yml new file mode 100644 index 00000000000..b9c68b70012 --- /dev/null +++ b/changelogs/unreleased/45481-sane-pages-artifacts.yml @@ -0,0 +1,6 @@ +--- +title: Don't automatically remove artifacts for pages jobs after pages:deploy has + run +merge_request: 18628 +author: +type: fixed diff --git a/changelogs/unreleased/45572-members-invitations-scheduled-before-commit.yml b/changelogs/unreleased/45572-members-invitations-scheduled-before-commit.yml new file mode 100644 index 00000000000..7cdea436d47 --- /dev/null +++ b/changelogs/unreleased/45572-members-invitations-scheduled-before-commit.yml @@ -0,0 +1,5 @@ +--- +title: Ensure member notifications are sent after the member actual creation/update in the DB +merge_request: 18538 +author: +type: fixed diff --git a/changelogs/unreleased/add-jwt-strategy-to-gitlab-suite.yml b/changelogs/unreleased/add-jwt-strategy-to-gitlab-suite.yml new file mode 100644 index 00000000000..22a839cef56 --- /dev/null +++ b/changelogs/unreleased/add-jwt-strategy-to-gitlab-suite.yml @@ -0,0 +1,5 @@ +--- +title: Ports omniauth-jwt gem onto GitLab OmniAuth Strategies suite +merge_request: 18580 +author: +type: fixed diff --git a/changelogs/unreleased/bvl-fix-openid-redirect.yml b/changelogs/unreleased/bvl-fix-openid-redirect.yml new file mode 100644 index 00000000000..83ee6d953e4 --- /dev/null +++ b/changelogs/unreleased/bvl-fix-openid-redirect.yml @@ -0,0 +1,5 @@ +--- +title: Fix redirection error for applications using OpenID +merge_request: 18599 +author: +type: fixed diff --git a/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml b/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml new file mode 100644 index 00000000000..6e2273ed9af --- /dev/null +++ b/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml @@ -0,0 +1,5 @@ +--- +title: For group dashboard, we no longer show groups which the visitor is not a member of (this applies to admins and auditors) +merge_request: 17884 +author: Roger Rüttimann +type: changed diff --git a/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml b/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml new file mode 100644 index 00000000000..fb6dffaf226 --- /dev/null +++ b/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml @@ -0,0 +1,5 @@ +--- +title: Fixed inconsistent protected branch pill baseline +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/jramsay-44880-filter-pipelines-by-sha.yml b/changelogs/unreleased/jramsay-44880-filter-pipelines-by-sha.yml new file mode 100644 index 00000000000..3654aa28ff4 --- /dev/null +++ b/changelogs/unreleased/jramsay-44880-filter-pipelines-by-sha.yml @@ -0,0 +1,5 @@ +--- +title: Add sha filter to pipelines list API +merge_request: 18125 +author: +type: changed diff --git a/changelogs/unreleased/move-notification-service-calls-to-sidekiq.yml b/changelogs/unreleased/move-notification-service-calls-to-sidekiq.yml new file mode 100644 index 00000000000..b2517884d3c --- /dev/null +++ b/changelogs/unreleased/move-notification-service-calls-to-sidekiq.yml @@ -0,0 +1,5 @@ +--- +title: Compute notification recipients in background jobs +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/performance-gb-improve-pipeline-creation-service.yml b/changelogs/unreleased/performance-gb-improve-pipeline-creation-service.yml new file mode 100644 index 00000000000..bd308f37bec --- /dev/null +++ b/changelogs/unreleased/performance-gb-improve-pipeline-creation-service.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance of a service responsible for creating a pipeline +merge_request: 18582 +author: +type: performance diff --git a/changelogs/unreleased/restore-size-and-position-for-fork-icon.yml b/changelogs/unreleased/restore-size-and-position-for-fork-icon.yml new file mode 100644 index 00000000000..dd8dad0b17d --- /dev/null +++ b/changelogs/unreleased/restore-size-and-position-for-fork-icon.yml @@ -0,0 +1,5 @@ +--- +title: Fix size and position for fork icon +merge_request: 18449 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/revert-discussion-counter-height.yml b/changelogs/unreleased/revert-discussion-counter-height.yml new file mode 100644 index 00000000000..331ff997009 --- /dev/null +++ b/changelogs/unreleased/revert-discussion-counter-height.yml @@ -0,0 +1,5 @@ +--- +title: Revert discussion counter height +merge_request: 18656 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml b/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml new file mode 100644 index 00000000000..0103a7fc430 --- /dev/null +++ b/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml @@ -0,0 +1,5 @@ +--- +title: Serve archive requests with the correct file in all cases +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security_issue_42029.yml b/changelogs/unreleased/security_issue_42029.yml new file mode 100644 index 00000000000..0772e33f930 --- /dev/null +++ b/changelogs/unreleased/security_issue_42029.yml @@ -0,0 +1,5 @@ +--- +title: Sanitizes user name to avoid XSS attacks +merge_request: +author: +type: security diff --git a/changelogs/unreleased/update-timeline-icon-for-description-edit.yml b/changelogs/unreleased/update-timeline-icon-for-description-edit.yml new file mode 100644 index 00000000000..560db00e503 --- /dev/null +++ b/changelogs/unreleased/update-timeline-icon-for-description-edit.yml @@ -0,0 +1,5 @@ +--- +title: Update timeline icon for description edit +merge_request: 18633 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/winh-dashboard-any-milestone.yml b/changelogs/unreleased/winh-dashboard-any-milestone.yml new file mode 100644 index 00000000000..49eecd3da2b --- /dev/null +++ b/changelogs/unreleased/winh-dashboard-any-milestone.yml @@ -0,0 +1,5 @@ +--- +title: Reset milestone filter when clicking "Any Milestone" in dashboard +merge_request: 18531 +author: +type: fixed diff --git a/changelogs/unreleased/zj-repository-exist-mandatory.yml b/changelogs/unreleased/zj-repository-exist-mandatory.yml new file mode 100644 index 00000000000..7d83446e90f --- /dev/null +++ b/changelogs/unreleased/zj-repository-exist-mandatory.yml @@ -0,0 +1,5 @@ +--- +title: Repository#exists? is always executed through Gitaly +merge_request: +author: +type: performance diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 8c39a1f2aa9..7eb44b8059e 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -184,18 +184,18 @@ production: &base # base_dir: uploads/-/system object_store: enabled: false - # remote_directory: uploads # Bucket name + remote_directory: uploads # Bucket name # direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false) # background_upload: false # Temporary option to limit automatic upload (Default: true) # proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage - connection: - provider: AWS - aws_access_key_id: AWS_ACCESS_KEY_ID - aws_secret_access_key: AWS_SECRET_ACCESS_KEY - region: us-east-1 - # host: 'localhost' # default: s3.amazonaws.com - # endpoint: 'http://127.0.0.1:9000' # default: nil - # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' + connection: + provider: AWS + aws_access_key_id: AWS_ACCESS_KEY_ID + aws_secret_access_key: AWS_SECRET_ACCESS_KEY + region: us-east-1 + # host: 'localhost' # default: s3.amazonaws.com + # endpoint: 'http://127.0.0.1:9000' # default: nil + # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' ## GitLab Pages pages: @@ -212,6 +212,8 @@ production: &base artifacts_server: true # external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages # external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages + admin: + address: unix:/home/git/gitlab/tmp/sockets/private/pages-admin.socket # TCP connections are supported too (e.g. tcp://host:port) ## Mattermost ## For enabling Add to Mattermost button @@ -532,7 +534,7 @@ production: &base # required_claims: ["name", "email"], # info_map: { name: "name", email: "email" }, # auth_url: 'https://example.com/', - # valid_within: nil, + # valid_within: null, # } # } # - { name: 'saml', @@ -823,7 +825,7 @@ test: required_claims: ["name", "email"], info_map: { name: "name", email: "email" }, auth_url: 'https://example.com/', - valid_within: nil, + valid_within: null, } } - { name: 'auth0', diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 575f27d1ea9..5248bd858a0 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -215,6 +215,9 @@ Settings.pages['external_http'] ||= false unless Settings.pages['external_ht Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present? Settings.pages['artifacts_server'] ||= Settings.pages['enabled'] if Settings.pages['artifacts_server'].nil? +Settings.pages['admin'] ||= Settingslogic.new({}) +Settings.pages.admin['certificate'] ||= '' + # # Git LFS # diff --git a/config/initializers/fast_gettext.rb b/config/initializers/9_fast_gettext.rb index fd0167aa476..fd0167aa476 100644 --- a/config/initializers/fast_gettext.rb +++ b/config/initializers/9_fast_gettext.rb diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 2079d3acb72..e3a342590d4 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -104,5 +104,5 @@ Doorkeeper.configure do # set to true if you want this to be allowed # wildcard_redirect_uri false - base_controller 'ApplicationController' + base_controller '::Gitlab::BaseDoorkeeperController' end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 00baea08613..e33ebb25c4c 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -25,5 +25,6 @@ end module OmniAuth module Strategies autoload :Bitbucket, Rails.root.join('lib', 'omni_auth', 'strategies', 'bitbucket') + autoload :Jwt, Rails.root.join('lib', 'omni_auth', 'strategies', 'jwt') end end diff --git a/config/initializers/pages.rb b/config/initializers/pages.rb new file mode 100644 index 00000000000..835197557e8 --- /dev/null +++ b/config/initializers/pages.rb @@ -0,0 +1,2 @@ +Gitlab::PagesClient.read_or_create_token +Gitlab::PagesClient.load_certificate diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb index ba04a2bf5fa..bc9b52ceef7 100644 --- a/config/initializers/peek.rb +++ b/config/initializers/peek.rb @@ -1,7 +1,6 @@ Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) } Peek.into Peek::Views::Host -Peek.into Peek::Views::PerformanceBar if Gitlab::Database.mysql? require 'peek-mysql2' diff --git a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb index bddc234db25..17357b67ab7 100644 --- a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb +++ b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb @@ -59,17 +59,17 @@ class RemoveDotGitFromGroupNames < ActiveRecord::Migration end def move_namespace(group_id, path_was, path) - repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row| - Gitlab.config.repositories.storages[row['repository_storage']].legacy_disk_path + repository_storages = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row| + row['repository_storage'] end.compact # Move the namespace directory in all storages paths used by member projects - repository_storage_paths.each do |repository_storage_path| + repository_storages.each do |repository_storage| # Ensure old directory exists before moving it - gitlab_shell.add_namespace(repository_storage_path, path_was) + gitlab_shell.add_namespace(repository_storage, path_was) - unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) - Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}" + unless gitlab_shell.mv_namespace(repository_storage, path_was, path) + Rails.logger.error "Exception moving on shard #{repository_storage} from #{path_was} to #{path}" # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb index 7c28d934c29..8986cd8cb4b 100644 --- a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb +++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb @@ -53,8 +53,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present? end - def path_exists?(path, repository_storage_path) - repository_storage_path && gitlab_shell.exists?(repository_storage_path, path) + def path_exists?(shard, repository_storage_path) + repository_storage_path && gitlab_shell.exists?(shard, repository_storage_path) end # Accepts invalid path like test.git and returns test_git or @@ -70,8 +70,8 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration def check_routes(base, counter, path) route_exists = route_exists?(path) - Gitlab.config.repositories.storages.each_value do |storage| - if route_exists || path_exists?(path, storage.legacy_disk_path) + Gitlab.config.repositories.storages.each do |shard, storage| + if route_exists || path_exists?(shard, storage.legacy_disk_path) counter += 1 path = "#{base}#{counter}" @@ -83,17 +83,17 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration end def move_namespace(namespace_id, path_was, path) - repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row| - Gitlab.config.repositories.storages[row['repository_storage']].legacy_disk_path + repository_storages = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row| + row['repository_storage'] end.compact - # Move the namespace directory in all storages paths used by member projects - repository_storage_paths.each do |repository_storage_path| + # Move the namespace directory in all storages used by member projects + repository_storages.each do |repository_storage| # Ensure old directory exists before moving it - gitlab_shell.add_namespace(repository_storage_path, path_was) + gitlab_shell.add_namespace(repository_storage, path_was) - unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) - Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}" + unless gitlab_shell.mv_namespace(repository_storage, path_was, path) + Rails.logger.error "Exception moving on shard #{repository_storage} from #{path_was} to #{path}" # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs diff --git a/doc/README.md b/doc/README.md index a841a4cfbf1..a2e152ce383 100644 --- a/doc/README.md +++ b/doc/README.md @@ -15,8 +15,8 @@ To understand what features you have access to, check the [GitLab subscriptions] | General documentation | GitLab CI/CD docs | | :----- | :----- | -| [User documentation](user/index.md) | [GitLab CI/CD](ci/README.md) | -| [Administrator documentation](administration/index.md) | [GitLab CI/CD quick start guide](ci/quick_start/README.md) | +| [User documentation](user/index.md) | [GitLab CI/CD quick start guide](ci/quick_start/README.md) | +| [Administrator documentation](administration/index.md) | [GitLab CI/CD examples](ci/examples/README.md) | | [Contributor documentation](#contributor-documentation) | [Configuring `.gitlab-ci.yml`](ci/yaml/README.md) | | [Getting started with GitLab](#getting-started-with-gitlab) | [Using Docker images](ci/docker/using_docker_images.md) | | [API](api/README.md) | [Auto DevOps](topics/autodevops/index.md) | @@ -90,6 +90,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i - [Create a file](user/project/repository/web_editor.md#create-a-file) - [Upload a file](user/project/repository/web_editor.md#upload-a-file) - [File templates](user/project/repository/web_editor.md#template-dropdowns) + - [Jupyter Notebook files](user/project/repository/index.md#jupyter-notebook-files) - [Create a directory](user/project/repository/web_editor.md#create-a-directory) - [Start a merge request](user/project/repository/web_editor.md#tips) (when committing via UI) - [Branches](user/project/repository/branches/index.md) @@ -100,6 +101,14 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i - [Commits](user/project/repository/index.md#commits) - [Signing commits](user/project/repository/gpg_signed_commits/index.md): use GPG to sign your commits. +#### Merge Requests + +- [Merge Requests](user/project/merge_requests/index.md) + - [Work In Progress "WIP" Merge Requests](user/project/merge_requests/work_in_progress_merge_requests.md) + - [Merge Request discussion resolution](user/discussions/index.md#moving-a-single-discussion-to-a-new-issue): Resolve discussions, move discussions in a merge request to an issue, only allow merge requests to be merged if all discussions are resolved. + - [Checkout merge requests locally](user/project/merge_requests/index.md#checkout-merge-requests-locally) + - [Cherry-pick](user/project/merge_requests/cherry_pick_changes.md) + #### Integrations - [Project Services](user/project/integrations/project_services.md): Integrate a project with external services, such as CI and chat. @@ -113,18 +122,16 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i ### Verify -Spot errors sooner and shorten feedback cycles with built-in code review, code testing, -Code Quality, and Review Apps. Customize your approval workflow controls, automatically -test the quality of your code, and spin up a staging environment for every code change. -GitLab Continuous Integration is the most popular next generation testing system that -auto scales to run your tests faster. +Spot errors sooner, improve security and shorten feedback cycles with built-in +static code analysis, code testing, code quality, dependency checking and review +apps. Customize your approval workflow controls, automatically test the quality +of your code, and spin up a staging environment for every code change. GitLab +Continuous Integration is the most popular next generation testing system that +scales to run your tests faster. -- [Merge Requests](user/project/merge_requests/index.md) - - [Work In Progress Merge Requests](user/project/merge_requests/work_in_progress_merge_requests.md) - - [Merge Request discussion resolution](user/discussions/index.md#moving-a-single-discussion-to-a-new-issue): Resolve discussions, move discussions in a merge request to an issue, only allow merge requests to be merged if all discussions are resolved. - - [Checkout merge requests locally](user/project/merge_requests/index.md#checkout-merge-requests-locally) - - [Cherry-pick](user/project/merge_requests/cherry_pick_changes.md) +- [GitLab CI/CD](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab. - [Review Apps](ci/review_apps/index.md): Preview changes to your app right from a merge request. +- [Pipeline Graphs](ci/pipelines.md#pipeline-graphs) ### Package @@ -132,7 +139,6 @@ GitLab Container Registry gives you the enhanced security and access controls of custom Docker images without 3rd party add-ons. Easily upload and download images from GitLab CI/CD with full Git repository management integration. -- [GitLab CI/CD](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab. - [GitLab Container Registry](user/project/container_registry.md): Learn how to use GitLab's built-in Container Registry. ### Release @@ -141,9 +147,11 @@ Spend less time configuring your tools, and more time creating. Whether you’re deploying to one server or thousands, build, test, and release your code confidently and securely with GitLab’s built-in Continuous Delivery and Deployment. -- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy a static site directly from GitLab. - [Auto Deploy](topics/autodevops/index.md#auto-deploy): Configure GitLab CI for the deployment of your application. - [Environments and deployments](ci/environments.md): With environments, you can control the continuous deployment of your software within GitLab. +- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy a static site directly from GitLab. +- [Scheduled Pipelines](user/project/pipelines/schedules.md) +- [Protected Runners](ci/runners/README.md#protected-runners) ### Configure @@ -152,6 +160,9 @@ Auto Devops. Best practice templates get you started with minimal to zero configuration. Then customize everything from buildpacks to CI/CD. - [Auto DevOps](topics/autodevops/index.md) +- [Deployment of Helm, Ingress, and Prometheus on Kubernetes](user/project/clusters/index.md#installing-applications) +- [Protected secret variables](ci/variables/README.md#protected-secret-variables) +- [Easy creation of Kubernetes clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) ### Monitor diff --git a/doc/administration/auth/jwt.md b/doc/administration/auth/jwt.md index b51e705ab52..8b00f52ffc1 100644 --- a/doc/administration/auth/jwt.md +++ b/doc/administration/auth/jwt.md @@ -50,7 +50,7 @@ JWT will provide you with a secret key for you to use. required_claims: ["name", "email"], info_map: { name: "name", email: "email" }, auth_url: 'https://example.com/', - valid_within: nil, + valid_within: null, } } ``` diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md index 430f865f1e7..031fb31ca4f 100644 --- a/doc/administration/high_availability/redis.md +++ b/doc/administration/high_availability/redis.md @@ -323,7 +323,7 @@ The prerequisites for a HA Redis setup are the following: # machines to connect to it. redis['port'] = 6379 - # The same password for Redeis authentication you set up for the master node. + # The same password for Redis authentication you set up for the master node. redis['password'] = 'redis-password-goes-here' # The IP of the master Redis node. diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 896cb93e5ed..77fe4d561a1 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -107,7 +107,7 @@ For source installations the following settings are nested under `artifacts:` an | Setting | Description | Default | |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | -| `remote_directory` | The bucket name where Artfacts will be stored| | +| `remote_directory` | The bucket name where Artifacts will be stored| | | `direct_upload` | Set to true to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. Currently only `Google` provider is supported | `false` | | `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | | `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | @@ -148,7 +148,7 @@ _The artifacts are stored by default in ``` NOTE: For GitLab 9.4+, if you are using AWS IAM profiles, be sure to omit the - AWS access key and secret acces key/value pairs. For example: + AWS access key and secret access key/value pairs. For example: ```ruby gitlab_rails['artifacts_object_store_connection'] = { diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index f495990d9a4..69600cad25c 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -46,7 +46,7 @@ In this experimental phase, only a few metrics are available: | redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | | user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | | filesystem_circuitbreaker_latency_seconds | Gauge | 9.5 | Time spent validating if a storage is accessible | -| filesystem_circuitbreaker | Gauge | 9.5 | Wether or not the circuit for a certain shard is broken or not | +| filesystem_circuitbreaker | Gauge | 9.5 | Whether or not the circuit for a certain shard is broken or not | | circuitbreaker_storage_check_duration_seconds | Histogram | 10.3 | Time a single storage probe took | ## Metrics shared directory diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md index bd6c7bb07b5..89331238ce4 100644 --- a/doc/administration/operations/fast_ssh_key_lookup.md +++ b/doc/administration/operations/fast_ssh_key_lookup.md @@ -31,7 +31,7 @@ GitLab Shell provides a way to authorize SSH users via a fast, indexed lookup to the GitLab database. GitLab Shell uses the fingerprint of the SSH key to check whether the user is authorized to access GitLab. -Add the following to your `sshd_config` file. This is usuaully located at +Add the following to your `sshd_config` file. This is usually located at `/etc/ssh/sshd_config`, but it will be `/assets/sshd_config` if you're using Omnibus Docker: diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md index 2fa3284b6be..7f0bd8f04e3 100644 --- a/doc/administration/uploads.md +++ b/doc/administration/uploads.md @@ -104,7 +104,7 @@ _The uploads are stored by default in ``` >**Note:** -If you are using AWS IAM profiles, be sure to omit the AWS access key and secret acces key/value pairs. +If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs. ```ruby gitlab_rails['uploads_object_store_connection'] = { diff --git a/doc/api/README.md b/doc/api/README.md index ae4481b400e..e777fc63d2b 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -13,6 +13,7 @@ following locations: - [Broadcast Messages](broadcast_messages.md) - [Project-level Variables](project_level_variables.md) - [Group-level Variables](group_level_variables.md) +- [Code Snippets](snippets.md) - [Commits](commits.md) - [Custom Attributes](custom_attributes.md) - [Deployments](deployments.md) @@ -85,6 +86,29 @@ have been resolved to our satisfaction by the relicensing of the reference implementations under MIT, and the use of the OWF license for the GraphQL specification. +## Compatibility Guidelines + +The HTTP API is versioned using a single number, the current one being 4. This +number symbolises the same as the major version number as described by +[SemVer](https://semver.org/). This mean that backward incompatible changes +will require this version number to change. However, the minor version is +not explicit. This allows for a stable API endpoint, but also means new +features can be added to the API in the same version number. + +New features and bug fixes are released in tandem with a new GitLab, and apart +from incidental patch and security releases, are released on the 22nd each +month. Backward incompatible changes (e.g. endpoints removal, parameters +removal etc.), as well as removal of entire API versions are done in tandem +with a major point release of GitLab itself. All deprecations and changes +between two versions should be listed in the documentation. For the changes +between v3 and v4; please read the [v3 to v4 documentation](v3_to_v4.md) + +#### Current status + +Currently two API versions are available, v3 and v4. v3 is deprecated and +will soon be removed. Deletion is scheduled for +[GitLab 11.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/36819). + ## Basic usage API requests should be prefixed with `api` and the API version. The API version @@ -269,7 +293,7 @@ The following table gives an overview of how the API functions generally behave. | `GET` | Access one or more resources and return the result as JSON. | | `POST` | Return `201 Created` if the resource is successfully created and return the newly created resource as JSON. | | `GET` / `PUT` | Return `200 OK` if the resource is accessed or modified successfully. The (modified) result is returned as JSON. | -| `DELETE` | Returns `204 No Content` if the resuource was deleted successfully. | +| `DELETE` | Returns `204 No Content` if the resource was deleted successfully. | The following table shows the possible return codes for API requests. diff --git a/doc/api/group_badges.md b/doc/api/group_badges.md index 0d7d0fd9c42..f2353542a5c 100644 --- a/doc/api/group_badges.md +++ b/doc/api/group_badges.md @@ -12,7 +12,7 @@ Badges support placeholders that will be replaced in real time in both the link - **%{default_branch}**: will be replaced by the project default branch. - **%{commit_sha}**: will be replaced by the last project's commit sha. -Because these enpoints aren't inside a project's context, the information used to replace the placeholders will be +Because these endpoints aren't inside a project's context, the information used to replace the placeholders will be from the first group's project by creation date. If the group hasn't got any project the original URL with the placeholders will be returned. ## List all badges of a group diff --git a/doc/api/groups.md b/doc/api/groups.md index 1aed8aac64e..923fd662a5b 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -10,7 +10,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | @@ -94,7 +94,7 @@ Parameters: | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) of the parent group | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | diff --git a/doc/api/pipeline_schedules.md b/doc/api/pipeline_schedules.md index c28f48e5fc6..137f1fdddec 100644 --- a/doc/api/pipeline_schedules.md +++ b/doc/api/pipeline_schedules.md @@ -108,7 +108,7 @@ POST /projects/:id/pipeline_schedules | `description` | string | yes | The description of pipeline schedule | | `ref` | string | yes | The branch/tag name will be triggered | | `cron ` | string | yes | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) | -| `cron_timezone ` | string | no | The timezone supproted by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) (default: `'UTC'`) | +| `cron_timezone ` | string | no | The timezone supported by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) (default: `'UTC'`) | | `active ` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially (default: `true`) | ```sh @@ -153,7 +153,7 @@ PUT /projects/:id/pipeline_schedules/:pipeline_schedule_id | `description` | string | no | The description of pipeline schedule | | `ref` | string | no | The branch/tag name will be triggered | | `cron ` | string | no | The cron (e.g. `0 1 * * *`) ([Cron syntax](https://en.wikipedia.org/wiki/Cron)) | -| `cron_timezone ` | string | no | The timezone supproted by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) or `TZInfo::Timezone` (e.g. `America/Los_Angeles`) | +| `cron_timezone ` | string | no | The timezone supported by `ActiveSupport::TimeZone` (e.g. `Pacific Time (US & Canada)`) or `TZInfo::Timezone` (e.g. `America/Los_Angeles`) | | `active ` | boolean | no | The activation of pipeline schedule. If false is set, the pipeline schedule will deactivated initially. | ```sh diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index a6631cab8c3..899f5da6647 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -14,6 +14,7 @@ GET /projects/:id/pipelines | `scope` | string | no | The scope of pipelines, one of: `running`, `pending`, `finished`, `branches`, `tags` | | `status` | string | no | The status of pipelines, one of: `running`, `pending`, `success`, `failed`, `canceled`, `skipped` | | `ref` | string | no | The ref of pipelines | +| `sha` | string | no | The sha or pipelines | | `yaml_errors`| boolean | no | Returns pipelines with invalid configurations | | `name`| string | no | The name of the user who triggered pipelines | | `username`| string | no | The username of the user who triggered pipelines | diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md index bfc8558a580..3d21c0cc306 100644 --- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md @@ -509,7 +509,7 @@ and unit tests, all running and deployed at every push to master - with shocking Errors can be easily debugged through GitLab's build logs, and within minutes of a successful commit, you can see the changes live on your game. -Setting up Continous Integration and Continuous Deployment from the start with Dark Nova enables +Setting up Continuous Integration and Continuous Deployment from the start with Dark Nova enables rapid but stable development. We can easily test changes in a separate [environment](../../../ci/environments.md#introduction-to-environments-and-deployments), or multiple environments if needed. Balancing and updating a multiplayer game can be ongoing and tedious, but having faith in a stable deployment with GitLab CI/CD allows diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md index 7f6519fd38e..a2de0408797 100644 --- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md @@ -30,7 +30,7 @@ and GitLab UI._ Many components and concepts are similar to Ruby on Rails or Python's Django. High developer productivity and high application performance are only a few advantages on learning how to use it. -Working on the MVC pattern, it's was designed to be modular and flexible. Easy to mantain a growing +Working on the MVC pattern, it's was designed to be modular and flexible. Easy to maintain a growing app is a plus. Phoenix can run in any OS where Erlang is supported: @@ -48,7 +48,7 @@ Check the [Phoenix learning guide][phoenix-learning-guide] for more information. ### What is Elixir? [Elixir][elixir-site] is a dynamic, functional language created to use all the maturity of Erlang -(30 years old!) in these days, in an easy way. It has similarities with Ruby, specially on sintax, +(30 years old!) in these days, in an easy way. It has similarities with Ruby, specially on syntax, so Ruby developers are quite excited with the rapid growing of Elixir. A full-stack Ruby developer can learn how to use Elixir and Phoenix in just a few weeks! @@ -162,7 +162,7 @@ productive, because every time we, or our co-workers push any code, GitLab CI/CD test the changes, telling us in realtime if anything goes wrong. Certainly, when our application starts to grow, we'll need more developers working on the same -project and this process of building and testing can easely become a mess without proper management. +project and this process of building and testing can easily become a mess without proper management. That's also why GitLab CI/CD is so important to our application. Every time someone pushes its code to GitLab, we'll quickly know if their changes broke something or not. We don't need to stop everything we're doing to test manually and locally every change our team does. @@ -237,7 +237,7 @@ Finished in 0.7 seconds Randomized with seed 610000 ``` -Our test was successfull. It's time to push our files to GitLab. +Our test was successful. It's time to push our files to GitLab. ## Configuring CI/CD Pipeline @@ -302,7 +302,7 @@ template** and select **Elixir**: ``` It's important to install `postgresql-client` to let GitLab CI/CD access PostgreSQL and create our - database with the login information provided earlier. More important is to respect the identation, + database with the login information provided earlier. More important is to respect the indentation, to avoid syntax errors when running the build. - And finally, we'll let `mix` session intact. @@ -333,7 +333,7 @@ mix: - mix test ``` -For safety, we can check if we get any syntax errors before submiting this file to GitLab. Copy the +For safety, we can check if we get any syntax errors before submitting this file to GitLab. Copy the contents of `.gitlab-ci.yml` and paste it on [GitLab CI/CD Lint tool][ci-lint]. Please note that this link will only work for logged in users. @@ -384,7 +384,7 @@ working properly. When we have a growing application with many developers working on it, or when we have an open source project being watched and contributed by the community, it is really important to have our -code permanently working. GitLab CI/CD is a time saving powerfull tool to help us mantain our code +code permanently working. GitLab CI/CD is a time saving powerful tool to help us maintain our code organized and working. As we could see in this post, GitLab CI/CD is really really easy to configure and use. We have [many diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index f0df8b96cab..38a988f4507 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -61,7 +61,7 @@ future GitLab releases.** | **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | | **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | -| **CI_PIPELINE_SOURCE** | 10.0 | all | The source for this pipeline, one of: push, web, trigger, schedule, api, external. Pipelines created before 9.5 will have unknown as source | +| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally | | **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) | @@ -551,7 +551,7 @@ You can find a full list of unsupported variables below: - `CI_DEPLOY_USER` - `CI_DEPLOY_PASSWORD` -These variables are also not supported in a contex of a +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" diff --git a/doc/development/README.md b/doc/development/README.md index 45e9565f9a7..3c77e99b8cf 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -39,9 +39,9 @@ comments: false - [Sidekiq debugging](sidekiq_debugging.md) - [Gotchas](gotchas.md) to avoid - [Avoid modules with instance variables](module_with_instance_variables.md) if possible -- [Issue and merge requests state models](object_state_models.md) - [How to dump production data to staging](db_dump.md) - [Working with the GitHub importer](github_importer.md) +- [Working with Merge Request diffs](diffs.md) ## Performance guides diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md index ce69694ab6a..46c5baddb9c 100644 --- a/doc/development/background_migrations.md +++ b/doc/development/background_migrations.md @@ -24,7 +24,7 @@ Some examples where background migrations can be useful: * Migrating events from one table to multiple separate tables. * Populating one column based on JSON stored in another column. -* Migrating data that depends on the output of exernal services (e.g. an API). +* Migrating data that depends on the output of external services (e.g. an API). ## Isolation @@ -46,7 +46,7 @@ See [Sidekiq best practices guidelines](https://github.com/mperham/sidekiq/wiki/ for more details. Make sure that in case that your migration job is going to be retried data -integrity is guarateed. +integrity is guaranteed. ## How It Works diff --git a/doc/development/diffs.md b/doc/development/diffs.md new file mode 100644 index 00000000000..55fc16e0b33 --- /dev/null +++ b/doc/development/diffs.md @@ -0,0 +1,115 @@ +# Working with Merge Request diffs + +Currently we rely on different sources to present merge request diffs, these include: + +- Rugged gem +- Gitaly service +- Database (through `merge_request_diff_files`) +- Redis (cached highlighted diffs) + +We're constantly moving Rugged calls to Gitaly and the progress can be followed through [Gitaly repo](https://gitlab.com/gitlab-org/gitaly). + +## Architecture overview + +When refreshing a Merge Request (pushing to a source branch, force-pushing to target branch, or if the target branch now contains any commits from the MR) +we fetch the comparison information using `Gitlab::Git::Compare`, which fetches `base` and `head` data using Gitaly and diff between them through +`Gitlab::Git::Diff.between` (which uses _Gitaly_ if it's enabled, otherwise _Rugged_). +The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are +then persisted on `merge_request_diff_files` table. + +Even though diffs higher than 10kb are collapsed (`Gitlab::Git::Diff::COLLAPSE_LIMIT`), we still keep them on Postgres. However, diff files over _safety limits_ +(see the [Diff limits section](#diff-limits)) are _not_ persisted. + +In order to present diffs information on the Merge Request diffs page, we: + +1. Fetch all diff files from database `merge_request_diff_files` +2. Fetch the _old_ and _new_ file blobs in batch to: + 1. Highlight old and new file content + 2. Know which viewer it should use for each file (text, image, deleted, etc) + 3. Know if the file content changed + 4. Know if it was stored externally + 5. Know if it had storage errors +3. If the diff file is cacheable (text-based), it's cached on Redis +using `Gitlab::Diff::FileCollection::MergeRequestDiff` + +## Diff limits + +As explained above, we limit single diff files and the size of the whole diff. There are scenarios where we collapse the diff file, +and cases where the diff file is not presented at all, and the user is guided to the Blob view. Here we'll go into details about +these limits. + +### Diff collection limits + +Limits that act onto all diff files collection. Files number, lines number and files size are considered. + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100 +``` + +File diffs will be collapsed (but be expandable) if 100 files have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 +``` + +File diffs will be collapsed (but be expandable) if 5000 lines have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes +``` + +File diffs will be collapsed (but be expandable) if 500 kilobytes have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000 +``` + +No more files will be rendered at all if 1000 files have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000 +``` + +No more files will be rendered at all if 50,000 lines have already been rendered. + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes +``` + +No more files will be rendered at all if 5 megabytes have already been rendered. + + +### Individual diff file limits + +Limits that act onto each diff file of a collection. Files number, lines number and files size are considered. + +```ruby +Gitlab::Git::Diff::COLLAPSE_LIMIT = 10.kilobytes +``` + +File diff will be collapsed (but be expandable) if it is larger than 10 kilobytes. + +```ruby +Gitlab::Git::Diff::SIZE_LIMIT = 100.kilobytes +``` + +File diff will not be rendered if it's larger than 100 kilobytes. + + +```ruby +Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 +``` + +File diff will be suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines. + +## Viewers + +Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to map metadata about each type of Diff File. It has information +whether it's a binary, which partial should be used to render it or which File extensions this class accounts for. + +`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type in order to check if it can be rendered. + diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 0550ea527cb..5da015ca557 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -4,7 +4,7 @@ The documentation style guide defines the markup structure used in GitLab documentation. Check the [documentation guidelines](writing_documentation.md) for general development instructions. -Check the GitLab hanbook for the [writing styles guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines). +Check the GitLab handbook for the [writing styles guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines). ## Text @@ -19,7 +19,7 @@ Check the GitLab hanbook for the [writing styles guidelines](https://about.gitla - Unless there's a logical reason not to, add documents in alphabetical order - Write in US English - Use [single spaces][] instead of double spaces -- Jump a line between different markups (e.g., after every paragraph, hearder, list, etc) +- Jump a line between different markups (e.g., after every paragraph, header, list, etc) - Capitalize "G" and "L" in GitLab - Capitalize feature, products, and methods names. E.g.: GitLab Runner, Geo, Issue Boards, Git, Prometheus, Continuous Integration. diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 287143d6255..4873090a2d4 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -279,7 +279,7 @@ end ``` In `lib/gitlab/visibility_level.rb` this method is used to return the -allowed visibilty levels: +allowed visibility levels: ```ruby def levels_for_user(user = nil) diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 7b5fa6ca42f..677168937c7 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -236,7 +236,7 @@ export class Foo { } ``` -On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized oustside of the constructor. +On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor. 1. Prefer `.map`, `.reduce` or `.filter` over `.forEach` A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`, diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md index 34a02bd2c3c..fdbd7f1fa37 100644 --- a/doc/development/file_storage.md +++ b/doc/development/file_storage.md @@ -84,7 +84,7 @@ The `RecordsUploads::Concern` concern will create an `Upload` entry for every fi By including the `ObjectStorage::Concern` in the `GitlabUploader` derived class, you may enable the object storage for this uploader. To enable the object storage in your uploader, you need to either 1) include `RecordsUpload::Concern` and prepend `ObjectStorage::Extension::RecordsUploads` or 2) mount the uploader and create a new field named `<mount>_store`. -The `CarrierWave::Uploader#store_dir` is overriden to +The `CarrierWave::Uploader#store_dir` is overridden to - `GitlabUploader.base_dir` + `GitlabUploader.dynamic_segment` when the store is LOCAL - `GitlabUploader.dynamic_segment` when the store is REMOTE (the bucket name is used to namespace) diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md index b1bec84a2f3..0edcb23c7c5 100644 --- a/doc/development/i18n/externalization.md +++ b/doc/development/i18n/externalization.md @@ -270,7 +270,7 @@ If there are merge conflicts in the `gitlab.pot` file, you can delete the file and regenerate it using the same command. Confirm that you are not deleting any strings accidentally by looking over the diff. The command also updates the translation files for each language: `locale/*/gitlab.po` -These changes can be discarded, the languange files will be updated by Crowdin +These changes can be discarded, the language files will be updated by Crowdin automatically. Discard all of them at once like this: diff --git a/doc/development/img/state-model-issue.png b/doc/development/img/state-model-issue.png Binary files differdeleted file mode 100644 index ee33b6886c6..00000000000 --- a/doc/development/img/state-model-issue.png +++ /dev/null diff --git a/doc/development/img/state-model-legend.png b/doc/development/img/state-model-legend.png Binary files differdeleted file mode 100644 index 1c121f2588c..00000000000 --- a/doc/development/img/state-model-legend.png +++ /dev/null diff --git a/doc/development/img/state-model-merge-request.png b/doc/development/img/state-model-merge-request.png Binary files differdeleted file mode 100644 index e00da10cac2..00000000000 --- a/doc/development/img/state-model-merge-request.png +++ /dev/null diff --git a/doc/development/merge_request_performance_guidelines.md b/doc/development/merge_request_performance_guidelines.md index 2b4126b43ef..12badbe39b2 100644 --- a/doc/development/merge_request_performance_guidelines.md +++ b/doc/development/merge_request_performance_guidelines.md @@ -162,7 +162,7 @@ need for running complex operations to fetch the data. You should use Redis if data should be cached for a certain time period instead of the duration of the transaction. -For example, say you process multiple snippets of text containiner username +For example, say you process multiple snippets of text containing username mentions (e.g. `Hello @alice` and `How are you doing @alice?`). By caching the user objects for every username we can remove the need for running the same query for every mention of `@alice`. diff --git a/doc/development/object_state_models.md b/doc/development/object_state_models.md deleted file mode 100644 index 623bbf143ef..00000000000 --- a/doc/development/object_state_models.md +++ /dev/null @@ -1,25 +0,0 @@ -# Object state models - -## Diagrams - -[GitLab object state models](https://drive.google.com/drive/u/3/folders/0B5tDlHAM4iZINmpvYlJXcDVqMGc) - ---- - -## Legend - -![legend](img/state-model-legend.png) - ---- - -## Issue - -[`app/models/issue.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/issue.rb) -![issue](img/state-model-issue.png) - ---- - -## Merge request - -[`app/models/merge_request.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/merge_request.rb) -![merge request](img/state-model-merge-request.png)
\ No newline at end of file diff --git a/doc/development/ordering_table_columns.md b/doc/development/ordering_table_columns.md index 249e70c7b0e..5d00e1f7a0c 100644 --- a/doc/development/ordering_table_columns.md +++ b/doc/development/ordering_table_columns.md @@ -30,7 +30,7 @@ example) at the end. ## Type Sizes -While the PostgreSQL docuemntation +While the PostgreSQL documentation (https://www.postgresql.org/docs/current/static/datatype.html) contains plenty of information we will list the sizes of common types here so it's easier to look them up. Here "word" refers to the word size, which is 4 bytes for a 32 diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index e86c1f5232a..51794f7f4df 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -28,7 +28,7 @@ records should use stubs/doubles as much as possible. | `app/uploaders/` | `spec/uploaders/` | RSpec | | | `app/views/` | `spec/views/` | RSpec | | | `app/workers/` | `spec/workers/` | RSpec | | -| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [Frontent Testing guide](frontend_testing.md) section. | +| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [Frontend Testing guide](frontend_testing.md) section. | ## Integration tests diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 012c64be79f..b57520a00e0 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -219,7 +219,7 @@ Blocks are a way to group related information. #### Content blocks -Content blocks (`.content-block`) are the basic grouping of content. They are commonly used in [lists](#lists), and are separated by a botton border. +Content blocks (`.content-block`) are the basic grouping of content. They are commonly used in [lists](#lists), and are separated by a button border. ![Content block](img/components-contentblock.png) @@ -281,7 +281,7 @@ Modals are only used for having a conversation and confirmation with the user. T | Modal with 2 actions | Modal with 3 actions | Special confirmation | | --------------------- | --------------------- | -------------------- | -| ![two-actions](img/modals-general-confimation-dialog.png) | ![three-actions](img/modals-three-buttons.png) | ![spcial-confirmation](img/modals-special-confimation-dialog.png) | +| ![two-actions](img/modals-general-confimation-dialog.png) | ![three-actions](img/modals-three-buttons.png) | ![special-confirmation](img/modals-special-confimation-dialog.png) | > TODO: Special case for modal. diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md index 9d0c62ecc35..b8be8daa157 100644 --- a/doc/development/what_requires_downtime.md +++ b/doc/development/what_requires_downtime.md @@ -255,7 +255,7 @@ otherwise it will raise a `TypeError`. ## Adding Indexes Adding indexes is an expensive process that blocks INSERT and UPDATE queries for -the duration. When using PostgreSQL one can work arounds this by using the +the duration. When using PostgreSQL one can work around this by using the `CONCURRENTLY` option: ```sql diff --git a/doc/development/writing_documentation.md b/doc/development/writing_documentation.md index d6a13e7483a..9bca4637830 100644 --- a/doc/development/writing_documentation.md +++ b/doc/development/writing_documentation.md @@ -49,7 +49,7 @@ do before. **Use cases**: provide at least two, ideally three, use cases for every major feature. You should answer this question: what can you do with this feature/change? Use cases -are examples of how this feauture or change can be used in real life. +are examples of how this feature or change can be used in real life. Examples: - CE and EE: [Issues](../user/project/issues/index.md#use-cases) diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index 5c7557ed2b3..e1af086f418 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -91,7 +91,7 @@ Follow the below instructions to ensure you use the most up to date requirements #### Check for InnoDB File-Per-Table Tablespaces -We need to check, enable and maybe convert your existing GitLab DB tables to the [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) as a prerequise for supporting **utfb8mb4 with long indexes** required by recent GitLab databases. +We need to check, enable and maybe convert your existing GitLab DB tables to the [InnoDB File-Per-Table Tablespaces](http://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) as a prerequisite for supporting **utfb8mb4 with long indexes** required by recent GitLab databases. # Login to MySQL mysql -u root -p diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md index 3389f0260f9..2691495e0d4 100644 --- a/doc/install/google_cloud_platform/index.md +++ b/doc/install/google_cloud_platform/index.md @@ -2,7 +2,7 @@ ![GCP landing page](img/gcp_landing.png) -Gettung started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy. +Getting started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy. ## Prerequisites diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md index 84eeacac3fd..429519a92e1 100644 --- a/doc/install/kubernetes/gitlab_chart.md +++ b/doc/install/kubernetes/gitlab_chart.md @@ -1,488 +1,6 @@ # GitLab Helm Chart -> **Note:** -* This chart has been tested on Google Kubernetes Engine and Azure Container Service. +> **Note:** This chart is currently in alpha. -**This chart is deprecated.** For small installations on Kubernetes today, we recommend the beta [`gitlab-omnibus` Helm chart](gitlab_omnibus.md). +The cloud native `gitlab` chart is the next generation Helm chart, currently in alpha, and will replace the [`gitlab-omnibus`](gitlab_omnibus.md) chart. It will support large deployments with horizontal scaling of individual GitLab components. -A new [cloud native GitLab chart](index.md#cloud-native-gitlab-chart) is in development with increased scalability and resilience, among other benefits. The cloud native chart will replace both the `gitlab` and `gitlab-omnibus` charts when available later this year. - -Due to the significant architectural changes, migrating will require backing up data out of this instance and restoring it into the new deployment. For more information on available GitLab Helm Charts, please see our [overview](index.md#chart-overview). - -## Introduction - -The `gitlab` Helm chart deploys just GitLab into your Kubernetes cluster, and offers extensive configuration options. This chart requires advanced knowledge of Kubernetes to successfully use. We **strongly recommend** the [gitlab-omnibus](gitlab_omnibus.md) chart. - -This chart includes the following: - -- Deployment using the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce) or [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee) container image -- ConfigMap containing the `gitlab.rb` contents that configure [Omnibus GitLab](https://docs.gitlab.com/omnibus/settings/configuration.html#configuration-options) -- Persistent Volume Claims for Data, Config, Logs, and Registry Storage -- A Kubernetes service -- Optional Redis deployment using the [Redis Chart](https://github.com/kubernetes/charts/tree/master/stable/redis) (defaults to enabled) -- Optional PostgreSQL deployment using the [PostgreSQL Chart](https://github.com/kubernetes/charts/tree/master/stable/postgresql) (defaults to enabled) -- Optional Ingress (defaults to disabled) - -## Prerequisites - -- _At least_ 3 GB of RAM available on your cluster. 41GB of storage and 2 CPU are also required. -- Kubernetes 1.4+ with Beta APIs enabled -- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure -- The ability to point a DNS entry or URL at your GitLab install -- The `kubectl` CLI installed locally and authenticated for the cluster -- The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine - -## Configuring GitLab - -Create a `values.yaml` file for your GitLab configuration. See the -[Helm docs](https://github.com/kubernetes/helm/blob/master/docs/chart_template_guide/values_files.md) -for information on how your values file will override the defaults. - -The default configuration can always be [found in the `values.yaml`](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab/values.yaml), in the chart repository. - -### Required configuration - -In order for GitLab to function, your config file **must** specify the following: - -- An `externalUrl` that GitLab will be reachable at. - -### Choosing GitLab Edition - -The Helm chart defaults to installing GitLab CE. This can be controlled by setting the `edition` variable in your values. - -Setting `edition` to GitLab Enterprise Edition (EE) in your `values.yaml` - -```yaml -edition: EE - -externalUrl: 'http://gitlab.example.com' -``` - -### Choosing a different GitLab release version - -The version of GitLab installed is based on the `edition` setting (see [section](#choosing-gitlab-edition) above), and -the value of the corresponding helm setting: `ceImage` or `eeImage`. - -```yaml -## GitLab Edition -## ref: https://about.gitlab.com/products/ -## - CE - Community Edition -## - EE - Enterprise Edition - (requires license issued by GitLab Inc) -## -edition: CE - -## GitLab CE image -## ref: https://hub.docker.com/r/gitlab/gitlab-ce/tags/ -## -ceImage: gitlab/gitlab-ce:9.1.2-ce.0 - -## GitLab EE image -## ref: https://hub.docker.com/r/gitlab/gitlab-ee/tags/ -## -eeImage: gitlab/gitlab-ee:9.1.2-ee.0 -``` - -The different images can be found in the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce/tags/) and [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee/tags/) -repositories on Docker Hub - -> **Note:** -There is no guarantee that other release versions of GitLab, other than what are -used by default in the chart, will be supported by a chart install. - - -### Custom Omnibus GitLab configuration - -In addition to the configuration options provided for GitLab in the Helm Chart, you can also pass any custom configuration -that is valid for the [Omnibus GitLab Configuration](https://docs.gitlab.com/omnibus/settings/configuration.html). - -The setting to pass these values in is `omnibusConfigRuby`. It accepts any valid -Ruby code that could used in the Omnibus `/etc/gitlab/gitlab.rb` file. In -Kubernetes, the contents will be stored in a ConfigMap. - -Example setting: - -```yaml -omnibusConfigRuby: | - unicorn['worker_processes'] = 2; - gitlab_rails['trusted_proxies'] = ["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]; -``` - -### Persistent storage - -By default, persistent storage is enabled for GitLab and the charts it depends -on (Redis and PostgreSQL). - -Components can have their claim size set from your `values.yaml`, and each -component allows you to optionally configure the `storageClass` variable so you -can take advantage of faster drives on your cloud provider. - -Basic configuration: - -```yaml -## Enable persistence using Persistent Volume Claims -## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ -## ref: https://docs.gitlab.com/ce/install/requirements.html#storage -## -persistence: - ## This volume persists generated configuration files, keys, and certs. - ## - gitlabEtc: - enabled: true - size: 1Gi - ## If defined, volume.beta.kubernetes.io/storage-class: <storageClass> - ## Default: volume.alpha.kubernetes.io/storage-class: default - ## - # storageClass: - accessMode: ReadWriteOnce - ## This volume is used to store git data and other project files. - ## ref: https://docs.gitlab.com/omnibus/settings/configuration.html#storing-git-data-in-an-alternative-directory - ## - gitlabData: - enabled: true - size: 10Gi - ## If defined, volume.beta.kubernetes.io/storage-class: <storageClass> - ## Default: volume.alpha.kubernetes.io/storage-class: default - ## - # storageClass: - accessMode: ReadWriteOnce - gitlabRegistry: - enabled: true - size: 10Gi - ## If defined, volume.beta.kubernetes.io/storage-class: <storageClass> - ## Default: volume.alpha.kubernetes.io/storage-class: default - ## - # storageClass: - - postgresql: - persistence: - # storageClass: - size: 10Gi - ## Configuration values for the Redis dependency. - ## ref: https://github.com/kubernetes/charts/blob/master/stable/redis/README.md - ## - redis: - persistence: - # storageClass: - size: 10Gi -``` - ->**Note:** -You can make use of faster SSD drives by adding a [StorageClass] to your cluster -and using the `storageClass` setting in the above config to the name of -your new storage class. - -### Routing - -By default, the GitLab chart uses a service type of `LoadBalancer` which will -result in the GitLab service being exposed externally using your cloud provider's -load balancer. - -This field is configurable in your `values.yml` by setting the top-level -`serviceType` field. See the [Service documentation][kube-srv] for more -information on the possible values. - -#### Ingress routing - -Optionally, you can enable the Chart's ingress for use by an ingress controller -deployed in your cluster. - -To enable the ingress, edit its section in your `values.yaml`: - -```yaml -ingress: - ## If true, gitlab Ingress will be created - ## - enabled: true - - ## gitlab Ingress hostnames - ## Must be provided if Ingress is enabled - ## - hosts: - - gitlab.example.com - - ## gitlab Ingress annotations - ## - annotations: - kubernetes.io/ingress.class: nginx -``` - -You must also provide the list of hosts that the ingress will use. In order for -you ingress controller to work with the GitLab Ingress, you will need to specify -its class in an annotation. - ->**Note:** -The Ingress alone doesn't expose GitLab externally. You need to have a Ingress controller setup to do that. -Setting up an Ingress controller can be done by installing the `nginx-ingress` helm chart. But be sure -to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md). ->**Note:** -If you would like to use the Registry, you will also need to ensure your Ingress supports a [sufficiently large request size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size). - -#### Preserving Source IPs - -If you are using the `LoadBalancer` serviceType you may run into issues where user IP addresses in the GitLab -logs, and used in abuse throttling are not accurate. This is due to how Kubernetes uses source NATing on cluster nodes without endpoints. - -See the [Kubernetes documentation](https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer) for more information. - -To fix this you can add the following service annotation to your `values.yaml` - -```yaml -## For minikube, set this to NodePort, elsewhere use LoadBalancer -## ref: http://kubernetes.io/docs/user-guide/services/#publishing-services---service-types -## -serviceType: LoadBalancer - -## Optional annotations for gitlab service. -serviceAnnotations: - service.beta.kubernetes.io/external-traffic: "OnlyLocal" -``` - ->**Note:** -If you are using the ingress routing, you will likely also need to specify the annotation on the service for the ingress -controller. For `nginx-ingress` you can check the -[configuration documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md#configuration) -on how to add the annotation to the `controller.service.annotations` array. - ->**Note:** -When using the `nginx-ingress` controller on Google Kubernetes Engine (GKE), and using the `external-traffic` annotation, -you will need to additionally set the `controller.kind` to be DaemonSet. Otherwise only pods running on the same node -as the nginx controller will be able to reach GitLab. This may result in pods within your cluster not being able to reach GitLab. -See the [Kubernetes documentation](https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer) and -[nginx-ingress configuration documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md#configuration) -for more information. - -### External database - -You can configure the GitLab Helm chart to connect to an external PostgreSQL -database. - ->**Note:** -This is currently our recommended approach for a Production setup. - -To use an external database, in your `values.yaml`, disable the included -PostgreSQL dependency, then configure access to your database: - -```yaml -dbHost: "<reachable postgres hostname>" -dbPassword: "<password for the user with access to the db>" -dbUsername: "<user with read/write access to the database>" -dbDatabase: "<database name on postgres to connect to for GitLab>" - -postgresql: - # Sets whether the PostgreSQL helm chart is used as a dependency - enabled: false -``` - -Be sure to check the GitLab documentation on how to -[configure the external database](../requirements.md#postgresql-requirements) - -You can also configure the chart to use an external Redis server, but this is -not required for basic production use: - -```yaml -dbHost: "<reachable redis hostname>" -dbPassword: "<password>" - -redis: - # Sets whether the Redis helm chart is used as a dependency - enabled: false -``` - -### Sending email - -By default, the GitLab container will not be able to send email from your cluster. -In order to send email, you should configure SMTP settings in the -`omnibusConfigRuby` section, as per the [GitLab Omnibus documentation](https://docs.gitlab.com/omnibus/settings/smtp.html). - ->**Note:** -Some cloud providers restrict emails being sent out on SMTP, so you will have -to use a SMTP service that is supported by your provider. See this -[Google Cloud Platform page](https://cloud.google.com/compute/docs/tutorials/sending-mail/) -as and example. - -Here is an example configuration for Mailgun SMTP support: - -```yaml -omnibusConfigRuby: | - # This is example config of what you may already have in your omnibusConfigRuby object - unicorn['worker_processes'] = 2; - gitlab_rails['trusted_proxies'] = ["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]; - - # SMTP settings - gitlab_rails['smtp_enable'] = true - gitlab_rails['smtp_address'] = "smtp.mailgun.org" - gitlab_rails['smtp_port'] = 2525 # High port needed for Google Cloud - gitlab_rails['smtp_authentication'] = "plain" - gitlab_rails['smtp_enable_starttls_auto'] = false - gitlab_rails['smtp_user_name'] = "postmaster@mg.your-mail-domain" - gitlab_rails['smtp_password'] = "you-password" - gitlab_rails['smtp_domain'] = "mg.your-mail-domain" -``` - -### HTTPS configuration - -To setup HTTPS access to your GitLab server, first you need to configure the -chart to use the [ingress](#ingress-routing). - -GitLab's config should be updated to support [proxied SSL](https://docs.gitlab.com/omnibus/settings/nginx.html#supporting-proxied-ssl). - -In addition to having a Ingress Controller deployed and the basic ingress -settings configured, you will also need to specify in the ingress settings -which hosts to use HTTPS for. - -Make sure `externalUrl` now includes `https://` instead of `http://` in its -value, and update the `omnibusConfigRuby` section: - -```yaml -externalUrl: 'https://gitlab.example.com' - -omnibusConfigRuby: | - # This is example config of what you may already have in your omnibusConfigRuby object - unicorn['worker_processes'] = 2; - gitlab_rails['trusted_proxies'] = ["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]; - - # These are the settings needed to support proxied SSL - nginx['listen_port'] = 80 - nginx['listen_https'] = false - nginx['proxy_set_headers'] = { - "X-Forwarded-Proto" => "https", - "X-Forwarded-Ssl" => "on" - } - -ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: 'true' Annotation used for letsencrypt support - - hosts: - - gitlab.example.com - - ## gitlab Ingress TLS configuration - ## Secrets must be created in the namespace, and is not done for you in this chart - ## - tls: - - secretName: gitlab-tls - hosts: - - gitlab.example.com -``` - -You will need to create the named secret in your cluster, specifying the private -and public certificate pair using the format outlined in the -[ingress documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls). - -Alternatively, you can use the `kubernetes.io/tls-acme` annotation, and install -the `kube-lego` chart to your cluster to have Let's Encrypt issue your -certificate. See the [kube-lego documentation](https://github.com/kubernetes/charts/blob/master/stable/kube-lego/README.md) -for more information. - -### Enabling the GitLab Container Registry - -The GitLab Registry is disabled by default but can be enabled by providing an -external URL for it in the configuration. In order for the Registry to be easily -used by GitLab CI and your Kubernetes cluster, you will need to set it up with -a TLS certificate, so these examples will include the ingress settings for that -as well. See the [HTTPS Configuration section](#https-configuration) -for more explanation on some of these settings. - -Example config: - -```yaml -externalUrl: 'https://gitlab.example.com' - -omnibusConfigRuby: | - # This is example config of what you may already have in your omnibusConfigRuby object - unicorn['worker_processes'] = 2; - gitlab_rails['trusted_proxies'] = ["10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]; - - registry_external_url 'https://registry.example.com'; - - # These are the settings needed to support proxied SSL - nginx['listen_port'] = 80 - nginx['listen_https'] = false - nginx['proxy_set_headers'] = { - "X-Forwarded-Proto" => "https", - "X-Forwarded-Ssl" => "on" - } - registry_nginx['listen_port'] = 80 - registry_nginx['listen_https'] = false - registry_nginx['proxy_set_headers'] = { - "X-Forwarded-Proto" => "https", - "X-Forwarded-Ssl" => "on" - } - -ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: 'true' Annotation used for letsencrypt support - - hosts: - - gitlab.example.com - - registry.example.com - - ## gitlab Ingress TLS configuration - ## Secrets must be created in the namespace, and is not done for you in this chart - ## - tls: - - secretName: gitlab-tls - hosts: - - gitlab.example.com - - registry.example.com -``` - -## Installing GitLab using the Helm Chart -> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage. - -Add the GitLab Helm repository and initialize Helm: - -```bash -helm repo add gitlab https://charts.gitlab.io -helm init -``` - -Once you [have configured](#configuration) GitLab in your `values.yml` file, -run the following: - -```bash -helm install --namespace <NAMESPACE> --name gitlab -f <CONFIG_VALUES_FILE> gitlab/gitlab -``` - -where: - -- `<NAMESPACE>` is the Kubernetes namespace where you want to install GitLab. -- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom - configuration. See the [Configuration](#configuration) section to create it. - -## Updating GitLab using the Helm Chart - -Once your GitLab Chart is installed, configuration changes and chart updates -should we done using `helm upgrade` - -```bash -helm upgrade --namespace <NAMESPACE> -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab -``` - -where: - -- `<NAMESPACE>` is the Kubernetes namespace where GitLab is installed. -- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom - [configuration] (#configuration). -- `<RELEASE-NAME>` is the name you gave the chart when installing it. - In the [Install section](#installing) we called it `gitlab`. - -## Uninstalling GitLab using the Helm Chart - -To uninstall the GitLab Chart, run the following: - -```bash -helm delete --namespace <NAMESPACE> <RELEASE-NAME> -``` - -where: - -- `<NAMESPACE>` is the Kubernetes namespace where GitLab is installed. -- `<RELEASE-NAME>` is the name you gave the chart when installing it. - In the [Install section](#installing) we called it `gitlab`. - -[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types -[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses +Installation instructions and known issues during alpha are available at the [project page](https://gitlab.com/charts/gitlab/).
\ No newline at end of file diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md index a03c49cbd89..0a093c9ec32 100644 --- a/doc/install/kubernetes/gitlab_runner_chart.md +++ b/doc/install/kubernetes/gitlab_runner_chart.md @@ -50,12 +50,12 @@ Here is a snippet of the important settings: gitlabUrl: http://gitlab.your-domain.com/ ## The Registration Token for adding new Runners to the GitLab Server. This must -## be retreived from your GitLab Instance. +## be retrieved from your GitLab Instance. ## ref: https://docs.gitlab.com/ce/ci/runners/README.html#creating-and-registering-a-runner ## runnerRegistrationToken: "" -## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use +## Set the certsSecretName in order to pass custom certificates for GitLab Runner to use ## Provide resource name for a Kubernetes Secret Object in the same namespace, ## this is used to populate the /etc/gitlab-runner/certs directory ## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates @@ -130,7 +130,7 @@ runners: ### Enabling RBAC support -If your cluster has RBAC enabled, you can choose to either have the chart create its own sevice account or provide one. +If your cluster has RBAC enabled, you can choose to either have the chart create its own service account or provide one. To have the chart create the service account for you, set `rbac.create` to true. @@ -208,7 +208,7 @@ You then need to provide the secret's name to the GitLab Runner chart. Add the following to your `values.yaml` ```yaml -## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use +## Set the certsSecretName in order to pass custom certificates for GitLab Runner to use ## Provide resource name for a Kubernetes Secret Object in the same namespace, ## this is used to populate the /etc/gitlab-runner/certs directory ## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md index aa9b8777359..7d8b8fc1597 100644 --- a/doc/install/kubernetes/index.md +++ b/doc/install/kubernetes/index.md @@ -10,10 +10,9 @@ should be deployed, upgraded, and configured. ## Chart Overview * **[GitLab-Omnibus](gitlab_omnibus.md)**: The best way to run GitLab on Kubernetes today, suited for small deployments. The chart is in beta and will be deprecated by the [cloud native GitLab chart](#cloud-native-gitlab-chart). -* **[Cloud Native GitLab Chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md)**: The next generation GitLab chart, currently in alpha. Will support large deployments with horizontal scaling of individual GitLab components. +* **[Cloud Native GitLab Chart](https://gitlab.com/charts/gitlab/blob/master/README.md)**: The next generation GitLab chart, currently in alpha. Will support large deployments with horizontal scaling of individual GitLab components. * Other Charts * [GitLab Runner Chart](gitlab_runner_chart.md): For deploying just the GitLab Runner. - * [Advanced GitLab Installation](gitlab_chart.md): Deprecated, being replaced by the [cloud native GitLab chart](#cloud-native-gitlab-chart). Provides additional deployment options, but provides less functionality out-of-the-box. * [Community Contributed Charts](#community-contributed-charts): Community contributed charts, deprecated by the official GitLab chart. ## GitLab-Omnibus Chart (Recommended) @@ -27,7 +26,7 @@ Learn more about the [gitlab-omnibus chart](gitlab_omnibus.md). ## Cloud Native GitLab Chart -GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current chart](#gitlab-omnibus-chart-recommended). +GitLab is working towards building a [cloud native GitLab chart](https://gitlab.com/charts/gitlab/blob/master/README.md). A key part of this effort is to isolate each service into its [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current chart](#gitlab-omnibus-chart-recommended). By offering individual containers and charts, we will be able to provide a number of benefits: * Easier horizontal scaling of each service, @@ -37,7 +36,7 @@ By offering individual containers and charts, we will be able to provide a numbe Presently this chart is available in alpha for testing, and not recommended for production use. -Learn more about the [cloud native GitLab chart here ](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) and [here [Video]](https://youtu.be/Z6jWR8Z8dv8). +Learn more about the [cloud native GitLab chart here ](https://gitlab.com/charts/gitlab/blob/master/README.md) and [here [Video]](https://youtu.be/Z6jWR8Z8dv8). ## Other Charts diff --git a/doc/integration/github.md b/doc/integration/github.md index b0d67db8b59..23bb8ef9303 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -69,7 +69,7 @@ GitHub will generate an application ID and secret key for you to use. "name" => "github", "app_id" => "YOUR_APP_ID", "app_secret" => "YOUR_APP_SECRET", - "url" => "https://github.com/", + "url" => "https://github.example.com/", "args" => { "scope" => "user:email" } } ] @@ -125,7 +125,7 @@ For omnibus package: "name" => "github", "app_id" => "YOUR_APP_ID", "app_secret" => "YOUR_APP_SECRET", - "url" => "https://github.com/", + "url" => "https://github.example.com/", "verify_ssl" => false, "args" => { "scope" => "user:email" } } diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md index e0fc1bb801f..8611d4f7315 100644 --- a/doc/integration/shibboleth.md +++ b/doc/integration/shibboleth.md @@ -43,7 +43,7 @@ exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibbo RequestHeader set X_FORWARDED_PROTO 'https' ``` -1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should addjust them to your need and environment. Add any other configuration you need. +1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should adjust them to your need and environment. Add any other configuration you need. File should look like this: ``` diff --git a/doc/ssh/README.md b/doc/ssh/README.md index aa14a39e4c9..b71e9bf3000 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -196,7 +196,7 @@ This is really useful for integrating repositories to secured, shared Continuous Integration (CI) services or other shared services. GitLab administrators can set up the Global Shared Deploy key in GitLab and add the private key to any shared systems. Individual repositories opt into -exposing their repsitory using these keys when a project masters (or higher) +exposing their repository using these keys when a project masters (or higher) authorizes a Global Shared Deploy key to be used with their project. Global Shared Keys can provide greater security compared to Per-Project Deploy @@ -224,7 +224,7 @@ if there is at least one Global Deploy Key configured. CAUTION: **Warning:** Defining Global Deploy Keys does not expose any given repository via -the key until that respository adds the Global Deploy Key to their project. +the key until that repository adds the Global Deploy Key to their project. In this way the Global Deploy Keys enable access by other systems, but do not implicitly give any access just by setting them up. diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index fb2ce27bf49..7c0cd2c40d2 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -10,8 +10,30 @@ applications. ## Overview With Auto DevOps, the software development process becomes easier to set up -as every project can have a complete workflow from build to deploy and monitoring, -with minimal to zero configuration. +as every project can have a complete workflow from verification to monitoring +without needing to configure anything. Just push your code and GitLab takes +care of everything else. This makes it easier to start new projects and brings +consistency to how applications are set up throughout a company. + +## Comparison to application platforms and PaaS + +Auto DevOps provides functionality described by others as an application +platform or as a Platform as a Service (PaaS). It takes inspiration from the +innovative work done by [Heroku](https://www.heroku.com/) and goes beyond it +in a couple of ways: + +1. Auto DevOps works with any Kubernetes cluster, you're not limited to running + on GitLab's infrastructure (note that many features also work without Kubernetes). +1. There is no additional cost (no markup on the infrastructure costs), and you + can use a self-hosted Kubernetes cluster or Containers as a Service on any + public cloud (for example [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)). +1. Auto DevOps has more features including security testing, performance testing, + and code quality testing. +1. It offers an incremental graduation path. If you need advanced customizations + you can start modifying the templates without having to start over on a + completely different platform. + +## Features Comprised of a set of stages, Auto DevOps brings these best practices to your project in an easy and automatic way: @@ -113,6 +135,11 @@ and `1.2.3.4` is the IP address of your load balancer; generally NGINX ([see prerequisites](#prerequisites)). How to set up the DNS record is beyond the scope of this document; you should check with your DNS provider. +Alternatively you can use free public services like [xip.io](http://xip.io) or +[nip.io](http://nip.io) which provide automatic wildcard DNS without any +configuration. Just set the Auto DevOps base domain to `1.2.3.4.xip.io` or +`1.2.3.4.nip.io`. + Once set up, all requests will hit the load balancer, which in turn will route them to the Kubernetes pods that run your application(s). diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md index a9ccbf5a085..945d6a578b0 100644 --- a/doc/university/glossary/README.md +++ b/doc/university/glossary/README.md @@ -89,7 +89,7 @@ A [copy](https://git-scm.com/docs/git-clone) of a repository stored on your mach ### Code Review -Examination of a progam's code. The main aim is to maintain high quality standards of code that is being shipped. Merge requests [serve as a code review tool](https://about.gitlab.com/2014/09/29/gitlab-flow/) in GitLab. +Examination of a program's code. The main aim is to maintain high quality standards of code that is being shipped. Merge requests [serve as a code review tool](https://about.gitlab.com/2014/09/29/gitlab-flow/) in GitLab. ### Code Snippet diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md index 47ccd0e6dbc..f340164b882 100644 --- a/doc/university/high-availability/aws/README.md +++ b/doc/university/high-availability/aws/README.md @@ -354,11 +354,11 @@ add the following script to the User Data section: - mount -a -t nfs - sudo gitlab-ctl reconfigure -On the security group section we can chosse our existing +On the security group section we can choose our existing `gitlab-ec2-security-group` group which has already been tested. After this is launched we are able to start creating our Auto Scaling -Group. Start by giving it a name and assinging it our VPC and private +Group. Start by giving it a name and assigning it our VPC and private subnets. We also want to always start with two instances and if you scroll down to Advanced Details we can choose to receive traffic from ELBs. Lets enable that option and select our ELB. We also want to use the ELB's diff --git a/doc/university/support/README.md b/doc/university/support/README.md index 25d5fe351ca..d1d5db6bbcd 100644 --- a/doc/university/support/README.md +++ b/doc/university/support/README.md @@ -163,7 +163,7 @@ Some tickets need specific knowledge or a deep understanding of a particular com - Aim to have a good understanding of the problems that customers are facing - Aim to have gained experience in scheduling and participating in calls with customers -- Aim to have a good understanding of ticket flow through Zendesk and how to interat with our various channels +- Aim to have a good understanding of ticket flow through Zendesk and how to interact with our various channels ### Stage 4 diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md index a882bf0eb48..9b8a8db58e2 100644 --- a/doc/university/training/end-user/README.md +++ b/doc/university/training/end-user/README.md @@ -27,7 +27,7 @@ project. ### Short Story of Git -- 1991-2002: The Linux kernel was being maintaned by sharing archived files +- 1991-2002: The Linux kernel was being maintained by sharing archived files and patches. - 2002: The Linux kernel project began using a DVCS called BitKeeper - 2005: BitKeeper revoked the free-of-charge status and Git was created diff --git a/doc/university/training/topics/tags.md b/doc/university/training/topics/tags.md index ab48d52d3c3..6333ceedbd7 100644 --- a/doc/university/training/topics/tags.md +++ b/doc/university/training/topics/tags.md @@ -9,7 +9,7 @@ comments: false - Useful for marking deployments and releases - Annotated tags are an unchangeable part of Git history - Soft/lightweight tags can be set and removed at will -- Many projects combine an anotated release tag with a stable branch +- Many projects combine an annotated release tag with a stable branch - Consider setting deployment/release tags automatically ---------- diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md index 90e1d2ba5e8..dccb6cbf071 100644 --- a/doc/university/training/user_training.md +++ b/doc/university/training/user_training.md @@ -279,7 +279,7 @@ See GitLab merge requests for examples: - Useful for marking deployments and releases - Annotated tags are an unchangeable part of Git history - Soft/lightweight tags can be set and removed at will -- Many projects combine an anotated release tag with a stable branch +- Many projects combine an annotated release tag with a stable branch - Consider setting deployment/release tags automatically --- diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md index 603b826e7f2..26329f20339 100644 --- a/doc/user/admin_area/settings/sign_up_restrictions.md +++ b/doc/user/admin_area/settings/sign_up_restrictions.md @@ -1,7 +1,7 @@ # Sign-up restrictions You can block email addresses of specific domains, or whitelist only some -specifc domains via the **Application Settings** in the Admin area. +specific domains via the **Application Settings** in the Admin area. >**Note**: These restrictions are only applied during sign-up. An admin is able to add add a user through the admin panel with a disallowed domain. Also diff --git a/doc/user/group/img/groups.png b/doc/user/group/img/groups.png Binary files differindex 6211f999d5e..3173ddce7ff 100644 --- a/doc/user/group/img/groups.png +++ b/doc/user/group/img/groups.png diff --git a/doc/user/group/img/new_group_from_groups.png b/doc/user/group/img/new_group_from_groups.png Binary files differindex baf34244cb2..9c5dd7ebd8b 100644 --- a/doc/user/group/img/new_group_from_groups.png +++ b/doc/user/group/img/new_group_from_groups.png diff --git a/doc/user/group/img/new_group_from_other_pages.png b/doc/user/group/img/new_group_from_other_pages.png Binary files differindex 014a7088af2..77427224447 100644 --- a/doc/user/group/img/new_group_from_other_pages.png +++ b/doc/user/group/img/new_group_from_other_pages.png diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index 2a982344e5f..02f8ef08117 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -55,7 +55,7 @@ first group being the name of the distro and subsequent groups split like: Another example of GitLab as a company would be the following: - Organization Group - GitLab - - Category Subroup - Marketing + - Category Subgroup - Marketing - (project) Design - (project) General - Category Subgroup - Software diff --git a/doc/user/index.md b/doc/user/index.md index 43b6fd53b91..2494df46f1c 100644 --- a/doc/user/index.md +++ b/doc/user/index.md @@ -56,7 +56,7 @@ With GitLab Enterprise Edition, you can also: [Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals), [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html), and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards) -- Create formal relashionships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html) +- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html) - Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software. - Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance - [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 716787532fc..edb875bc7e6 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -238,6 +238,7 @@ work. The default environment scope is `*`, which means all jobs, regardless of their environment, will use that cluster. Each scope can only be used by a single cluster in a project, and a validation error will occur if otherwise. +Also, jobs that don't have an environment keyword set will not be able to access any cluster. --- diff --git a/doc/user/project/issues/closing_issues.md b/doc/user/project/issues/closing_issues.md index dcfa5ff59b2..1d88745af9f 100644 --- a/doc/user/project/issues/closing_issues.md +++ b/doc/user/project/issues/closing_issues.md @@ -48,12 +48,12 @@ link to each other, but the MR will NOT close the issue(s) when merged. ## From the Issue Board -You can close an issue from [Issue Boards](../issue_board.md) by draging an issue card +You can close an issue from [Issue Boards](../issue_board.md) by dragging an issue card from its list and dropping into **Closed**. ![close issue from the Issue Board](img/close_issue_from_board.gif) -## Customizing the issue closing patern +## Customizing the issue closing pattern Alternatively, a GitLab **administrator** can -[customize the issue closing patern](../../../administration/issue_closing_pattern.md). +[customize the issue closing pattern](../../../administration/issue_closing_pattern.md). diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md index cc8988be36b..786d1c81b1b 100644 --- a/doc/user/project/issues/crosslinking_issues.md +++ b/doc/user/project/issues/crosslinking_issues.md @@ -60,4 +60,4 @@ or simply link both issue and merge request as described in the ### Close an issue by merging a merge request -To [close an issue when a merge request is merged](closing_issues.md#via-merge-request), use the [automatic issue closing patern](automatic_issue_closing.md). +To [close an issue when a merge request is merged](closing_issues.md#via-merge-request), use the [automatic issue closing pattern](automatic_issue_closing.md). diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md index cf5cf1794ee..e9903b01c82 100644 --- a/doc/user/project/issues/issues_functionalities.md +++ b/doc/user/project/issues/issues_functionalities.md @@ -152,7 +152,7 @@ know you like it without spamming them. These text fields also fully support [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm). -#### 17. Comment, start a discusion, or comment and close +#### 17. Comment, start a discussion, or comment and close Once you wrote your comment, you can either: diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index 10e6321eb82..64bb33be547 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -10,7 +10,7 @@ Milestones allow you to organize issues and merge requests into a cohesive group - **Project milestones** can be assigned to issues or merge requests in that project only. - **Group milestones** can be assigned to any issue or merge request of any project in that group. -- In the [future](https://gitlab.com/gitlab-org/gitlab-ce/issues/36862), you will be able to assign group milestones to issues and merge reqeusts of projects in [subgroups](../../group/subgroups/index.md). +- In the [future](https://gitlab.com/gitlab-org/gitlab-ce/issues/36862), you will be able to assign group milestones to issues and merge requests of projects in [subgroups](../../group/subgroups/index.md). ## Creating milestones diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md index 2274cac8ace..556bf1db116 100644 --- a/doc/user/project/pages/getting_started_part_two.md +++ b/doc/user/project/pages/getting_started_part_two.md @@ -50,14 +50,14 @@ created for the steps below. 1. [Fork a sample project](../../../gitlab-basics/fork-project.md) from the [Pages group](https://gitlab.com/pages) 1. Trigger a build (push a change to any file) 1. As soon as the build passes, your website will have been deployed with GitLab Pages. Your website URL will be available under your project's **Settings** > **Pages** -1. Optionally, remove the fork relationship by navigating to your project's **Settings** > expanding **Advanced settings** and scrolling down to **Remove fork relashionship**: +1. Optionally, remove the fork relationship by navigating to your project's **Settings** > expanding **Advanced settings** and scrolling down to **Remove fork relationship**: - ![remove fork relashionship](img/remove_fork_relashionship.png) + ![remove fork relationship](img/remove_fork_relationship.png) To turn a **project website** forked from the Pages group into a **user/group** website, you'll need to: - Rename it to `namespace.gitlab.io`: navigate to project's **Settings** > expand **Advanced settings** > and scroll down to **Rename repository** -- Adjust your SSG's [base URL](#urls-and-baseurls) to from `"project-name"` to `""`. This setting will be at a different place for each SSG, as each of them have their own structure and file tree. Most likelly, it will be in the SSG's config file. +- Adjust your SSG's [base URL](#urls-and-baseurls) to from `"project-name"` to `""`. This setting will be at a different place for each SSG, as each of them have their own structure and file tree. Most likely, it will be in the SSG's config file. > **Notes:** > diff --git a/doc/user/project/pages/img/remove_fork_relashionship.png b/doc/user/project/pages/img/remove_fork_relationship.png Binary files differindex 67c45491f08..67c45491f08 100644 --- a/doc/user/project/pages/img/remove_fork_relashionship.png +++ b/doc/user/project/pages/img/remove_fork_relationship.png diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index a65aa758198..a97ce84b861 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -1,23 +1,22 @@ # GitLab Pages -With GitLab Pages you can host your website at no cost. - -Your files live in a GitLab project's [repository](../repository/index.md), -from which you can deploy [static websites](#explore-gitlab-pages). -GitLab Pages supports all static site generators (SSGs). +With GitLab Pages it's easy to publish your project website. GitLab Pages is a hosting service for static websites, at no additional cost. ## Getting Started -Follow the steps below to get your website live. They shouldn't take more than -5 minutes to complete: +[Create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch) +to get you started quickly, or, +alternatively, start from an existing project as follows: -- 1. [Fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project) an [example project](https://gitlab.com/pages) -- 2. Change a file to trigger a GitLab CI/CD pipeline -- 3. Visit your project's **Settings > Pages** to see your **website link**, and click on it. Bam! Your website is live. +- 1. [Fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project) an [example project](https://gitlab.com/pages): +by forking a project, you create a copy of the codebase you're forking from to start from a template instead of starting from scratch. +- 2. Change a file to trigger a GitLab CI/CD pipeline: GitLab CI/CD will build and deploy your site to GitLab Pages. +- 3. Visit your project's **Settings > Pages** to see your **website link**, and click on it. Bam! Your website is live! :) _Further steps (optional):_ -- 4. Remove the [fork relationship](getting_started_part_two.md#fork-a-project-to-get-started-from) (_You don't need the relationship unless you intent to contribute back to the example project you forked from_). +- 4. Remove the [fork relationship](getting_started_part_two.md#fork-a-project-to-get-started-from) +(_You don't need the relationship unless you intent to contribute back to the example project you forked from_). - 5. Make it a [user/group website](getting_started_part_one.md#user-and-group-websites) **Watch a video with the steps above: https://www.youtube.com/watch?v=TWqh9MtT4Bg** @@ -27,14 +26,23 @@ _Advanced options:_ - [Use a custom domain](getting_started_part_three.md#adding-your-custom-domain-to-gitlab-pages) - Apply [SSL/TLS certification](getting_started_part_three.md#ssl-tls-certificates) to your custom domain -## Explore GitLab Pages +## How Does It Work? With GitLab Pages you can create [static websites](getting_started_part_one.md#what-you-need-to-know-before-getting-started) -for your GitLab projects, groups, or user accounts. You can use any static -website generator: Jekyll, Middleman, Hexo, Hugo, Pelican, you name it! +for your GitLab projects, groups, or user accounts. + +It supports plain static content, such as HTML, and **all** [static site generators (SSGs)](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/), such as Jekyll, Middleman, Hexo, Hugo, and Pelican. + Connect as many custom domains as you like and bring your own TLS certificate to secure them. +Your files live in a project [repository](../repository/index.md) on GitLab. +[GitLab CI](../../../ci/README.md) picks up those files and makes them available at, typically, +`http://<username>.gilab.io/<projectname>`. Please read through the docs on +[GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain) for more info. + +## Explore GitLab Pages + Read the following tutorials to know more about: - [Static websites and GitLab Pages domains](getting_started_part_one.md): Understand what is a static website, and how GitLab Pages default domains work diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md index 08805a4dc99..a06ecc3220f 100644 --- a/doc/user/project/repository/reducing_the_repo_size_using_git.md +++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md @@ -1,6 +1,6 @@ # Reducing the repository size using Git -A GitLab Entrerprise Edition administrator can set a [repository size limit][admin-repo-size] +A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size] which will prevent you to exceed it. When a project has reached its size limit, you will not be able to push to it, diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 2b23c494dc4..4f1b96b775c 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -96,7 +96,7 @@ On the field **Filter by name**, type the project or group name you want to find will filter them for you as you type. You can also look for the projects you starred (**Starred projects**), and **Explore** all -public and internal projects available in GitLab.com, from which you can filter by visibitily, +public and internal projects available in GitLab.com, from which you can filter by visibility, through **Trending**, best rated with **Most starts**, or **All** of them. You can also sort them by **Name**, **Last created**, **Oldest created**, **Last updated**, diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index 104ac0cf31b..0e29740b15f 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -243,7 +243,7 @@ GitLab checks files to detect LFS pointers on push. If LFS pointers are detected Verify that LFS in installed locally and consider a manual push with `git lfs push --all`. -If you are storing LFS files outside of GitLab you can disable LFS on the project by settting `lfs_enabled: false` with the [projects api](../../api/projects.md#edit-project). +If you are storing LFS files outside of GitLab you can disable LFS on the project by setting `lfs_enabled: false` with the [projects api](../../api/projects.md#edit-project). ### Hosting LFS objects externally diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 4a4df1b8b9e..92e3d5cc10a 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -37,13 +37,11 @@ module API use :pagination end - def find_groups(params) - find_params = { - all_available: params[:all_available], - custom_attributes: params[:custom_attributes], - owned: params[:owned] - } - find_params[:parent] = find_group!(params[:id]) if params[:id] + def find_groups(params, parent_id = nil) + find_params = params.slice(:all_available, :custom_attributes, :owned) + find_params[:parent] = find_group!(parent_id) if parent_id + find_params[:all_available] = + find_params.fetch(:all_available, current_user&.full_private_access?) groups = GroupsFinder.new(current_user, find_params).execute groups = groups.search(params[:search]) if params[:search].present? @@ -85,7 +83,7 @@ module API use :with_custom_attributes end get do - groups = find_groups(params) + groups = find_groups(declared_params(include_missing: false), params[:id]) present_groups params, groups end @@ -213,7 +211,7 @@ module API use :with_custom_attributes end get ":id/subgroups" do - groups = find_groups(params) + groups = find_groups(declared_params(include_missing: false), params[:id]) present_groups params, groups end diff --git a/lib/api/helpers/custom_attributes.rb b/lib/api/helpers/custom_attributes.rb index 70e4eda95f8..10d652e33f5 100644 --- a/lib/api/helpers/custom_attributes.rb +++ b/lib/api/helpers/custom_attributes.rb @@ -7,6 +7,9 @@ module API helpers do params :with_custom_attributes do optional :with_custom_attributes, type: Boolean, default: false, desc: 'Include custom attributes in the response' + + optional :custom_attributes, type: Hash, + desc: 'Filter with custom attributes' end def with_custom_attributes(collection_or_resource, options = {}) diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index d2b8b832e4e..735591fedd5 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -19,6 +19,7 @@ module API optional :status, type: String, values: HasStatus::AVAILABLE_STATUSES, desc: 'The status of pipelines' optional :ref, type: String, desc: 'The ref of pipelines' + optional :sha, type: String, desc: 'The sha of pipelines' optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations' optional :name, type: String, desc: 'The name of the user who triggered pipelines' optional :username, type: String, desc: 'The username of the user who triggered pipelines' diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb index 1a25138e7d6..4ca5a78e068 100644 --- a/lib/gitlab/bare_repository_import/importer.rb +++ b/lib/gitlab/bare_repository_import/importer.rb @@ -75,10 +75,11 @@ module Gitlab end def mv_repo(project) - FileUtils.mv(repo_path, File.join(project.repository_storage_path, project.disk_path + '.git')) + storage_path = storage_path_for_shard(project.repository_storage) + FileUtils.mv(repo_path, project.repository.path_to_repo) if bare_repo.wiki_exists? - FileUtils.mv(wiki_path, File.join(project.repository_storage_path, project.disk_path + '.wiki.git')) + FileUtils.mv(wiki_path, File.join(storage_path, project.disk_path + '.wiki.git')) end true @@ -88,6 +89,10 @@ module Gitlab false end + def storage_path_for_shard(shard) + Gitlab.config.repositories.storages[shard].legacy_disk_path + end + def find_or_create_groups return nil unless group_path.present? diff --git a/lib/gitlab/base_doorkeeper_controller.rb b/lib/gitlab/base_doorkeeper_controller.rb new file mode 100644 index 00000000000..e4227af25d2 --- /dev/null +++ b/lib/gitlab/base_doorkeeper_controller.rb @@ -0,0 +1,8 @@ +# This is a base controller for doorkeeper. +# It adds the `can?` helper used in the views. +module Gitlab + class BaseDoorkeeperController < ActionController::Base + include Gitlab::Allowable + helper_method :can? + end +end diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb index d299a5677de..69b8a8fc68f 100644 --- a/lib/gitlab/ci/pipeline/chain/populate.rb +++ b/lib/gitlab/ci/pipeline/chain/populate.rb @@ -14,14 +14,10 @@ module Gitlab @command.seeds_block&.call(pipeline) ## - # Populate pipeline with all stages and builds from pipeline seeds. + # Populate pipeline with all stages, and stages with builds. # pipeline.stage_seeds.each do |stage| pipeline.stages << stage.to_resource - - stage.seeds.each do |build| - pipeline.builds << build.to_resource - end end if pipeline.stages.none? diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb index 05b86f32ce2..73971af6a74 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb @@ -62,21 +62,20 @@ module Gitlab end def move_repositories(namespace, old_full_path, new_full_path) - repo_paths_for_namespace(namespace).each do |repository_storage_path| + repo_shards_for_namespace(namespace).each do |repository_storage| # Ensure old directory exists before moving it - gitlab_shell.add_namespace(repository_storage_path, old_full_path) + gitlab_shell.add_namespace(repository_storage, old_full_path) - unless gitlab_shell.mv_namespace(repository_storage_path, old_full_path, new_full_path) - message = "Exception moving path #{repository_storage_path} \ - from #{old_full_path} to #{new_full_path}" + unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path) + message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}" Rails.logger.error message end end end - def repo_paths_for_namespace(namespace) + def repo_shards_for_namespace(namespace) projects_for_namespace(namespace).distinct.select(:repository_storage) - .map(&:repository_storage_path) + .map(&:repository_storage) end def projects_for_namespace(namespace) diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 979225dd216..827aeb12a02 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -51,7 +51,7 @@ module Gitlab end def move_repository(project, old_path, new_path) - unless gitlab_shell.mv_repository(project.repository_storage_path, + unless gitlab_shell.mv_repository(project.repository_storage, old_path, new_path) Rails.logger.error "Error moving #{old_path} to #{new_path}" diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb index eb3d8819239..92f6c45ce25 100644 --- a/lib/gitlab/git/raw_diff_change.rb +++ b/lib/gitlab/git/raw_diff_change.rb @@ -38,7 +38,9 @@ module Gitlab end def extract_operation - case @raw_operation&.first(1) + return :unknown unless @raw_operation + + case @raw_operation[0] when 'A' :added when 'C' diff --git a/lib/gitlab/git/remote_repository.rb b/lib/gitlab/git/remote_repository.rb index 6bd6e58feeb..f40e59a8dd0 100644 --- a/lib/gitlab/git/remote_repository.rb +++ b/lib/gitlab/git/remote_repository.rb @@ -12,7 +12,7 @@ module Gitlab # class. # class RemoteRepository - attr_reader :path, :relative_path, :gitaly_repository + attr_reader :relative_path, :gitaly_repository def initialize(repository) @relative_path = repository.relative_path @@ -21,7 +21,6 @@ module Gitlab # These instance variables will not be available in gitaly-ruby, where # we have no disk access to this repository. @repository = repository - @path = repository.path end def empty? @@ -69,6 +68,10 @@ module Gitlab env end + def path + @repository.path + end + private # Must return an object that responds to 'address' and 'storage'. diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 5a6e2e0b937..de0044fc149 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -142,15 +142,7 @@ module Gitlab end def exists? - Gitlab::GitalyClient.migrate(:repository_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| - if enabled - gitaly_repository_client.exists? - else - circuit_breaker.perform do - File.exist?(File.join(path, 'refs')) - end - end - end + gitaly_repository_client.exists? end # Returns an Array of branch names @@ -399,18 +391,6 @@ module Gitlab nil end - def archive_prefix(ref, sha, append_sha:) - append_sha = (ref != sha) if append_sha.nil? - - project_name = self.name.chomp('.git') - formatted_ref = ref.tr('/', '-') - - prefix_segments = [project_name, formatted_ref] - prefix_segments << sha if append_sha - - prefix_segments.join('-') - end - def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:) ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) @@ -421,12 +401,44 @@ module Gitlab { 'RepoPath' => path, 'ArchivePrefix' => prefix, - 'ArchivePath' => archive_file_path(prefix, storage_path, format), + 'ArchivePath' => archive_file_path(storage_path, commit.id, prefix, format), 'CommitId' => commit.id } end - def archive_file_path(name, storage_path, format = "tar.gz") + # This is both the filename of the archive (missing the extension) and the + # name of the top-level member of the archive under which all files go + # + # FIXME: The generated prefix is incorrect for projects with hashed + # storage enabled + def archive_prefix(ref, sha, append_sha:) + append_sha = (ref != sha) if append_sha.nil? + + project_name = self.name.chomp('.git') + formatted_ref = ref.tr('/', '-') + + prefix_segments = [project_name, formatted_ref] + prefix_segments << sha if append_sha + + prefix_segments.join('-') + end + private :archive_prefix + + # The full path on disk where the archive should be stored. This is used + # to cache the archive between requests. + # + # The path is a global namespace, so needs to be globally unique. This is + # achieved by including `gl_repository` in the path. + # + # Archives relating to a particular ref when the SHA is not present in the + # filename must be invalidated when the ref is updated to point to a new + # SHA. This is achieved by including the SHA in the path. + # + # As this is a full path on disk, it is not "cloud native". This should + # be resolved by either removing the cache, or moving the implementation + # into Gitaly and removing the ArchivePath parameter from the git-archive + # senddata response. + def archive_file_path(storage_path, sha, name, format = "tar.gz") # Build file path return nil unless name @@ -444,8 +456,9 @@ module Gitlab end file_name = "#{name}.#{extension}" - File.join(storage_path, self.name, file_name) + File.join(storage_path, self.gl_repository, sha, file_name) end + private :archive_file_path # Return repo size in megabytes def size @@ -1187,6 +1200,8 @@ module Gitlab if is_enabled gitaly_fetch_ref(source_repository, source_ref: source_ref, target_ref: target_ref) else + # When removing this code, also remove source_repository#path + # to remove deprecated method calls local_fetch_ref(source_repository.path, source_ref: source_ref, target_ref: target_ref) end end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index bf5a491e28d..498187997e1 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -142,7 +142,7 @@ module Gitlab :repository_service, :is_rebase_in_progress, request, - timeout: GitalyClient.default_timeout + timeout: GitalyClient.fast_timeout ) response.in_progress @@ -159,7 +159,7 @@ module Gitlab :repository_service, :is_squash_in_progress, request, - timeout: GitalyClient.default_timeout + timeout: GitalyClient.fast_timeout ) response.in_progress diff --git a/lib/gitlab/pages_client.rb b/lib/gitlab/pages_client.rb new file mode 100644 index 00000000000..7b358a3bd1b --- /dev/null +++ b/lib/gitlab/pages_client.rb @@ -0,0 +1,117 @@ +module Gitlab + class PagesClient + class << self + attr_reader :certificate, :token + + def call(service, rpc, request, timeout: nil) + kwargs = request_kwargs(timeout) + stub(service).__send__(rpc, request, kwargs) # rubocop:disable GitlabSecurity/PublicSend + end + + # This function is not thread-safe. Call it from an initializer only. + def read_or_create_token + @token = read_token + rescue Errno::ENOENT + # TODO: uncomment this when omnibus knows how to write the token file for us + # https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466 + # + # write_token(SecureRandom.random_bytes(64)) + # + # # Read from disk in case someone else won the race and wrote the file + # # before us. If this fails again let the exception bubble up. + # @token = read_token + end + + # This function is not thread-safe. Call it from an initializer only. + def load_certificate + cert_path = config.certificate + return unless cert_path.present? + + @certificate = File.read(cert_path) + end + + def ping + request = Grpc::Health::V1::HealthCheckRequest.new + call(:health_check, :check, request, timeout: 5.seconds) + end + + private + + def request_kwargs(timeout) + encoded_token = Base64.strict_encode64(token.to_s) + metadata = { + 'authorization' => "Bearer #{encoded_token}" + } + + result = { metadata: metadata } + + return result unless timeout + + # Do not use `Time.now` for deadline calculation, since it + # will be affected by Timecop in some tests, but grpc's c-core + # uses system time instead of timecop's time, so tests will fail + # `Time.at(Process.clock_gettime(Process::CLOCK_REALTIME))` will + # circumvent timecop + deadline = Time.at(Process.clock_gettime(Process::CLOCK_REALTIME)) + timeout + result[:deadline] = deadline + + result + end + + def stub(name) + stub_class(name).new(address, grpc_creds) + end + + def stub_class(name) + if name == :health_check + Grpc::Health::V1::Health::Stub + else + # TODO use pages namespace + Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub) + end + end + + def address + addr = config.address + addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp' + addr + end + + def grpc_creds + if address.start_with?('unix:') + :this_channel_is_insecure + elsif @certificate + GRPC::Core::ChannelCredentials.new(@certificate) + else + # Use system certificate pool + GRPC::Core::ChannelCredentials.new + end + end + + def config + Gitlab.config.pages.admin + end + + def read_token + File.read(token_path) + end + + def token_path + Rails.root.join('.gitlab_pages_secret').to_s + end + + def write_token(new_token) + Tempfile.open(File.basename(token_path), File.dirname(token_path), encoding: 'ascii-8bit') do |f| + f.write(new_token) + f.close + File.link(f.path, token_path) + end + rescue Errno::EACCES => ex + # TODO stop rescuing this exception in GitLab 11.0 https://gitlab.com/gitlab-org/gitlab-ce/issues/45672 + Rails.logger.error("Could not write pages admin token file: #{ex}") + rescue Errno::EEXIST + # Another process wrote the token file concurrently with us. Use their token, not ours. + end + end + end +end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index ac4ac537a8a..156115f8a8f 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -65,11 +65,11 @@ module Gitlab # Init new repository # - # storage - project's storage name + # storage - the shard key # name - project disk path # # Ex. - # create_repository("/path/to/storage", "gitlab/gitlab-ci") + # create_repository("default", "gitlab/gitlab-ci") # def create_repository(storage, name) relative_path = name.dup @@ -291,13 +291,13 @@ module Gitlab # Add empty directory for storing repositories # # Ex. - # add_namespace("/path/to/storage", "gitlab") + # add_namespace("default", "gitlab") # def add_namespace(storage, name) Gitlab::GitalyClient.migrate(:add_namespace, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled - gitaly_namespace_client(storage).add(name) + Gitlab::GitalyClient::NamespaceService.new(storage).add(name) else path = full_path(storage, name) FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name) @@ -313,13 +313,13 @@ module Gitlab # Every repository inside this directory will be removed too # # Ex. - # rm_namespace("/path/to/storage", "gitlab") + # rm_namespace("default", "gitlab") # def rm_namespace(storage, name) Gitlab::GitalyClient.migrate(:remove_namespace, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled - gitaly_namespace_client(storage).remove(name) + Gitlab::GitalyClient::NamespaceService.new(storage).remove(name) else FileUtils.rm_r(full_path(storage, name), force: true) end @@ -338,7 +338,8 @@ module Gitlab Gitlab::GitalyClient.migrate(:rename_namespace, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled - gitaly_namespace_client(storage).rename(old_name, new_name) + Gitlab::GitalyClient::NamespaceService.new(storage) + .rename(old_name, new_name) else break false if exists?(storage, new_name) || !exists?(storage, old_name) @@ -374,7 +375,8 @@ module Gitlab Gitlab::GitalyClient.migrate(:namespace_exists, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |enabled| if enabled - gitaly_namespace_client(storage).exists?(dir_name) + Gitlab::GitalyClient::NamespaceService.new(storage) + .exists?(dir_name) else File.exist?(full_path(storage, dir_name)) end @@ -398,7 +400,7 @@ module Gitlab def full_path(storage, dir_name) raise ArgumentError.new("Directory name can't be blank") if dir_name.blank? - File.join(storage, dir_name) + File.join(Gitlab.config.repositories.storages[storage].legacy_disk_path, dir_name) end def gitlab_shell_projects_path @@ -475,14 +477,6 @@ module Gitlab Bundler.with_original_env { Popen.popen(cmd, nil, vars) } end - def gitaly_namespace_client(storage_path) - storage, _value = Gitlab.config.repositories.storages.find do |storage, value| - value.legacy_disk_path == storage_path - end - - Gitlab::GitalyClient::NamespaceService.new(storage) - end - def git_timeout Gitlab.config.gitlab_shell.git_timeout end diff --git a/lib/omni_auth/strategies/jwt.rb b/lib/omni_auth/strategies/jwt.rb new file mode 100644 index 00000000000..2349b2a28aa --- /dev/null +++ b/lib/omni_auth/strategies/jwt.rb @@ -0,0 +1,62 @@ +require 'omniauth' +require 'jwt' + +module OmniAuth + module Strategies + class JWT + ClaimInvalid = Class.new(StandardError) + + include OmniAuth::Strategy + + args [:secret] + + option :secret, nil + option :algorithm, 'HS256' + option :uid_claim, 'email' + option :required_claims, %w(name email) + option :info_map, { name: "name", email: "email" } + option :auth_url, nil + option :valid_within, nil + + uid { decoded[options.uid_claim] } + + extra do + { raw_info: decoded } + end + + info do + options.info_map.each_with_object({}) do |(k, v), h| + h[k.to_s] = decoded[v.to_s] + end + end + + def request_phase + redirect options.auth_url + end + + def decoded + @decoded ||= ::JWT.decode(request.params['jwt'], options.secret, options.algorithm).first + + (options.required_claims || []).each do |field| + raise ClaimInvalid, "Missing required '#{field}' claim" unless @decoded.key?(field.to_s) + end + + raise ClaimInvalid, "Missing required 'iat' claim" if options.valid_within && !@decoded["iat"] + + if options.valid_within && (Time.now.to_i - @decoded["iat"]).abs > options.valid_within + raise ClaimInvalid, "'iat' timestamp claim is too skewed from present" + end + + @decoded + end + + def callback_phase + super + rescue ClaimInvalid => e + fail! :claim_invalid, e + end + end + + class Jwt < JWT; end + end +end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index abef8cd2bcc..c04dae7446f 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -427,10 +427,7 @@ namespace :gitlab do user = User.find_by(username: username) if user repo_dirs = user.authorized_projects.map do |p| - File.join( - p.repository_storage_path, - "#{p.disk_path}.git" - ) + p.repository.path_to_repo end repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) } diff --git a/lib/tasks/gitlab/list_repos.rake b/lib/tasks/gitlab/list_repos.rake index d7f28691098..b854c34a8e5 100644 --- a/lib/tasks/gitlab/list_repos.rake +++ b/lib/tasks/gitlab/list_repos.rake @@ -10,9 +10,8 @@ namespace :gitlab do end scope.find_each do |project| - base = File.join(project.repository_storage_path, project.disk_path) - puts base + '.git' - puts base + '.wiki.git' + puts project.repository.path_to_repo + puts project.wiki.repository.path_to_repo end end end diff --git a/lib/tasks/gitlab/pages.rake b/lib/tasks/gitlab/pages.rake new file mode 100644 index 00000000000..100e480bd66 --- /dev/null +++ b/lib/tasks/gitlab/pages.rake @@ -0,0 +1,9 @@ +namespace :gitlab do + namespace :pages do + desc 'Ping the pages admin API' + task admin_ping: :gitlab_environment do + Gitlab::PagesClient.ping + puts "OK: gitlab-pages admin API is reachable" + end + end +end diff --git a/package.json b/package.json index 96dee321548..8d0473069c3 100644 --- a/package.json +++ b/package.json @@ -83,11 +83,11 @@ "underscore": "^1.8.3", "url-loader": "^0.6.2", "visibilityjs": "^1.2.4", - "vue": "^2.5.13", + "vue": "^2.5.16", "vue-loader": "^14.1.1", - "vue-resource": "^1.3.5", + "vue-resource": "^1.5.0", "vue-router": "^3.0.1", - "vue-template-compiler": "^2.5.13", + "vue-template-compiler": "^2.5.16", "vue-virtual-scroll-list": "^1.2.5", "vuex": "^3.0.1", "webpack": "^3.11.0", diff --git a/qa/Dockerfile b/qa/Dockerfile index ed2ee73bea0..77cee9c5461 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.4 +FROM ruby:2.4-stretch LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" ENV DEBIAN_FRONTEND noninteractive diff --git a/qa/Gemfile b/qa/Gemfile index c3e61568f3d..d69c71003ae 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -6,5 +6,4 @@ gem 'capybara-screenshot', '~> 1.0.18' gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' gem 'selenium-webdriver', '~> 3.8.0' -gem 'net-ssh', require: false gem 'airborne', '~> 0.2.13' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 51d2e4d7a10..565adac7499 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -46,7 +46,6 @@ GEM mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.11.1) - net-ssh (4.1.0) netrc (0.11.0) nokogiri (1.8.1) mini_portile2 (~> 2.3.0) @@ -98,7 +97,6 @@ DEPENDENCIES airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) - net-ssh pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) @@ -11,9 +11,15 @@ module QA autoload :Scenario, 'qa/runtime/scenario' autoload :Browser, 'qa/runtime/browser' autoload :Env, 'qa/runtime/env' - autoload :RSAKey, 'qa/runtime/rsa_key' autoload :Address, 'qa/runtime/address' autoload :API, 'qa/runtime/api' + + module Key + autoload :Base, 'qa/runtime/key/base' + autoload :RSA, 'qa/runtime/key/rsa' + autoload :ECDSA, 'qa/runtime/key/ecdsa' + autoload :ED25519, 'qa/runtime/key/ed25519' + end end ## diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb index 6e8905cde78..795f1f9cb1a 100644 --- a/qa/qa/factory/repository/push.rb +++ b/qa/qa/factory/repository/push.rb @@ -2,7 +2,10 @@ module QA module Factory module Repository class Push < Factory::Base - attr_writer :file_name, :file_content, :commit_message, :branch_name, :new_branch + attr_accessor :file_name, :file_content, :commit_message, + :branch_name, :new_branch + + attr_writer :remote_branch dependency Factory::Resource::Project, as: :project do |project| project.name = 'project-with-code' @@ -17,23 +20,32 @@ module QA @new_branch = true end + def remote_branch + @remote_branch ||= branch_name + end + def fabricate! project.visit! Git::Repository.perform do |repository| - repository.location = Page::Project::Show.act do + repository.uri = Page::Project::Show.act do choose_repository_clone_http - repository_location + repository_location.uri end repository.use_default_credentials repository.clone repository.configure_identity('GitLab QA', 'root@gitlab.com') - repository.checkout(@branch_name) unless @new_branch - repository.add_file(@file_name, @file_content) - repository.commit(@commit_message) - repository.push_changes(@branch_name) + if new_branch + repository.checkout_new_branch(branch_name) + else + repository.checkout(branch_name) + end + + repository.add_file(file_name, file_content) + repository.commit(commit_message) + repository.push_changes("#{branch_name}:#{remote_branch}") end end end diff --git a/qa/qa/factory/resource/branch.rb b/qa/qa/factory/resource/branch.rb index d0ef142e90d..1785441f5a8 100644 --- a/qa/qa/factory/resource/branch.rb +++ b/qa/qa/factory/resource/branch.rb @@ -2,7 +2,8 @@ module QA module Factory module Resource class Branch < Factory::Base - attr_accessor :project, :branch_name, :allow_to_push, :protected + attr_accessor :project, :branch_name, + :allow_to_push, :allow_to_merge, :protected dependency Factory::Resource::Project, as: :project do |project| project.name = 'protected-branch-project' @@ -23,6 +24,7 @@ module QA def initialize @branch_name = 'test/branch' @allow_to_push = true + @allow_to_merge = true @protected = false end @@ -39,7 +41,9 @@ module QA resource.project = project resource.file_name = 'README.md' resource.commit_message = 'Add readme' - resource.branch_name = "master:#{@branch_name}" + resource.branch_name = 'master' + resource.new_branch = false + resource.remote_branch = @branch_name end Page::Project::Show.act { wait_for_push } @@ -63,7 +67,22 @@ module QA page.allow_no_one_to_push end + if allow_to_merge + page.allow_devs_and_masters_to_merge + else + page.allow_no_one_to_merge + end + + page.wait(reload: false) do + !page.first('.btn-create').disabled? + end + page.protect_branch + + # Wait for page load, which resets the expanded sections + page.wait(reload: false) do + !page.has_content?('Collapse') + end end end end diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb index ff0b4a46b77..ea8a3ad687d 100644 --- a/qa/qa/factory/resource/deploy_key.rb +++ b/qa/qa/factory/resource/deploy_key.rb @@ -4,15 +4,15 @@ module QA class DeployKey < Factory::Base attr_accessor :title, :key - product :title do + product :fingerprint do |resource| Page::Project::Settings::Repository.act do - expand_deploy_keys(&:key_title) - end - end + expand_deploy_keys do |key| + key_offset = key.key_titles.index do |title| + title.text == resource.title + end - product :fingerprint do - Page::Project::Settings::Repository.act do - expand_deploy_keys(&:key_fingerprint) + key.key_fingerprints[key_offset].text + end end end diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb index 539fe6b8a70..7588ac5735d 100644 --- a/qa/qa/factory/resource/merge_request.rb +++ b/qa/qa/factory/resource/merge_request.rb @@ -24,12 +24,14 @@ module QA dependency Factory::Repository::Push, as: :target do |push, factory| factory.project.visit! push.project = factory.project - push.branch_name = "master:#{factory.target_branch}" + push.branch_name = 'master' + push.remote_branch = factory.target_branch end dependency Factory::Repository::Push, as: :source do |push, factory| push.project = factory.project - push.branch_name = "#{factory.target_branch}:#{factory.source_branch}" + push.branch_name = factory.target_branch + push.remote_branch = factory.source_branch push.file_name = "added_file.txt" push.file_content = "File Added" end diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb index 7df2dc6618c..cda1b35ba6a 100644 --- a/qa/qa/factory/resource/project.rb +++ b/qa/qa/factory/resource/project.rb @@ -17,6 +17,13 @@ module QA Page::Project::Show.act { project_name } end + product :repository_ssh_location do + Page::Project::Show.act do + choose_repository_clone_ssh + repository_location + end + end + def fabricate! group.visit! diff --git a/qa/qa/factory/resource/secret_variable.rb b/qa/qa/factory/resource/secret_variable.rb index c734d739b4a..12a830da116 100644 --- a/qa/qa/factory/resource/secret_variable.rb +++ b/qa/qa/factory/resource/secret_variable.rb @@ -16,8 +16,7 @@ module QA Page::Project::Settings::CICD.perform do |setting| setting.expand_secret_variables do |page| - page.fill_variable_key(key) - page.fill_variable_value(value) + page.fill_variable(key, value) page.save_variables end diff --git a/qa/qa/git/location.rb b/qa/qa/git/location.rb index 30538388530..b74f38f3ae3 100644 --- a/qa/qa/git/location.rb +++ b/qa/qa/git/location.rb @@ -14,7 +14,7 @@ module QA def initialize(git_uri) @git_uri = git_uri @uri = - if git_uri.start_with?('ssh://') + if git_uri =~ %r{\A(?:ssh|http|https)://} URI.parse(git_uri) else *rest, path = git_uri.split(':') diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 2f9f06ba277..1367671e3ca 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -15,8 +15,7 @@ module QA end end - def location=(address) - @location = address + def uri=(address) @uri = URI(address) end @@ -43,6 +42,10 @@ module QA `git checkout "#{branch_name}"` end + def checkout_new_branch(branch_name) + `git checkout -b "#{branch_name}"` + end + def shallow_clone clone('--depth 1') end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index a313d46205d..0a69af88570 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -64,6 +64,10 @@ module QA find(element_selector_css(name)) end + def all_elements(name) + all(element_selector_css(name)) + end + def click_element(name) find_element(name).click end diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb index 332e84724c7..4428e263bbb 100644 --- a/qa/qa/page/project/settings/deploy_keys.rb +++ b/qa/qa/page/project/settings/deploy_keys.rb @@ -42,6 +42,18 @@ module QA end end + def key_titles + within_project_deploy_keys do + all_elements(:key_title) + end + end + + def key_fingerprints + within_project_deploy_keys do + all_elements(:key_fingerprint) + end + end + private def within_project_deploy_keys diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index f3563401124..63bc3aaa2bc 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -11,6 +11,13 @@ module QA view 'app/views/projects/protected_branches/_create_protected_branch.html.haml' do element :allowed_to_push_select element :allowed_to_push_dropdown + element :allowed_to_merge_select + element :allowed_to_merge_dropdown + end + + view 'app/views/projects/protected_branches/_update_protected_branch.html.haml' do + element :allowed_to_push + element :allowed_to_merge end view 'app/views/projects/protected_branches/shared/_branches_list.html.haml' do @@ -30,11 +37,19 @@ module QA end def allow_no_one_to_push - allow_to_push('No one') + click_allow(:push, 'No one') end def allow_devs_and_masters_to_push - allow_to_push('Developers + Masters') + click_allow(:push, 'Developers + Masters') + end + + def allow_no_one_to_merge + click_allow(:merge, 'No one') + end + + def allow_devs_and_masters_to_merge + click_allow(:merge, 'Developers + Masters') end def protect_branch @@ -55,11 +70,15 @@ module QA private - def allow_to_push(text) - click_element :allowed_to_push_select + def click_allow(action, text) + click_element :"allowed_to_#{action}_select" - within_element(:allowed_to_push_dropdown) do + within_element(:"allowed_to_#{action}_dropdown") do click_on text + + wait(reload: false) do + has_css?('.is-active') + end end end end diff --git a/qa/qa/page/project/settings/secret_variables.rb b/qa/qa/page/project/settings/secret_variables.rb index c95c79f137d..d2f5d5a9060 100644 --- a/qa/qa/page/project/settings/secret_variables.rb +++ b/qa/qa/page/project/settings/secret_variables.rb @@ -7,10 +7,8 @@ module QA view 'app/views/ci/variables/_variable_row.html.haml' do element :variable_row, '.ci-variable-row-body' - element :variable_key, '.js-ci-variable-input-key' - element :variable_value, '.js-ci-variable-input-value' - element :key_placeholder, 'Input variable key' - element :value_placeholder, 'Input variable value' + element :variable_key, '.qa-ci-variable-input-key' + element :variable_value, '.qa-ci-variable-input-value' end view 'app/views/ci/variables/_index.html.haml' do @@ -18,12 +16,14 @@ module QA element :reveal_values, '.js-secret-value-reveal-button' end - def fill_variable_key(key) - fill_in('Input variable key', with: key, match: :first) - end + def fill_variable(key, value) + keys = all_elements(:ci_variable_input_key) + index = keys.size - 1 - def fill_variable_value(value) - fill_in('Input variable value', with: value, match: :first) + # After we fill the key, JS would generate another field so + # we need to use the same index to find the corresponding one. + keys[index].set(key) + all_elements(:ci_variable_input_value)[index].set(value) end def save_variables @@ -36,7 +36,7 @@ module QA def variable_value(key) within('.ci-variable-row-body', text: key) do - find('.js-ci-variable-input-value').value + find('.qa-ci-variable-input-value').value end end end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index c7e7ece792d..5bbef040330 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -38,11 +38,7 @@ module QA end def repository_location - find('#project_clone').value - end - - def repository_location_uri - Git::Location.new(repository_location) + Git::Location.new(find('#project_clone').value) end def project_name @@ -91,7 +87,7 @@ module QA end # Ensure git clone textbox was updated - repository_location.include?(detect_text) + repository_location.git_uri.include?(detect_text) end end end diff --git a/qa/qa/runtime/key/base.rb b/qa/qa/runtime/key/base.rb new file mode 100644 index 00000000000..c7e5ebada7b --- /dev/null +++ b/qa/qa/runtime/key/base.rb @@ -0,0 +1,36 @@ +module QA + module Runtime + module Key + class Base + attr_reader :name, :bits, :private_key, :public_key, :fingerprint + + def initialize(name, bits) + @name = name + @bits = bits + + Dir.mktmpdir do |dir| + path = "#{dir}/id_#{name}" + + ssh_keygen(name, bits, path) + populate_key_data(path) + end + end + + private + + def ssh_keygen(name, bits, path) + cmd = %W[ssh-keygen -t #{name} -b #{bits} -f #{path} -N] << '' + + Service::Shellout.shell(cmd) + end + + def populate_key_data(path) + @private_key = File.binread(path) + @public_key = File.binread("#{path}.pub") + @fingerprint = + `ssh-keygen -l -E md5 -f #{path} | cut -d' ' -f2 | cut -d: -f2-`.chomp + end + end + end + end +end diff --git a/qa/qa/runtime/key/ecdsa.rb b/qa/qa/runtime/key/ecdsa.rb new file mode 100644 index 00000000000..20adad45913 --- /dev/null +++ b/qa/qa/runtime/key/ecdsa.rb @@ -0,0 +1,12 @@ +# rubocop:disable Naming/FileName +module QA + module Runtime + module Key + class ECDSA < Base + def initialize(bits = 521) + super('ecdsa', bits) + end + end + end + end +end diff --git a/qa/qa/runtime/key/ed25519.rb b/qa/qa/runtime/key/ed25519.rb new file mode 100644 index 00000000000..63865c1cee5 --- /dev/null +++ b/qa/qa/runtime/key/ed25519.rb @@ -0,0 +1,12 @@ +# rubocop:disable Naming/FileName +module QA + module Runtime + module Key + class ED25519 < Base + def initialize + super('ed25519', 256) + end + end + end + end +end diff --git a/qa/qa/runtime/key/rsa.rb b/qa/qa/runtime/key/rsa.rb new file mode 100644 index 00000000000..d94bde52325 --- /dev/null +++ b/qa/qa/runtime/key/rsa.rb @@ -0,0 +1,11 @@ +module QA + module Runtime + module Key + class RSA < Base + def initialize(bits = 4096) + super('rsa', bits) + end + end + end + end +end diff --git a/qa/qa/runtime/rsa_key.rb b/qa/qa/runtime/rsa_key.rb deleted file mode 100644 index fcd7dcc4f02..00000000000 --- a/qa/qa/runtime/rsa_key.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'net/ssh' -require 'forwardable' - -module QA - module Runtime - class RSAKey - extend Forwardable - - attr_reader :key - def_delegators :@key, :fingerprint, :to_pem - - def initialize(bits = 4096) - @key = OpenSSL::PKey::RSA.new(bits) - end - - def public_key - @public_key ||= "#{key.ssh_type} #{[key.to_blob].pack('m0')}" - end - end - end -end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 76fb2af6319..1ca9504bb33 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -5,6 +5,8 @@ module QA module Shellout CommandError = Class.new(StandardError) + module_function + ## # TODO, make it possible to use generic QA framework classes # as a library - gitlab-org/gitlab-qa#94 @@ -12,7 +14,7 @@ module QA def shell(command) puts "Executing `#{command}`" - Open3.popen2e(command) do |_in, out, wait| + Open3.popen2e(*command) do |_in, out, wait| out.each { |line| puts line } if wait.value.exited? && wait.value.exitstatus.nonzero? diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb index b9998dda895..de53613dee1 100644 --- a/qa/qa/specs/features/project/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb @@ -4,7 +4,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - key = Runtime::RSAKey.new + key = Runtime::Key::RSA.new deploy_key_title = 'deploy key title' deploy_key_value = key.public_key @@ -13,7 +13,6 @@ module QA resource.key = deploy_key_value end - expect(deploy_key.title).to eq(deploy_key_title) expect(deploy_key.fingerprint).to eq(key.fingerprint) end end diff --git a/qa/qa/specs/features/project/deploy_key_clone_spec.rb b/qa/qa/specs/features/project/deploy_key_clone_spec.rb index 19d3c83758a..98ea86bf75e 100644 --- a/qa/qa/specs/features/project/deploy_key_clone_spec.rb +++ b/qa/qa/specs/features/project/deploy_key_clone_spec.rb @@ -2,79 +2,103 @@ require 'digest/sha1' module QA feature 'cloning code using a deploy key', :core, :docker do - let(:runner_name) { "qa-runner-#{Time.now.to_i}" } - let(:key) { Runtime::RSAKey.new } + def login + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + end - given(:project) do - Factory::Resource::Project.fabricate! do |resource| + before(:all) do + login + + @runner_name = "qa-runner-#{Time.now.to_i}" + + @project = Factory::Resource::Project.fabricate! do |resource| resource.name = 'deploy-key-clone-project' end - end - after do - Service::Runner.new(runner_name).remove! - end - - scenario 'user sets up a deploy key to clone code using pipelines' do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + @repository_location = @project.repository_ssh_location Factory::Resource::Runner.fabricate! do |resource| - resource.project = project - resource.name = runner_name + resource.project = @project + resource.name = @runner_name resource.tags = %w[qa docker] resource.image = 'gitlab/gitlab-runner:ubuntu' end - Factory::Resource::DeployKey.fabricate! do |resource| - resource.project = project - resource.title = 'deploy key title' - resource.key = key.public_key - end + Page::Menu::Main.act { sign_out } + end - Factory::Resource::SecretVariable.fabricate! do |resource| - resource.project = project - resource.key = 'DEPLOY_KEY' - resource.value = key.to_pem - end + after(:all) do + Service::Runner.new(@runner_name).remove! + end - project.visit! + keys = [ + Runtime::Key::RSA.new(8192), + Runtime::Key::ECDSA.new(521), + Runtime::Key::ED25519.new + ] - repository_uri = Page::Project::Show.act do - choose_repository_clone_ssh - repository_location_uri - end + keys.each do |key| + scenario "user sets up a deploy key with #{key.name}(#{key.bits}) to clone code using pipelines" do + login - gitlab_ci = <<~YAML - cat-config: - script: - - mkdir -p ~/.ssh - - ssh-keyscan -p #{repository_uri.port} #{repository_uri.host} >> ~/.ssh/known_hosts - - eval $(ssh-agent -s) - - echo "$DEPLOY_KEY" | ssh-add - - - git clone #{repository_uri.git_uri} - - sha1sum #{project.name}/.gitlab-ci.yml - tags: - - qa - - docker - YAML - - Factory::Repository::Push.fabricate! do |resource| - resource.project = project - resource.file_name = '.gitlab-ci.yml' - resource.commit_message = 'Add .gitlab-ci.yml' - resource.file_content = gitlab_ci - end + Factory::Resource::DeployKey.fabricate! do |resource| + resource.project = @project + resource.title = "deploy key #{key.name}(#{key.bits})" + resource.key = key.public_key + end + + deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}" + + Factory::Resource::SecretVariable.fabricate! do |resource| + resource.project = @project + resource.key = deploy_key_name + resource.value = key.private_key + end + + gitlab_ci = <<~YAML + cat-config: + script: + - mkdir -p ~/.ssh + - ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts + - eval $(ssh-agent -s) + - ssh-add -D + - echo "$#{deploy_key_name}" | ssh-add - + - git clone #{@repository_location.git_uri} + - cd #{@project.name} + - git checkout #{deploy_key_name} + - sha1sum .gitlab-ci.yml + tags: + - qa + - docker + YAML + + Factory::Repository::Push.fabricate! do |resource| + resource.project = @project + resource.file_name = '.gitlab-ci.yml' + resource.commit_message = 'Add .gitlab-ci.yml' + resource.file_content = gitlab_ci + resource.branch_name = deploy_key_name + resource.new_branch = true + end + + sha1sum = Digest::SHA1.hexdigest(gitlab_ci) + + Page::Project::Show.act { wait_for_push } + Page::Menu::Side.act { click_ci_cd_pipelines } + Page::Project::Pipeline::Index.act { go_to_latest_pipeline } - sha1sum = Digest::SHA1.hexdigest(gitlab_ci) + Page::Project::Pipeline::Show.act do + go_to_first_job - Page::Project::Show.act { wait_for_push } - Page::Menu::Side.act { click_ci_cd_pipelines } - Page::Project::Pipeline::Index.act { go_to_latest_pipeline } - Page::Project::Pipeline::Show.act { go_to_first_job } + wait do + !has_content?('running') + end + end - Page::Project::Job::Show.perform do |job| - expect(job.output).to include(sha1sum) + Page::Project::Job::Show.perform do |job| + expect(job.output).to include(sha1sum) + end end end end diff --git a/qa/qa/specs/features/repository/clone_spec.rb b/qa/qa/specs/features/repository/clone_spec.rb index 2adb7524a46..bc9eb57bdb4 100644 --- a/qa/qa/specs/features/repository/clone_spec.rb +++ b/qa/qa/specs/features/repository/clone_spec.rb @@ -18,7 +18,7 @@ module QA end Git::Repository.perform do |repository| - repository.location = location + repository.uri = location.uri repository.use_default_credentials repository.act do @@ -33,7 +33,7 @@ module QA scenario 'user performs a deep clone' do Git::Repository.perform do |repository| - repository.location = location + repository.uri = location.uri repository.use_default_credentials repository.act { clone } @@ -44,7 +44,7 @@ module QA scenario 'user performs a shallow clone' do Git::Repository.perform do |repository| - repository.location = location + repository.uri = location.uri repository.use_default_credentials repository.act { shallow_clone } diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb index 88fa4994e32..406b2772b64 100644 --- a/qa/qa/specs/features/repository/protected_branches_spec.rb +++ b/qa/qa/specs/features/repository/protected_branches_spec.rb @@ -19,6 +19,13 @@ module QA Page::Main::Login.act { sign_in_using_credentials } end + after do + # We need to clear localStorage because we're using it for the dropdown, + # and capybara doesn't do this for us. + # https://github.com/teamcapybara/capybara/issues/1702 + Capybara.execute_script 'localStorage.clear()' + end + scenario 'user is able to protect a branch' do protected_branch = Factory::Resource::Branch.fabricate! do |resource| resource.branch_name = branch_name @@ -42,7 +49,7 @@ module QA project.visit! Git::Repository.perform do |repository| - repository.location = location + repository.uri = location.uri repository.use_default_credentials repository.act do diff --git a/qa/spec/runtime/key/ecdsa_spec.rb b/qa/spec/runtime/key/ecdsa_spec.rb new file mode 100644 index 00000000000..8951e82b9bb --- /dev/null +++ b/qa/spec/runtime/key/ecdsa_spec.rb @@ -0,0 +1,18 @@ +describe QA::Runtime::Key::ECDSA do + describe '#public_key' do + [256, 384, 521].each do |bits| + it "generates a public #{bits}-bits ECDSA key" do + subject = described_class.new(bits).public_key + + expect(subject).to match(%r{\Aecdsa\-sha2\-\w+ AAAA[0-9A-Za-z+/]+={0,3}}) + end + end + end + + describe '#new' do + it 'does not support arbitrary bits' do + expect { described_class.new(123) } + .to raise_error(QA::Service::Shellout::CommandError) + end + end +end diff --git a/qa/spec/runtime/key/ed25519_spec.rb b/qa/spec/runtime/key/ed25519_spec.rb new file mode 100644 index 00000000000..4844e7affdf --- /dev/null +++ b/qa/spec/runtime/key/ed25519_spec.rb @@ -0,0 +1,9 @@ +describe QA::Runtime::Key::ED25519 do + describe '#public_key' do + subject { described_class.new.public_key } + + it 'generates a public ED25519 key' do + expect(subject).to match(%r{\Assh\-ed25519 AAAA[0-9A-Za-z+/]}) + end + end +end diff --git a/qa/spec/runtime/rsa_key.rb b/qa/spec/runtime/key/rsa_spec.rb index 6d7ab4dcd2e..fbcc7ffdcb4 100644 --- a/qa/spec/runtime/rsa_key.rb +++ b/qa/spec/runtime/key/rsa_spec.rb @@ -1,9 +1,9 @@ -describe QA::Runtime::RSAKey do +describe QA::Runtime::Key::RSA do describe '#public_key' do subject { described_class.new.public_key } it 'generates a public RSA key' do - expect(subject).to match(%r{\Assh\-rsa AAAA[0-9A-Za-z+/]+={0,3}\z}) + expect(subject).to match(%r{\Assh\-rsa AAAA[0-9A-Za-z+/]+={0,3}}) end end end diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb index de6ef919221..c621eb69171 100644 --- a/spec/controllers/profiles_controller_spec.rb +++ b/spec/controllers/profiles_controller_spec.rb @@ -125,7 +125,7 @@ describe ProfilesController, :request_store do user.reload expect(response.status).to eq(302) - expect(gitlab_shell.exists?(project.repository_storage_path, "#{new_username}/#{project.path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{new_username}/#{project.path}.git")).to be_truthy end end @@ -143,7 +143,7 @@ describe ProfilesController, :request_store do user.reload expect(response.status).to eq(302) - expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_truthy expect(before_disk_path).to eq(project.disk_path) end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 803498d3b19..1904615778c 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -147,7 +147,8 @@ FactoryBot.define do # We delete hooks so that gitlab-shell will not try to authenticate with # an API that isn't running - FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.disk_path}.git", 'hooks')) + project.gitlab_shell.rm_directory(project.repository_storage, + File.join("#{project.disk_path}.git", 'hooks')) end end @@ -172,7 +173,8 @@ FactoryBot.define do after(:create) do |project| raise "Failed to create repository!" unless project.create_repository - FileUtils.rm_r(File.join(project.repository_storage_path, "#{project.disk_path}.git", 'refs')) + project.gitlab_shell.rm_directory(project.repository_storage, + File.join("#{project.disk_path}.git", 'refs')) end end diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index a71020002dc..ed47f7ed390 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -40,7 +40,7 @@ feature 'Dashboard Groups page', :js do expect(page).to have_content(nested_group.name) end - describe 'when filtering groups', :nested_groups do + context 'when filtering groups', :nested_groups do before do group.add_owner(user) nested_group.add_owner(user) @@ -75,7 +75,7 @@ feature 'Dashboard Groups page', :js do end end - describe 'group with subgroups', :nested_groups do + context 'with subgroups', :nested_groups do let!(:subgroup) { create(:group, :public, parent: group) } before do @@ -106,7 +106,7 @@ feature 'Dashboard Groups page', :js do end end - describe 'when using pagination' do + context 'when using pagination' do let(:group) { create(:group, created_at: 5.days.ago) } let(:group2) { create(:group, created_at: 2.days.ago) } @@ -141,4 +141,20 @@ feature 'Dashboard Groups page', :js do expect(page).not_to have_selector("#group-#{group2.id}") end end + + context 'when signed in as admin' do + let(:admin) { create(:admin) } + + it 'shows only groups admin is member of' do + group.add_owner(admin) + expect(another_group).to be_persisted + + sign_in(admin) + visit dashboard_groups_path + wait_for_requests + + expect(page).to have_content(group.name) + expect(page).not_to have_content(another_group.name) + end + end end diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb index c965b565ca3..8cd57f4f327 100644 --- a/spec/features/dashboard/milestone_filter_spec.rb +++ b/spec/features/dashboard/milestone_filter_spec.rb @@ -10,13 +10,16 @@ feature 'Dashboard > milestone filter', :js do let!(:issue) { create :issue, author: user, project: project, milestone: milestone } let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 } + dropdown_toggle_button = '.js-milestone-select' + before do sign_in(user) - visit issues_dashboard_path(author_id: user.id) end context 'default state' do it 'shows issues with Any Milestone' do + visit issues_dashboard_path(author_id: user.id) + page.all('.issue-info').each do |issue_info| expect(issue_info.text).to match(/v\d.0/) end @@ -24,31 +27,51 @@ feature 'Dashboard > milestone filter', :js do end context 'filtering by milestone' do - milestone_select_selector = '.js-milestone-select' - before do - filter_item_select('v1.0', milestone_select_selector) - find(milestone_select_selector).click + visit issues_dashboard_path(author_id: user.id) + filter_item_select('v1.0', dropdown_toggle_button) + find(dropdown_toggle_button).click wait_for_requests end it 'shows issues with Milestone v1.0' do expect(find('.issues-list')).to have_selector('.issue', count: 1) - expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + expect(find('.milestone-filter .dropdown-content')).to have_selector('a.is-active', count: 1) end it 'should not change active Milestone unless clicked' do - expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + page.within '.milestone-filter' do + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + + find('.dropdown-menu-close').click - # open & close dropdown - find('.dropdown-menu-close').click + expect(page).not_to have_selector('.dropdown.open') + + find(dropdown_toggle_button).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + expect(find('.dropdown-content a.is-active')).to have_content('v1.0') + end + end + end + + context 'with milestone filter in URL' do + before do + visit issues_dashboard_path(author_id: user.id, milestone_title: milestone.title) + find(dropdown_toggle_button).click + wait_for_requests + end + + it 'has milestone selected' do + expect(find('.milestone-filter .dropdown-content')).to have_css('.is-active', text: milestone.title) + end - expect(find('.milestone-filter')).not_to have_selector('.dropdown.open') + it 'removes milestone filter from URL after clicking "Any Milestone"' do + expect(current_url).to include("milestone_title=#{milestone.title}") - find(milestone_select_selector).click + find('.milestone-filter .dropdown-content li', text: 'Any Milestone').click - expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) - expect(find('.dropdown-content a.is-active')).to have_content('v1.0') + expect(current_url).not_to include('milestone_title') end end end diff --git a/spec/features/groups/members/manage_access_requests_spec.rb b/spec/features/groups/members/manage_access_requests_spec.rb deleted file mode 100644 index b83cd657ef7..00000000000 --- a/spec/features/groups/members/manage_access_requests_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -feature 'Groups > Members > Manage access requests' do - let(:user) { create(:user) } - let(:owner) { create(:user) } - let(:group) { create(:group, :public, :access_requestable) } - - background do - group.request_access(user) - group.add_owner(owner) - sign_in(owner) - end - - scenario 'owner can see access requests' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - end - - scenario 'owner can grant access' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - - perform_enqueued_jobs { click_on 'Grant access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was granted" - end - - scenario 'owner can deny access' do - visit group_group_members_path(group) - - expect_visible_access_request(group, user) - - perform_enqueued_jobs { click_on 'Deny access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied" - end - - def expect_visible_access_request(group, user) - expect(group.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "Users requesting access to #{group.name} 1" - expect(page).to have_content user.name - end -end diff --git a/spec/features/groups/members/master_manages_access_requests_spec.rb b/spec/features/groups/members/master_manages_access_requests_spec.rb new file mode 100644 index 00000000000..2fd6d1ec599 --- /dev/null +++ b/spec/features/groups/members/master_manages_access_requests_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +feature 'Groups > Members > Master manages access requests' do + it_behaves_like 'Master manages access requests' do + let(:entity) { create(:group, :public, :access_requestable) } + let(:members_page_path) { group_group_members_path(entity) } + end +end diff --git a/spec/features/projects/issues/user_toggles_subscription_spec.rb b/spec/features/projects/issues/user_toggles_subscription_spec.rb index 117a614b980..c2b2a193682 100644 --- a/spec/features/projects/issues/user_toggles_subscription_spec.rb +++ b/spec/features/projects/issues/user_toggles_subscription_spec.rb @@ -1,9 +1,9 @@ require "spec_helper" describe "User toggles subscription", :js do - set(:project) { create(:project_empty_repo, :public) } - set(:user) { create(:user) } - set(:issue) { create(:issue, project: project, author: user) } + let(:project) { create(:project_empty_repo, :public) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, author: user) } before do project.add_developer(user) @@ -12,7 +12,7 @@ describe "User toggles subscription", :js do visit(project_issue_path(project, issue)) end - it "unsibscribes from issue" do + it "unsubscribes from issue" do subscription_button = find(".js-issuable-subscribe-button") # Check we're subscribed. diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index 1f4eec0a317..3ac6ca4fc86 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -1,47 +1,8 @@ require 'spec_helper' feature 'Projects > Members > Master manages access requests' do - let(:user) { create(:user) } - let(:master) { create(:user) } - let(:project) { create(:project, :public, :access_requestable) } - - background do - project.request_access(user) - project.add_master(master) - sign_in(master) - end - - scenario 'master can see access requests' do - visit project_project_members_path(project) - - expect_visible_access_request(project, user) - end - - scenario 'master can grant access' do - visit project_project_members_path(project) - - expect_visible_access_request(project, user) - - perform_enqueued_jobs { click_on 'Grant access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was granted" - end - - scenario 'master can deny access' do - visit project_project_members_path(project) - - expect_visible_access_request(project, user) - - perform_enqueued_jobs { click_on 'Deny access' } - - expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was denied" - end - - def expect_visible_access_request(project, user) - expect(project.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "Users requesting access to #{project.name} 1" - expect(page).to have_content user.name + it_behaves_like 'Master manages access requests' do + let(:entity) { create(:project, :public, :access_requestable) } + let(:members_page_path) { project_project_members_path(entity) } end end diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb index 8e53ae15700..4dfc325b37e 100644 --- a/spec/features/projects/tree/upload_file_spec.rb +++ b/spec/features/projects/tree/upload_file_spec.rb @@ -35,17 +35,4 @@ feature 'Multi-file editor upload file', :js do expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt') expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline)) end - - it 'uploads image file' do - find('.add-to-tree').click - - # make the field visible so capybara can use it - execute_script('document.querySelector("#file-upload").classList.remove("hidden")') - attach_file('file-upload', img_file) - - find('.add-to-tree').click - - expect(page).to have_selector('.multi-file-tab', text: 'dk.png') - expect(page).not_to have_selector('.monaco-editor') - end end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index abc470788e1..16c0d418d98 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -2,43 +2,71 @@ require 'spec_helper' describe GroupsFinder do describe '#execute' do - let(:user) { create(:user) } - - context 'root level groups' do - let!(:private_group) { create(:group, :private) } - let!(:internal_group) { create(:group, :internal) } - let!(:public_group) { create(:group, :public) } - - context 'without a user' do - subject { described_class.new.execute } - - it { is_expected.to eq([public_group]) } + let(:user) { create(:user) } + + describe 'root level groups' do + using RSpec::Parameterized::TableSyntax + + where(:user_type, :params, :results) do + nil | { all_available: true } | %i(public_group user_public_group) + nil | { all_available: false } | %i(public_group user_public_group) + nil | {} | %i(public_group user_public_group) + + :regular | { all_available: true } | %i(public_group internal_group user_public_group user_internal_group + user_private_group) + :regular | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :regular | {} | %i(public_group internal_group user_public_group user_internal_group user_private_group) + + :external | { all_available: true } | %i(public_group user_public_group user_internal_group user_private_group) + :external | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :external | {} | %i(public_group user_public_group user_internal_group user_private_group) + + :admin | { all_available: true } | %i(public_group internal_group private_group user_public_group + user_internal_group user_private_group) + :admin | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :admin | {} | %i(public_group internal_group private_group user_public_group user_internal_group + user_private_group) end - context 'with a user' do - subject { described_class.new(user).execute } - - context 'normal user' do - it { is_expected.to contain_exactly(public_group, internal_group) } - end - - context 'external user' do - let(:user) { create(:user, external: true) } - - it { is_expected.to contain_exactly(public_group) } + with_them do + before do + # Fixme: Because of an issue: https://github.com/tomykaira/rspec-parameterized/issues/8#issuecomment-381888428 + # The groups need to be created here, not with let syntax, and also compared by name and not ids + + @groups = { + private_group: create(:group, :private, name: 'private_group'), + internal_group: create(:group, :internal, name: 'internal_group'), + public_group: create(:group, :public, name: 'public_group'), + + user_private_group: create(:group, :private, name: 'user_private_group'), + user_internal_group: create(:group, :internal, name: 'user_internal_group'), + user_public_group: create(:group, :public, name: 'user_public_group') + } + + if user_type + user = + case user_type + when :regular + create(:user) + when :external + create(:user, external: true) + when :admin + create(:user, :admin) + end + @groups.values_at(:user_private_group, :user_internal_group, :user_public_group).each do |group| + group.add_developer(user) + end + end end - context 'user is member of the private group' do - before do - private_group.add_guest(user) - end + subject { described_class.new(User.last, params).execute.to_a } - it { is_expected.to contain_exactly(public_group, internal_group, private_group) } - end + it { is_expected.to match_array(@groups.values_at(*results)) } end end context 'subgroups', :nested_groups do + let(:user) { create(:user) } let!(:parent_group) { create(:group, :public) } let!(:public_subgroup) { create(:group, :public, parent: parent_group) } let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) } diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index 2b19cda35b0..d6253b605b9 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -203,5 +203,25 @@ describe PipelinesFinder do end end end + + context 'when sha is specified' do + let!(:pipeline) { create(:ci_pipeline, project: project, sha: '97de212e80737a608d939f648d959671fb0a0142') } + + context 'when sha exists' do + let(:params) { { sha: '97de212e80737a608d939f648d959671fb0a0142' } } + + it 'returns matched pipelines' do + is_expected.to eq([pipeline]) + end + end + + context 'when sha does not exist' do + let(:params) { { sha: 'invalid-sha' } } + + it 'returns empty' do + is_expected.to be_empty + end + end + end end end diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index c94fedd615b..120b23e66ac 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -18,6 +18,30 @@ describe AuthHelper do end end + describe "providers_for_base_controller" do + it 'returns all enabled providers from devise' do + allow(helper).to receive(:auth_providers) { [:twitter, :github] } + expect(helper.providers_for_base_controller).to include(*[:twitter, :github]) + end + + it 'excludes ldap providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.providers_for_base_controller).not_to include(:ldapmain) + end + end + + describe "form_based_providers" do + it 'includes LDAP providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.form_based_providers).to eq %i(ldapmain) + end + + it 'includes crowd provider' do + allow(helper).to receive(:auth_providers) { [:twitter, :crowd] } + expect(helper.form_based_providers).to eq %i(crowd) + end + end + describe 'enabled_button_based_providers' do before do allow(helper).to receive(:auth_providers) { [:twitter, :github] } diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 1fa194fe1b8..8de615ad8c2 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -242,4 +242,29 @@ describe BlobHelper do end end end + + describe '#ide_edit_path' do + let(:project) { create(:project) } + + around do |example| + old_script_name = Rails.application.routes.default_url_options[:script_name] + begin + example.run + ensure + Rails.application.routes.default_url_options[:script_name] = old_script_name + end + end + + it 'returns full IDE path' do + Rails.application.routes.default_url_options[:script_name] = nil + + expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/") + end + + it 'returns IDE path without relative_url_root' do + Rails.application.routes.default_url_options[:script_name] = "/gitlab" + + expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/") + end + end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 46c55da24f8..8fcb175416f 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -274,16 +274,16 @@ describe ProjectsHelper do end end - describe '#sanitized_import_error' do + describe '#sanitizerepo_repo_path' do let(:project) { create(:project, :repository) } + let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path } before do - allow(project).to receive(:repository_storage_path).and_return('/base/repo/path') allow(Settings.shared).to receive(:[]).with('path').and_return('/base/repo/export/path') end it 'removes the repo path' do - repo = '/base/repo/path/namespace/test.git' + repo = "#{storage_path}/namespace/test.git" import_error = "Could not clone #{repo}\n" expect(sanitize_repo_path(project, import_error)).to eq('Could not clone [REPOS PATH]/namespace/test.git') diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js index e08abe7d849..7b637f37eba 100644 --- a/spec/javascripts/ide/components/new_dropdown/index_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js @@ -32,12 +32,8 @@ describe('new dropdown component', () => { it('renders new file, upload and new directory links', () => { expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); - expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe( - 'Upload file', - ); - expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe( - 'New directory', - ); + expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file'); + expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory'); }); describe('createNewItem', () => { @@ -81,4 +77,18 @@ describe('new dropdown component', () => { .catch(done.fail); }); }); + + describe('dropdownOpen', () => { + it('scrolls dropdown into view', done => { + spyOn(vm.$refs.dropdownMenu, 'scrollIntoView'); + + vm.dropdownOpen = true; + + setTimeout(() => { + expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalled(); + + done(); + }); + }); + }); }); diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js index a6e1e5a0d35..f362ed4db65 100644 --- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js @@ -25,25 +25,17 @@ describe('new file modal component', () => { it(`sets modal title as ${type}`, () => { const title = type === 'tree' ? 'directory' : 'file'; - expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe( - `Create new ${title}`, - ); + expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`); }); it(`sets button label as ${type}`, () => { const title = type === 'tree' ? 'directory' : 'file'; - expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe( - `Create ${title}`, - ); + expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`); }); it(`sets form label as ${type}`, () => { - const title = type === 'tree' ? 'Directory' : 'File'; - - expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe( - `${title} name`, - ); + expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe('Name'); }); describe('createEntryInStore', () => { diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index b6eadf56f9d..a64af5b941b 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -1,4 +1,9 @@ -import actions, { stageAllChanges, unstageAllChanges, toggleFileFinder } from '~/ide/stores/actions'; +import actions, { + stageAllChanges, + unstageAllChanges, + toggleFileFinder, + updateTempFlagForEntry, +} from '~/ide/stores/actions'; import store from '~/ide/stores'; import * as types from '~/ide/stores/mutation_types'; import router from '~/ide/ide_router'; @@ -340,6 +345,49 @@ describe('Multi-file store actions', () => { }); }); + describe('updateTempFlagForEntry', () => { + it('commits UPDATE_TEMP_FLAG', done => { + const f = { + ...file(), + path: 'test', + tempFile: true, + }; + store.state.entries[f.path] = f; + + testAction( + updateTempFlagForEntry, + { file: f, tempFile: false }, + store.state, + [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }], + [], + done, + ); + }); + + it('commits UPDATE_TEMP_FLAG and dispatches for parent', done => { + const parent = { + ...file(), + path: 'testing', + }; + const f = { + ...file(), + path: 'test', + parentPath: 'testing', + }; + store.state.entries[parent.path] = parent; + store.state.entries[f.path] = f; + + testAction( + updateTempFlagForEntry, + { file: f, tempFile: false }, + store.state, + [{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }], + [{ type: 'updateTempFlagForEntry', payload: { file: parent, tempFile: false } }], + done, + ); + }); + }); + describe('toggleFileFinder', () => { it('commits TOGGLE_FILE_FINDER', done => { testAction( diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 575039e755e..997711d1e19 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -87,6 +87,28 @@ describe('Multi-file store mutations', () => { }); }); + describe('UPDATE_TEMP_FLAG', () => { + beforeEach(() => { + localState.entries.test = { + ...file(), + tempFile: true, + changed: true, + }; + }); + + it('updates tempFile flag', () => { + mutations.UPDATE_TEMP_FLAG(localState, { path: 'test', tempFile: false }); + + expect(localState.entries.test.tempFile).toBe(false); + }); + + it('updates changed flag', () => { + mutations.UPDATE_TEMP_FLAG(localState, { path: 'test', tempFile: false }); + + expect(localState.entries.test.changed).toBe(false); + }); + }); + describe('TOGGLE_FILE_FINDER', () => { it('updates fileFindVisible', () => { mutations.TOGGLE_FILE_FINDER(localState, true); diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index 8b6e8b24f00..fcd7bea3f6d 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -138,7 +138,7 @@ const RESPONSE_MAP = { }, { id: 20, - name_with_namespace: 'foo / bar', + name_with_namespace: '<img src=x onerror=alert(document.domain)> foo / bar', }, ], }, diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js index a3fb965fbab..00847df4b60 100644 --- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js +++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js @@ -69,6 +69,15 @@ describe('SidebarMoveIssue', function () { expect($.fn.glDropdown).toHaveBeenCalled(); }); + + it('escapes html from project name', (done) => { + this.$toggleButton.dropdown('toggle'); + + setTimeout(() => { + expect(this.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual('<img src=x onerror=alert(document.domain)> foo / bar'); + done(); + }); + }); }); describe('onConfirmClicked', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js index dd1d62cd4ed..a0a74648328 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js @@ -4,21 +4,37 @@ import eventHub from '~/vue_merge_request_widget/event_hub'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; describe('MRWidgetFailedToMerge', () => { + const dummyIntervalId = 1337; let Component; let vm; beforeEach(() => { Component = Vue.extend(failedToMergeComponent); spyOn(eventHub, '$emit'); - vm = mountComponent(Component, { mr: { - mergeError: 'Merge error happened.', - } }); + spyOn(window, 'setInterval').and.returnValue(dummyIntervalId); + spyOn(window, 'clearInterval').and.stub(); + vm = mountComponent(Component, { + mr: { + mergeError: 'Merge error happened.', + }, + }); }); afterEach(() => { vm.$destroy(); }); + it('sets interval to refresh', () => { + expect(window.setInterval).toHaveBeenCalledWith(vm.updateTimer, 1000); + expect(vm.intervalId).toBe(dummyIntervalId); + }); + + it('clears interval when destroying ', () => { + vm.$destroy(); + + expect(window.clearInterval).toHaveBeenCalledWith(dummyIntervalId); + }); + describe('computed', () => { describe('timerText', () => { it('should return correct timer text', () => { @@ -65,11 +81,13 @@ describe('MRWidgetFailedToMerge', () => { }); describe('while it is refreshing', () => { - it('renders Refresing now', (done) => { + it('renders Refresing now', done => { vm.isRefreshing = true; Vue.nextTick(() => { - expect(vm.$el.querySelector('.js-refresh-label').textContent.trim()).toEqual('Refreshing now'); + expect(vm.$el.querySelector('.js-refresh-label').textContent.trim()).toEqual( + 'Refreshing now', + ); done(); }); }); @@ -78,11 +96,15 @@ describe('MRWidgetFailedToMerge', () => { describe('while it is not regresing', () => { it('renders warning icon and disabled merge button', () => { expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull(); - expect(vm.$el.querySelector('.js-disabled-merge-button').getAttribute('disabled')).toEqual('disabled'); + expect(vm.$el.querySelector('.js-disabled-merge-button').getAttribute('disabled')).toEqual( + 'disabled', + ); }); it('renders given error', () => { - expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual('Merge error happened..'); + expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual( + 'Merge error happened..', + ); }); it('renders refresh button', () => { @@ -90,13 +112,13 @@ describe('MRWidgetFailedToMerge', () => { }); it('renders remaining time', () => { - expect( - vm.$el.querySelector('.has-custom-error').textContent.trim(), - ).toEqual('Refreshing in 10 seconds to show the updated status...'); + expect(vm.$el.querySelector('.has-custom-error').textContent.trim()).toEqual( + 'Refreshing in 10 seconds to show the updated status...', + ); }); }); - it('should just generic merge failed message if merge_error is not available', (done) => { + it('should just generic merge failed message if merge_error is not available', done => { vm.mr.mergeError = null; Vue.nextTick(() => { @@ -106,7 +128,7 @@ describe('MRWidgetFailedToMerge', () => { }); }); - it('should show refresh label when refresh requested', (done) => { + it('should show refresh label when refresh requested', done => { vm.refresh(); Vue.nextTick(() => { expect(vm.$el.innerText).not.toContain('Merge failed. Refreshing'); diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index eb4b9d8b12f..5c8a19a53bc 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do let!(:admin) { create(:admin) } let!(:base_dir) { Dir.mktmpdir + '/' } let(:bare_repository) { Gitlab::BareRepositoryImport::Repository.new(base_dir, File.join(base_dir, "#{project_path}.git")) } + let(:gitlab_shell) { Gitlab::Shell.new } subject(:importer) { described_class.new(admin, bare_repository) } @@ -84,12 +85,14 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do importer.create_project_if_needed project = Project.find_by_full_path(project_path) - repo_path = File.join(project.repository_storage_path, project.disk_path + '.git') + repo_path = "#{project.disk_path}.git" hook_path = File.join(repo_path, 'hooks') - expect(File).to exist(repo_path) - expect(File.symlink?(hook_path)).to be true - expect(File.readlink(hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path) + expect(gitlab_shell.exists?(project.repository_storage, repo_path)).to be(true) + expect(gitlab_shell.exists?(project.repository_storage, hook_path)).to be(true) + + full_hook_path = File.join(project.repository.path_to_repo, 'hooks') + expect(File.readlink(full_hook_path)).to eq(Gitlab.config.gitlab_shell.hooks_path) end context 'hashed storage enabled' do @@ -144,8 +147,8 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do project = Project.find_by_full_path("#{admin.full_path}/#{project_path}") - expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.git')) - expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.wiki.git')) + expect(gitlab_shell.exists?(project.repository_storage, project.disk_path + '.git')).to be(true) + expect(gitlab_shell.exists?(project.repository_storage, project.disk_path + '.wiki.git')).to be(true) end it 'moves an existing project to the correct path' do @@ -155,7 +158,9 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do project = build(:project, :legacy_storage, :repository) original_commit_count = project.repository.commit_count - bare_repo = Gitlab::BareRepositoryImport::Repository.new(project.repository_storage_path, project.repository.path) + legacy_path = Gitlab.config.repositories.storages[project.repository_storage].legacy_disk_path + + bare_repo = Gitlab::BareRepositoryImport::Repository.new(legacy_path, project.repository.path) gitlab_importer = described_class.new(admin, bare_repo) expect(gitlab_importer).to receive(:create_project).and_call_original @@ -183,7 +188,7 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do project = Project.find_by_full_path(project_path) - expect(File).to exist(File.join(project.repository_storage_path, project.disk_path + '.wiki.git')) + expect(gitlab_shell.exists?(project.repository_storage, project.disk_path + '.wiki.git')).to be(true) end end diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 0dc3705825d..1504826c7a5 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -67,7 +67,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do end after do - gitlab_shell.remove_repository(root_path, hashed_path) + gitlab_shell.remove_repository(repository_storage, hashed_path) end subject { described_class.new(root_path, repo_path) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 8312fa47cfa..4d7d6951a51 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -35,11 +35,6 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do it 'populates pipeline with stages' do expect(pipeline.stages).to be_one expect(pipeline.stages.first).not_to be_persisted - end - - it 'populates pipeline with builds' do - expect(pipeline.builds).to be_one - expect(pipeline.builds.first).not_to be_persisted expect(pipeline.stages.first.builds).to be_one expect(pipeline.stages.first.builds.first).not_to be_persisted end @@ -151,8 +146,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do step.perform! expect(pipeline.stages.size).to eq 1 - expect(pipeline.builds.size).to eq 1 - expect(pipeline.builds.first.name).to eq 'rspec' + expect(pipeline.stages.first.builds.size).to eq 1 + expect(pipeline.stages.first.builds.first.name).to eq 'rspec' end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 5acf40ea5ce..9924641f829 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -234,59 +234,72 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :tag_names end - shared_examples 'archive check' do |extenstion| - it { expect(metadata['ArchivePath']).to match(%r{tmp/gitlab-git-test.git/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}}) } - it { expect(metadata['ArchivePath']).to end_with extenstion } - end + describe '#archive_metadata' do + let(:storage_path) { '/tmp' } + let(:cache_key) { File.join(repository.gl_repository, SeedRepo::LastCommit::ID) } - describe '#archive_prefix' do - let(:project_name) { 'project-name'} + let(:append_sha) { true } + let(:ref) { 'master' } + let(:format) { nil } - before do - expect(repository).to receive(:name).once.and_return(project_name) - end + let(:expected_extension) { 'tar.gz' } + let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" } + let(:expected_path) { File.join(storage_path, cache_key, expected_filename) } + let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" } - it 'returns parameterised string for a ref containing slashes' do - prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: nil) + subject(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: append_sha) } - expect(prefix).to eq("#{project_name}-test-branch-SHA") + it 'sets RepoPath to the repository path' do + expect(metadata['RepoPath']).to eq(repository.path) end - it 'returns correct string for a ref containing dots' do - prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: nil) - - expect(prefix).to eq("#{project_name}-test.branch-SHA") + it 'sets CommitId to the commit SHA' do + expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID) end - it 'returns string with sha when append_sha is false' do - prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: false) - - expect(prefix).to eq("#{project_name}-test.branch") + it 'sets ArchivePrefix to the expected prefix' do + expect(metadata['ArchivePrefix']).to eq(expected_prefix) end - end - describe '#archive' do - let(:metadata) { repository.archive_metadata('master', '/tmp', append_sha: true) } + it 'sets ArchivePath to the expected globally-unique path' do + # This is really important from a security perspective. Think carefully + # before changing it: https://gitlab.com/gitlab-org/gitlab-ce/issues/45689 + expect(expected_path).to include(File.join(repository.gl_repository, SeedRepo::LastCommit::ID)) - it_should_behave_like 'archive check', '.tar.gz' - end - - describe '#archive_zip' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip', append_sha: true) } + expect(metadata['ArchivePath']).to eq(expected_path) + end - it_should_behave_like 'archive check', '.zip' - end + context 'append_sha varies archive path and filename' do + where(:append_sha, :ref, :expected_prefix) do + sha = SeedRepo::LastCommit::ID - describe '#archive_bz2' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2', append_sha: true) } + true | 'master' | "gitlab-git-test-master-#{sha}" + true | sha | "gitlab-git-test-#{sha}-#{sha}" + false | 'master' | "gitlab-git-test-master" + false | sha | "gitlab-git-test-#{sha}" + nil | 'master' | "gitlab-git-test-master-#{sha}" + nil | sha | "gitlab-git-test-#{sha}" + end - it_should_behave_like 'archive check', '.tar.bz2' - end + with_them do + it { expect(metadata['ArchivePrefix']).to eq(expected_prefix) } + it { expect(metadata['ArchivePath']).to eq(expected_path) } + end + end - describe '#archive_fallback' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup', append_sha: true) } + context 'format varies archive path and filename' do + where(:format, :expected_extension) do + nil | 'tar.gz' + 'madeup' | 'tar.gz' + 'tbz2' | 'tar.bz2' + 'zip' | 'zip' + end - it_should_behave_like 'archive check', '.tar.gz' + with_them do + it { expect(metadata['ArchivePrefix']).to eq(expected_prefix) } + it { expect(metadata['ArchivePath']).to eq(expected_path) } + end + end end describe '#size' do @@ -689,7 +702,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end after do - Gitlab::Shell.new.remove_repository(storage_path, 'my_project') + Gitlab::Shell.new.remove_repository('default', 'my_project') end shared_examples 'repository mirror fecthing' do diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb index 5c01ee0ebb8..f99f198da33 100644 --- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -24,8 +24,8 @@ describe Gitlab::ImportExport::WikiRestorer do after do FileUtils.rm_rf(export_path) - Gitlab::Shell.new.remove_repository(project_with_wiki.wiki.repository_storage_path, project_with_wiki.wiki.disk_path) - Gitlab::Shell.new.remove_repository(project.wiki.repository_storage_path, project.wiki.disk_path) + Gitlab::Shell.new.remove_repository(project_with_wiki.wiki.repository_storage, project_with_wiki.wiki.disk_path) + Gitlab::Shell.new.remove_repository(project.wiki.repository_storage, project.wiki.disk_path) end it 'restores the wiki repo successfully' do diff --git a/spec/lib/gitlab/pages_client_spec.rb b/spec/lib/gitlab/pages_client_spec.rb new file mode 100644 index 00000000000..da6d26f4aee --- /dev/null +++ b/spec/lib/gitlab/pages_client_spec.rb @@ -0,0 +1,172 @@ +require 'spec_helper' + +describe Gitlab::PagesClient do + subject { described_class } + + describe '.token' do + it 'returns the token as it is on disk' do + pending 'add omnibus support for generating the secret file https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466' + expect(subject.token).to eq(File.read('.gitlab_pages_secret')) + end + end + + describe '.read_or_create_token' do + subject { described_class.read_or_create_token } + let(:token_path) { 'tmp/tests/gitlab-pages-secret' } + before do + allow(described_class).to receive(:token_path).and_return(token_path) + FileUtils.rm_f(token_path) + end + + it 'uses the existing token file if it exists' do + secret = 'existing secret' + File.write(token_path, secret) + + subject + expect(described_class.token).to eq(secret) + end + + it 'creates one if none exists' do + pending 'add omnibus support for generating the secret file https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2466' + + old_token = described_class.token + # sanity check + expect(File.exist?(token_path)).to eq(false) + + subject + expect(described_class.token.bytesize).to eq(64) + expect(described_class.token).not_to eq(old_token) + end + end + + describe '.write_token' do + let(:token_path) { 'tmp/tests/gitlab-pages-secret' } + before do + allow(described_class).to receive(:token_path).and_return(token_path) + FileUtils.rm_f(token_path) + end + + it 'writes the secret' do + new_secret = 'hello new secret' + expect(File.exist?(token_path)).to eq(false) + + described_class.send(:write_token, new_secret) + + expect(File.read(token_path)).to eq(new_secret) + end + + it 'does nothing if the file already exists' do + existing_secret = 'hello secret' + File.write(token_path, existing_secret) + + described_class.send(:write_token, 'new secret') + + expect(File.read(token_path)).to eq(existing_secret) + end + end + + describe '.load_certificate' do + subject { described_class.load_certificate } + before do + allow(described_class).to receive(:config).and_return(config) + end + + context 'with no certificate in the config' do + let(:config) { double(:config, certificate: '') } + + it 'does not set @certificate' do + subject + + expect(described_class.certificate).to be_nil + end + end + + context 'with a certificate path in the config' do + let(:certificate_path) { 'tmp/tests/fake-certificate' } + let(:config) { double(:config, certificate: certificate_path) } + + it 'sets @certificate' do + certificate_data = "--- BEGIN CERTIFICATE ---\nbla\n--- END CERTIFICATE ---\n" + File.write(certificate_path, certificate_data) + subject + + expect(described_class.certificate).to eq(certificate_data) + end + end + end + + describe '.request_kwargs' do + let(:token) { 'secret token' } + let(:auth_header) { 'Bearer c2VjcmV0IHRva2Vu' } + before do + allow(described_class).to receive(:token).and_return(token) + end + + context 'without timeout' do + it { expect(subject.send(:request_kwargs, nil)[:metadata]['authorization']).to eq(auth_header) } + end + + context 'with timeout' do + let(:timeout) { 1.second } + + it 'still sets the authorization header' do + expect(subject.send(:request_kwargs, timeout)[:metadata]['authorization']).to eq(auth_header) + end + + it 'sets a deadline value' do + now = Time.now + deadline = subject.send(:request_kwargs, timeout)[:deadline] + + expect(deadline).to be_between(now, now + 2 * timeout) + end + end + end + + describe '.stub' do + before do + allow(described_class).to receive(:address).and_return('unix:/foo/bar') + end + + it { expect(subject.send(:stub, :health_check)).to be_a(Grpc::Health::V1::Health::Stub) } + end + + describe '.address' do + subject { described_class.send(:address) } + + before do + allow(described_class).to receive(:config).and_return(config) + end + + context 'with a unix: address' do + let(:config) { double(:config, address: 'unix:/foo/bar') } + + it { expect(subject).to eq('unix:/foo/bar') } + end + + context 'with a tcp:// address' do + let(:config) { double(:config, address: 'tcp://localhost:1234') } + + it { expect(subject).to eq('localhost:1234') } + end + end + + describe '.grpc_creds' do + subject { described_class.send(:grpc_creds) } + + before do + allow(described_class).to receive(:config).and_return(config) + end + + context 'with a unix: address' do + let(:config) { double(:config, address: 'unix:/foo/bar') } + + it { expect(subject).to eq(:this_channel_is_insecure) } + end + + context 'with a tcp:// address' do + let(:config) { double(:config, address: 'tcp://localhost:1234') } + + it { expect(subject).to be_a(GRPC::Core::ChannelCredentials) } + end + end +end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 7f579df1c36..bf6ee4b0b59 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -447,18 +447,18 @@ describe Gitlab::Shell do let(:disk_path) { "#{project.disk_path}.git" } it 'returns true when the command succeeds' do - expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(true) + expect(gitlab_shell.exists?(project.repository_storage, disk_path)).to be(true) - expect(gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path)).to be(true) + expect(gitlab_shell.remove_repository(project.repository_storage, project.disk_path)).to be(true) - expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false) + expect(gitlab_shell.exists?(project.repository_storage, disk_path)).to be(false) end it 'keeps the namespace directory' do - gitlab_shell.remove_repository(project.repository_storage_path, project.disk_path) + gitlab_shell.remove_repository(project.repository_storage, project.disk_path) - expect(gitlab_shell.exists?(project.repository_storage_path, disk_path)).to be(false) - expect(gitlab_shell.exists?(project.repository_storage_path, project.disk_path.gsub(project.name, ''))).to be(true) + expect(gitlab_shell.exists?(project.repository_storage, disk_path)).to be(false) + expect(gitlab_shell.exists?(project.repository_storage, project.disk_path.gsub(project.name, ''))).to be(true) end end @@ -469,18 +469,18 @@ describe Gitlab::Shell do old_path = project2.disk_path new_path = "project/new_path" - expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(true) - expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(false) + expect(gitlab_shell.exists?(project2.repository_storage, "#{old_path}.git")).to be(true) + expect(gitlab_shell.exists?(project2.repository_storage, "#{new_path}.git")).to be(false) - expect(gitlab_shell.mv_repository(project2.repository_storage_path, old_path, new_path)).to be_truthy + expect(gitlab_shell.mv_repository(project2.repository_storage, old_path, new_path)).to be_truthy - expect(gitlab_shell.exists?(project2.repository_storage_path, "#{old_path}.git")).to be(false) - expect(gitlab_shell.exists?(project2.repository_storage_path, "#{new_path}.git")).to be(true) + expect(gitlab_shell.exists?(project2.repository_storage, "#{old_path}.git")).to be(false) + expect(gitlab_shell.exists?(project2.repository_storage, "#{new_path}.git")).to be(true) end it 'returns false when the command fails' do - expect(gitlab_shell.mv_repository(project2.repository_storage_path, project2.disk_path, '')).to be_falsy - expect(gitlab_shell.exists?(project2.repository_storage_path, "#{project2.disk_path}.git")).to be(true) + expect(gitlab_shell.mv_repository(project2.repository_storage, project2.disk_path, '')).to be_falsy + expect(gitlab_shell.exists?(project2.repository_storage, "#{project2.disk_path}.git")).to be(true) end end @@ -679,48 +679,48 @@ describe Gitlab::Shell do describe 'namespace actions' do subject { described_class.new } - let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path } + let(:storage) { Gitlab.config.repositories.storages.keys.first } describe '#add_namespace' do it 'creates a namespace' do - subject.add_namespace(storage_path, "mepmep") + subject.add_namespace(storage, "mepmep") - expect(subject.exists?(storage_path, "mepmep")).to be(true) + expect(subject.exists?(storage, "mepmep")).to be(true) end end describe '#exists?' do context 'when the namespace does not exist' do it 'returns false' do - expect(subject.exists?(storage_path, "non-existing")).to be(false) + expect(subject.exists?(storage, "non-existing")).to be(false) end end context 'when the namespace exists' do it 'returns true' do - subject.add_namespace(storage_path, "mepmep") + subject.add_namespace(storage, "mepmep") - expect(subject.exists?(storage_path, "mepmep")).to be(true) + expect(subject.exists?(storage, "mepmep")).to be(true) end end end describe '#remove' do it 'removes the namespace' do - subject.add_namespace(storage_path, "mepmep") - subject.rm_namespace(storage_path, "mepmep") + subject.add_namespace(storage, "mepmep") + subject.rm_namespace(storage, "mepmep") - expect(subject.exists?(storage_path, "mepmep")).to be(false) + expect(subject.exists?(storage, "mepmep")).to be(false) end end describe '#mv_namespace' do it 'renames the namespace' do - subject.add_namespace(storage_path, "mepmep") - subject.mv_namespace(storage_path, "mepmep", "2mep") + subject.add_namespace(storage, "mepmep") + subject.mv_namespace(storage, "mepmep", "2mep") - expect(subject.exists?(storage_path, "mepmep")).to be(false) - expect(subject.exists?(storage_path, "2mep")).to be(true) + expect(subject.exists?(storage, "mepmep")).to be(false) + expect(subject.exists?(storage, "2mep")).to be(true) end end end diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb new file mode 100644 index 00000000000..23485fbcb18 --- /dev/null +++ b/spec/lib/omni_auth/strategies/jwt_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe OmniAuth::Strategies::Jwt do + include Rack::Test::Methods + include DeviseHelpers + + context '.decoded' do + let(:strategy) { described_class.new({}) } + let(:timestamp) { Time.now.to_i } + let(:jwt_config) { Devise.omniauth_configs[:jwt] } + let(:key) { JWT.encode(claims, jwt_config.strategy.secret) } + + let(:claims) do + { + id: 123, + name: "user_example", + email: "user@example.com", + iat: timestamp + } + end + + before do + allow_any_instance_of(OmniAuth::Strategy).to receive(:options).and_return(jwt_config.strategy) + allow_any_instance_of(Rack::Request).to receive(:params).and_return({ 'jwt' => key }) + end + + it 'decodes the user information' do + result = strategy.decoded + + expect(result["id"]).to eq(123) + expect(result["name"]).to eq("user_example") + expect(result["email"]).to eq("user@example.com") + expect(result["iat"]).to eq(timestamp) + end + + context 'required claims is missing' do + let(:claims) do + { + id: 123, + email: "user@example.com", + iat: timestamp + } + end + + it 'raises error' do + expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::JWT::ClaimInvalid) + end + end + + context 'when valid_within is specified but iat attribute is missing in response' do + let(:claims) do + { + id: 123, + name: "user_example", + email: "user@example.com" + } + end + + before do + jwt_config.strategy.valid_within = Time.now.to_i + end + + it 'raises error' do + expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::JWT::ClaimInvalid) + end + end + + context 'when timestamp claim is too skewed from present' do + let(:claims) do + { + id: 123, + name: "user_example", + email: "user@example.com", + iat: timestamp - 10.minutes.to_i + } + end + + before do + jwt_config.strategy.valid_within = 2.seconds + end + + it 'raises error' do + expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::JWT::ClaimInvalid) + end + end + end +end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 5a3b5b1f517..ffc78015f94 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -28,52 +28,12 @@ describe GroupMember do end end - describe 'notifications' do - describe "#after_create" do - it "sends email to user" do - membership = build(:group_member) + it_behaves_like 'members notifications', :group - allow(membership).to receive(:notification_service) - .and_return(double('NotificationService').as_null_object) - expect(membership).to receive(:notification_service) + describe '#real_source_type' do + subject { create(:group_member).real_source_type } - membership.save - end - end - - describe "#after_update" do - before do - @group_member = create :group_member - allow(@group_member).to receive(:notification_service) - .and_return(double('NotificationService').as_null_object) - end - - it "sends email to user" do - expect(@group_member).to receive(:notification_service) - @group_member.update_attribute(:access_level, GroupMember::MASTER) - end - - it "does not send an email when the access level has not changed" do - expect(@group_member).not_to receive(:notification_service) - @group_member.update_attribute(:access_level, GroupMember::OWNER) - end - end - - describe '#after_accept_request' do - it 'calls NotificationService.accept_group_access_request' do - member = create(:group_member, user: build(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:new_group_member) - - member.__send__(:after_accept_request) - end - end - - describe '#real_source_type' do - subject { create(:group_member).real_source_type } - - it { is_expected.to eq 'Group' } - end + it { is_expected.to eq 'Group' } end describe '#update_two_factor_requirement' do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index b8b0e63f92e..574eb468e4c 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -123,15 +123,5 @@ describe ProjectMember do it { expect(@project_2.users).to be_empty } end - describe 'notifications' do - describe '#after_accept_request' do - it 'calls NotificationService.new_project_member' do - member = create(:project_member, user: create(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:new_project_member) - - member.__send__(:after_accept_request) - end - end - end + it_behaves_like 'members notifications', :project end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 62e95a622eb..506057dce87 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -5,6 +5,7 @@ describe Namespace do let!(:namespace) { create(:namespace) } let(:gitlab_shell) { Gitlab::Shell.new } + let(:repository_storage) { 'default' } describe 'associations' do it { is_expected.to have_many :projects } @@ -201,7 +202,7 @@ describe Namespace do it "moves dir if path changed" do namespace.update_attributes(path: namespace.full_path + '_new') - expect(gitlab_shell.exists?(project.repository_storage_path, "#{namespace.path}/#{project.path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{namespace.path}/#{project.path}.git")).to be_truthy end context 'with subgroups', :nested_groups do @@ -281,7 +282,7 @@ describe Namespace do namespace.update_attributes(path: namespace.full_path + '_new') expect(before_disk_path).to eq(project.disk_path) - expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_truthy end end @@ -322,7 +323,7 @@ describe Namespace do end it 'schedules the namespace for deletion' do - expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path) + expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage, deleted_path) namespace.destroy end @@ -344,7 +345,7 @@ describe Namespace do end it 'schedules the namespace for deletion' do - expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage_path, deleted_path) + expect(GitlabShellWorker).to receive(:perform_in).with(5.minutes, :rm_namespace, repository_storage, deleted_path) child.destroy end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 127eb998abe..a9587b1005e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -449,14 +449,6 @@ describe Project do end end - describe '#repository_storage_path' do - let(:project) { create(:project) } - - it 'returns the repository storage path' do - expect(Dir.exist?(project.repository_storage_path)).to be(true) - end - end - it 'returns valid url to repo' do project = described_class.new(path: 'somewhere') expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') @@ -1108,7 +1100,7 @@ describe Project do end context 'repository storage by default' do - let(:project) { create(:project) } + let(:project) { build(:project) } before do storages = { @@ -1461,7 +1453,7 @@ describe Project do .and_return(false) allow(shell).to receive(:create_repository) - .with(project.repository_storage_path, project.disk_path) + .with(project.repository_storage, project.disk_path) .and_return(true) expect(project).to receive(:create_repository).with(force: true) @@ -2636,7 +2628,7 @@ describe Project do describe '#ensure_storage_path_exists' do it 'delegates to gitlab_shell to ensure namespace is created' do - expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, project.base_dir) + expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage, project.base_dir) project.ensure_storage_path_exists end @@ -2675,12 +2667,12 @@ describe Project do expect(gitlab_shell).to receive(:mv_repository) .ordered - .with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}") + .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}") .and_return(true) expect(gitlab_shell).to receive(:mv_repository) .ordered - .with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki") + .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki") .and_return(true) expect_any_instance_of(SystemHooksService) @@ -2829,7 +2821,7 @@ describe Project do it 'delegates to gitlab_shell to ensure namespace is created' do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) - expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, hashed_prefix) + expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage, hashed_prefix) project.ensure_storage_path_exists end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 4e83f4353cf..cbe7d111fcd 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -11,7 +11,7 @@ describe ProjectWiki do subject { project_wiki } it { is_expected.to delegate_method(:empty?).to :pages } - it { is_expected.to delegate_method(:repository_storage_path).to :project } + it { is_expected.to delegate_method(:repository_storage).to :project } it { is_expected.to delegate_method(:hashed_storage?).to :project } describe "#full_path" do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index e45fe7db1e7..630b9e0519f 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1224,15 +1224,15 @@ describe Repository do end end - shared_examples 'repo exists check' do + describe '#exists?' do it 'returns true when a repository exists' do - expect(repository.exists?).to eq(true) + expect(repository.exists?).to be(true) end it 'returns false if no full path can be constructed' do allow(repository).to receive(:full_path).and_return(nil) - expect(repository.exists?).to eq(false) + expect(repository.exists?).to be(false) end context 'with broken storage', :broken_storage do @@ -1242,16 +1242,6 @@ describe Repository do end end - describe '#exists?' do - context 'when repository_exists is disabled' do - it_behaves_like 'repo exists check' - end - - context 'when repository_exists is enabled', :skip_gitaly_mock do - it_behaves_like 'repo exists check' - end - end - describe '#has_visible_content?' do before do # If raw_repository.has_visible_content? gets called more than once then diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 6bed8e812c0..cd1a6cfc427 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -153,4 +153,13 @@ describe 'OpenID Connect requests' do end end end + + context 'OpenID configuration information' do + it 'correctly returns the configuration' do + get '/.well-known/openid-configuration' + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to have_key('issuer') + end + end end diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index e8216abb08b..a9baccd061a 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -53,8 +53,8 @@ describe Groups::DestroyService do end it 'verifies that paths have been deleted' do - expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey - expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, group.path)).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, remove_path)).to be_falsey end end end @@ -71,13 +71,13 @@ describe Groups::DestroyService do after do # Clean up stale directories - gitlab_shell.rm_namespace(project.repository_storage_path, group.path) - gitlab_shell.rm_namespace(project.repository_storage_path, remove_path) + gitlab_shell.rm_namespace(project.repository_storage, group.path) + gitlab_shell.rm_namespace(project.repository_storage, remove_path) end it 'verifies original paths and projects still exist' do - expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_truthy - expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, group.path)).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, remove_path)).to be_falsey expect(Project.unscoped.count).to eq(1) expect(Group.unscoped.count).to eq(2) end @@ -144,7 +144,7 @@ describe Groups::DestroyService do let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: group) } it 'removes repository' do - expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey end end @@ -152,7 +152,7 @@ describe Groups::DestroyService do let!(:project) { create(:project, :empty_repo, namespace: group) } it 'removes repository' do - expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 55bbe954491..48ef5f3c115 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -96,6 +96,37 @@ describe NotificationService, :mailer do it_should_behave_like 'participating by assignee notification' end + describe '#async' do + let(:async) { notification.async } + set(:key) { create(:personal_key) } + + it 'returns an Async object with the correct parent' do + expect(async).to be_a(described_class::Async) + expect(async.parent).to eq(notification) + end + + context 'when receiving a public method' do + it 'schedules a MailScheduler::NotificationServiceWorker' do + expect(MailScheduler::NotificationServiceWorker) + .to receive(:perform_async).with('new_key', key) + + async.new_key(key) + end + end + + context 'when receiving a private method' do + it 'raises NoMethodError' do + expect { async.notifiable?(key) }.to raise_error(NoMethodError) + end + end + + context 'when recieving a non-existent method' do + it 'raises NoMethodError' do + expect { async.foo(key) }.to raise_error(NoMethodError) + end + end + end + describe 'Keys' do describe '#new_key' do let(:key_options) { {} } @@ -982,6 +1013,8 @@ describe NotificationService, :mailer do let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user), description: 'cc @participant' } before do + project.add_master(merge_request.author) + project.add_master(merge_request.assignee) build_team(merge_request.target_project) add_users_with_subscription(merge_request.target_project, merge_request) update_custom_notification(:new_merge_request, @u_guest_custom, resource: project) @@ -1093,15 +1126,18 @@ describe NotificationService, :mailer do end describe '#reassigned_merge_request' do + let(:current_user) { create(:user) } + before do update_custom_notification(:reassign_merge_request, @u_guest_custom, resource: project) update_custom_notification(:reassign_merge_request, @u_custom_global) end it do - notification.reassigned_merge_request(merge_request, merge_request.author) + notification.reassigned_merge_request(merge_request, current_user, merge_request.author) should_email(merge_request.assignee) + should_email(merge_request.author) should_email(@u_watcher) should_email(@u_participant_mentioned) should_email(@subscriber) @@ -1116,7 +1152,7 @@ describe NotificationService, :mailer do end it 'adds "assigned" reason for new assignee' do - notification.reassigned_merge_request(merge_request, merge_request.author) + notification.reassigned_merge_request(merge_request, current_user, merge_request.author) email = find_email_for(merge_request.assignee) @@ -1126,7 +1162,7 @@ describe NotificationService, :mailer do it_behaves_like 'participating notifications' do let(:participant) { create(:user, username: 'user-participant') } let(:issuable) { merge_request } - let(:notification_trigger) { notification.reassigned_merge_request(merge_request, @u_disabled) } + let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, merge_request.author) } end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e35f0f6337a..a8f003b1073 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -171,7 +171,6 @@ describe Projects::CreateService, '#execute' do context 'when another repository already exists on disk' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } let(:opts) do { @@ -186,7 +185,7 @@ describe Projects::CreateService, '#execute' do end after do - gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + gitlab_shell.remove_repository(repository_storage, "#{user.namespace.full_path}/existing") end it 'does not allow to create a project when path matches existing repository on disk' do @@ -222,7 +221,7 @@ describe Projects::CreateService, '#execute' do end after do - gitlab_shell.remove_repository(repository_storage_path, hashed_path) + gitlab_shell.remove_repository(repository_storage, hashed_path) end it 'does not allow to create a project when path matches existing repository on disk' do diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index a66e3c5e995..b2c52214f48 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -18,8 +18,8 @@ describe Projects::DestroyService do it 'deletes the project' do expect(Project.unscoped.all).not_to include(project) - expect(project.gitlab_shell.exists?(project.repository_storage_path, path + '.git')).to be_falsey - expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path + '.git')).to be_falsey + expect(project.gitlab_shell.exists?(project.repository_storage, path + '.git')).to be_falsey + expect(project.gitlab_shell.exists?(project.repository_storage, remove_path + '.git')).to be_falsey end end @@ -252,21 +252,21 @@ describe Projects::DestroyService do let(:path) { project.disk_path + '.git' } before do - expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy - expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + expect(project.gitlab_shell.exists?(project.repository_storage, path)).to be_truthy + expect(project.gitlab_shell.exists?(project.repository_storage, remove_path)).to be_falsey # Dont run sidekiq to check if renamed repository exists Sidekiq::Testing.fake! { destroy_project(project, user, {}) } - expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_falsey - expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy + expect(project.gitlab_shell.exists?(project.repository_storage, path)).to be_falsey + expect(project.gitlab_shell.exists?(project.repository_storage, remove_path)).to be_truthy end it 'restores the repositories' do Sidekiq::Testing.fake! { described_class.new(project, user).attempt_repositories_rollback } - expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy - expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey + expect(project.gitlab_shell.exists?(project.repository_storage, path)).to be_truthy + expect(project.gitlab_shell.exists?(project.repository_storage, remove_path)).to be_falsey end end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 0f7c46367d0..a93f6f1ddc2 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -112,7 +112,7 @@ describe Projects::ForkService do end after do - gitlab_shell.remove_repository(repository_storage_path, "#{@to_user.namespace.full_path}/#{@from_project.path}") + gitlab_shell.remove_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}") end it 'does not allow creation' do diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 747bd4529a0..7dca81eb59e 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -16,8 +16,8 @@ describe Projects::HashedStorage::MigrateRepositoryService do it 'renames project and wiki repositories' do service.execute - expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.git")).to be_truthy - expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.wiki.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{hashed_storage.disk_path}.git")).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{hashed_storage.disk_path}.wiki.git")).to be_truthy end it 'updates project to be hashed and not read-only' do @@ -52,8 +52,8 @@ describe Projects::HashedStorage::MigrateRepositoryService do service.execute - expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.git")).to be_falsey - expect(gitlab_shell.exists?(project.repository_storage_path, "#{hashed_storage.disk_path}.wiki.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{hashed_storage.disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{hashed_storage.disk_path}.wiki.git")).to be_falsey expect(project.repository_read_only?).to be_falsey end @@ -63,11 +63,11 @@ describe Projects::HashedStorage::MigrateRepositoryService do before do hashed_storage.ensure_storage_path_exists - gitlab_shell.mv_repository(project.repository_storage_path, from_name, to_name) + gitlab_shell.mv_repository(project.repository_storage, from_name, to_name) end it 'does not try to move nil repository over hashed' do - expect(gitlab_shell).not_to receive(:mv_repository).with(project.repository_storage_path, from_name, to_name) + expect(gitlab_shell).not_to receive(:mv_repository).with(project.repository_storage, from_name, to_name) expect_move_repository("#{project.disk_path}.wiki", "#{hashed_storage.disk_path}.wiki") service.execute @@ -76,7 +76,7 @@ describe Projects::HashedStorage::MigrateRepositoryService do end def expect_move_repository(from_name, to_name) - expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage_path, from_name, to_name).and_call_original + expect(gitlab_shell).to receive(:mv_repository).with(project.repository_storage, from_name, to_name).and_call_original end end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index ff9b2372a35..3e6483d7e28 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -84,7 +84,7 @@ describe Projects::TransferService do end def project_path(project) - File.join(project.repository_storage_path, "#{project.disk_path}.git") + project.repository.path_to_repo end def current_path @@ -94,7 +94,7 @@ describe Projects::TransferService do it 'rolls back repo location' do attempt_project_transfer - expect(Dir.exist?(original_path)).to be_truthy + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be(true) expect(original_path).to eq current_path end @@ -165,7 +165,7 @@ describe Projects::TransferService do end after do - gitlab_shell.remove_repository(repository_storage_path, "#{group.full_path}/#{project.path}") + gitlab_shell.remove_repository(repository_storage, "#{group.full_path}/#{project.path}") end it { expect(@result).to eq false } diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 1b6caeab15d..a418808fd26 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -29,25 +29,10 @@ describe Projects::UpdatePagesService do end describe 'pages artifacts' do - context 'with expiry date' do - before do - build.artifacts_expire_in = "2 days" - build.save! - end - - it "doesn't delete artifacts" do - expect(execute).to eq(:success) - - expect(build.reload.artifacts?).to eq(true) - end - end - - context 'without expiry date' do - it "does delete artifacts" do - expect(execute).to eq(:success) + it "doesn't delete artifacts after deploying" do + expect(execute).to eq(:success) - expect(build.reload.artifacts?).to eq(false) - end + expect(build.reload.artifacts?).to eq(true) end end @@ -100,25 +85,10 @@ describe Projects::UpdatePagesService do end describe 'pages artifacts' do - context 'with expiry date' do - before do - build.artifacts_expire_in = "2 days" - build.save! - end - - it "doesn't delete artifacts" do - expect(execute).to eq(:success) - - expect(build.artifacts?).to eq(true) - end - end - - context 'without expiry date' do - it "does delete artifacts" do - expect(execute).to eq(:success) + it "doesn't delete artifacts after deploying" do + expect(execute).to eq(:success) - expect(build.reload.artifacts?).to eq(false) - end + expect(build.artifacts?).to eq(true) end end @@ -171,13 +141,12 @@ describe Projects::UpdatePagesService do build.reload expect(deploy_status).to be_failed - expect(build.artifacts?).to be_truthy end end context 'when failed to extract zip artifacts' do before do - allow_any_instance_of(described_class) + expect_any_instance_of(described_class) .to receive(:extract_zip_archive!) .and_raise(Projects::UpdatePagesService::FailedToExtractError) end @@ -188,21 +157,19 @@ describe Projects::UpdatePagesService do build.reload expect(deploy_status).to be_failed - expect(build.artifacts?).to be_truthy end end context 'when missing artifacts metadata' do before do - allow(build).to receive(:artifacts_metadata?).and_return(false) + expect(build).to receive(:artifacts_metadata?).and_return(false) end - it 'does not raise an error and remove artifacts as failed job' do + it 'does not raise an error as failed job' do execute build.reload expect(deploy_status).to be_failed - expect(build.artifacts?).to be_falsey end end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index f48d466d263..3e6073b9861 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -200,7 +200,7 @@ describe Projects::UpdateService do end after do - gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") + gitlab_shell.remove_repository(repository_storage, "#{user.namespace.full_path}/existing") end it 'does not allow renaming when new path matches existing repository on disk' do diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb index 2d7fa3f80f7..ab1c638fc39 100644 --- a/spec/services/repository_archive_clean_up_service_spec.rb +++ b/spec/services/repository_archive_clean_up_service_spec.rb @@ -1,15 +1,47 @@ require 'spec_helper' describe RepositoryArchiveCleanUpService do - describe '#execute' do - subject(:service) { described_class.new } + subject(:service) { described_class.new } + describe '#execute (new archive locations)' do + let(:sha) { "0" * 40 } + + it 'removes outdated archives and directories in a new-style path' do + in_directory_with_files("project-999/#{sha}", %w[tar tar.bz2 tar.gz zip], 3.hours) do |dirname, files| + service.execute + + files.each { |filename| expect(File.exist?(filename)).to be_falsy } + expect(File.directory?(dirname)).to be_falsy + expect(File.directory?(File.dirname(dirname))).to be_falsy + end + end + + it 'does not remove directories when they contain outdated non-archives' do + in_directory_with_files("project-999/#{sha}", %w[tar conf rb], 2.hours) do |dirname, files| + service.execute + + expect(File.directory?(dirname)).to be_truthy + end + end + + it 'does not remove in-date archives in a new-style path' do + in_directory_with_files("project-999/#{sha}", %w[tar tar.bz2 tar.gz zip], 1.hour) do |dirname, files| + service.execute + + files.each { |filename| expect(File.exist?(filename)).to be_truthy } + end + end + end + + describe '#execute (legacy archive locations)' do context 'when the downloads directory does not exist' do it 'does not remove any archives' do path = '/invalid/path/' stub_repository_downloads_path(path) + allow(File).to receive(:directory?).and_call_original expect(File).to receive(:directory?).with(path).and_return(false) + expect(service).not_to receive(:clean_up_old_archives) expect(service).not_to receive(:clean_up_empty_directories) @@ -19,7 +51,7 @@ describe RepositoryArchiveCleanUpService do context 'when the downloads directory exists' do shared_examples 'invalid archive files' do |dirname, extensions, mtime| - it 'does not remove files and directoy' do + it 'does not remove files and directory' do in_directory_with_files(dirname, extensions, mtime) do |dir, files| service.execute @@ -43,7 +75,7 @@ describe RepositoryArchiveCleanUpService do end context 'with files older than 2 hours inside invalid directories' do - it_behaves_like 'invalid archive files', 'john_doe/sample.git', %w[conf rb tar tar.gz], 2.hours + it_behaves_like 'invalid archive files', 'john/doe/sample.git', %w[conf rb tar tar.gz], 2.hours end context 'with files newer than 2 hours that matches valid archive extensions' do @@ -58,24 +90,24 @@ describe RepositoryArchiveCleanUpService do it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb tar tar.gz], 1.hour end end + end - def in_directory_with_files(dirname, extensions, mtime) - Dir.mktmpdir do |tmpdir| - stub_repository_downloads_path(tmpdir) - dir = File.join(tmpdir, dirname) - files = create_temporary_files(dir, extensions, mtime) + def in_directory_with_files(dirname, extensions, mtime) + Dir.mktmpdir do |tmpdir| + stub_repository_downloads_path(tmpdir) + dir = File.join(tmpdir, dirname) + files = create_temporary_files(dir, extensions, mtime) - yield(dir, files) - end + yield(dir, files) end + end - def stub_repository_downloads_path(path) - allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path) - end + def stub_repository_downloads_path(path) + allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path) + end - def create_temporary_files(dir, extensions, mtime) - FileUtils.mkdir_p(dir) - FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime) - end + def create_temporary_files(dir, extensions, mtime) + FileUtils.mkdir_p(dir) + FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime) end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 893804f1470..e28b0ea5cf2 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -909,13 +909,7 @@ describe SystemNoteService do it 'sets the note text' do noteable.update_attribute(:time_estimate, 277200) - expect(subject.note).to eq "changed time estimate to 1w 4d 5h," - end - - it 'appends a comma to separate the note from the update_at time' do - noteable.update_attribute(:time_estimate, 277200) - - expect(subject.note).to end_with(',') + expect(subject.note).to eq "changed time estimate to 1w 4d 5h" end end diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index 11c75ddfcf8..76f1e625fda 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -176,7 +176,7 @@ describe Users::DestroyService do let!(:project) { create(:project, :empty_repo, :legacy_storage, namespace: user.namespace) } it 'removes repository' do - expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey end end @@ -184,7 +184,7 @@ describe Users::DestroyService do let!(:project) { create(:project, :empty_repo, namespace: user.namespace) } it 'removes repository' do - expect(gitlab_shell.exists?(project.repository_storage_path, "#{project.disk_path}.git")).to be_falsey + expect(gitlab_shell.exists?(project.repository_storage, "#{project.disk_path}.git")).to be_falsey end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 53045815a6a..cc61cd7d838 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -113,10 +113,10 @@ RSpec.configure do |config| m.call(*args) shard_name, repository_relative_path = args - shard_path = Gitlab.config.repositories.storages.fetch(shard_name).legacy_disk_path # We can't leave the hooks in place after a fork, as those would fail in tests # The "internal" API is not available - FileUtils.rm_rf(File.join(shard_path, repository_relative_path, 'hooks')) + Gitlab::Shell.new.rm_directory(shard_name, + File.join(repository_relative_path, 'hooks')) end # Enable all features by default for testing diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 2197bc9d853..086a345dca8 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -31,7 +31,7 @@ module JavaScriptFixturesHelpers end def remove_repository(project) - Gitlab::Shell.new.remove_repository(project.repository_storage_path, project.disk_path) + Gitlab::Shell.new.remove_repository(project.repository_storage, project.disk_path) end private diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index d87f265cdf0..1dad39fdab3 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -218,7 +218,8 @@ module TestEnv end def copy_repo(project, bare_repo:, refs:) - target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.disk_path}.git") + target_repo_path = File.expand_path(repos_path + "/#{project.disk_path}.git") + FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path @@ -226,7 +227,7 @@ module TestEnv end def repos_path - Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path + @repos_path ||= Gitlab.config.repositories.storages[REPOS_STORAGE].legacy_disk_path end def backup_path diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb new file mode 100644 index 00000000000..b29bb3c2fc0 --- /dev/null +++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb @@ -0,0 +1,52 @@ +RSpec.shared_examples 'Master manages access requests' do + let(:user) { create(:user) } + let(:master) { create(:user) } + + before do + entity.request_access(user) + entity.respond_to?(:add_owner) ? entity.add_owner(master) : entity.add_master(master) + sign_in(master) + end + + it 'master can see access requests' do + visit members_page_path + + expect_visible_access_request(entity, user) + end + + it 'master can grant access', :js do + visit members_page_path + + expect_visible_access_request(entity, user) + + accept_confirm { click_on 'Grant access' } + + expect_no_visible_access_request(entity, user) + + page.within('.members-list') do + expect(page).to have_content user.name + end + end + + it 'master can deny access', :js do + visit members_page_path + + expect_visible_access_request(entity, user) + + accept_confirm { click_on 'Deny access' } + + expect_no_visible_access_request(entity, user) + expect(page).not_to have_content user.name + end + + def expect_visible_access_request(entity, user) + expect(entity.requesters.exists?(user_id: user)).to be_truthy + expect(page).to have_content "Users requesting access to #{entity.name} 1" + expect(page).to have_content user.name + end + + def expect_no_visible_access_request(entity, user) + expect(entity.requesters.exists?(user_id: user)).to be_falsy + expect(page).not_to have_content "Users requesting access to #{entity.name}" + end +end diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb new file mode 100644 index 00000000000..76611e54306 --- /dev/null +++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb @@ -0,0 +1,63 @@ +RSpec.shared_examples 'members notifications' do |entity_type| + let(:notification_service) { double('NotificationService').as_null_object } + + before do + allow(member).to receive(:notification_service).and_return(notification_service) + end + + describe "#after_create" do + let(:member) { build(:"#{entity_type}_member") } + + it "sends email to user" do + expect(notification_service).to receive(:"new_#{entity_type}_member").with(member) + + member.save + end + end + + describe "#after_update" do + let(:member) { create(:"#{entity_type}_member", :developer) } + + it "calls NotificationService.update_#{entity_type}_member" do + expect(notification_service).to receive(:"update_#{entity_type}_member").with(member) + + member.update_attribute(:access_level, Member::MASTER) + end + + it "does not send an email when the access level has not changed" do + expect(notification_service).not_to receive(:"update_#{entity_type}_member") + + member.touch + end + end + + describe '#accept_request' do + let(:member) { create(:"#{entity_type}_member", :access_request) } + + it "calls NotificationService.new_#{entity_type}_member" do + expect(notification_service).to receive(:"new_#{entity_type}_member").with(member) + + member.accept_request + end + end + + describe "#accept_invite!" do + let(:member) { create(:"#{entity_type}_member", :invited) } + + it "calls NotificationService.accept_#{entity_type}_invite" do + expect(notification_service).to receive(:"accept_#{entity_type}_invite").with(member) + + member.accept_invite!(build(:user)) + end + end + + describe "#decline_invite!" do + let(:member) { create(:"#{entity_type}_member", :invited) } + + it "calls NotificationService.decline_#{entity_type}_invite" do + expect(notification_service).to receive(:"decline_#{entity_type}_invite").with(member) + + member.decline_invite! + end + end +end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 0d24782f317..a2e5642a72c 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -195,15 +195,12 @@ describe 'gitlab:app namespace rake task' do end context 'multiple repository storages' do - let(:storage_default) do - Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage')) - end let(:test_second_storage) do Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/custom_storage')) end let(:storages) do { - 'default' => storage_default, + 'default' => Gitlab.config.repositories.storages.default, 'test_second_storage' => test_second_storage } end @@ -215,8 +212,7 @@ describe 'gitlab:app namespace rake task' do before do # We only need a backup of the repositories for this test stub_env('SKIP', 'db,uploads,builds,artifacts,lfs,registry') - FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage')) - FileUtils.mkdir(Settings.absolute('tmp/tests/custom_storage')) + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) # Avoid asking gitaly about the root ref (which will fail beacuse of the @@ -225,14 +221,23 @@ describe 'gitlab:app namespace rake task' do end after do - FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage')) FileUtils.rm_rf(Settings.absolute('tmp/tests/custom_storage')) end it 'includes repositories in all repository storages' do - project_a = create(:project, :repository, repository_storage: 'default') + project_a = create(:project, :repository) project_b = create(:project, :repository, repository_storage: 'test_second_storage') + b_storage_dir = File.join(Settings.absolute('tmp/tests/custom_storage'), File.dirname(project_b.disk_path)) + + FileUtils.mkdir_p(b_storage_dir) + + # Even when overriding the storage, we have to move it there, so it exists + FileUtils.mv( + File.join(Settings.absolute(storages['default'].legacy_disk_path), project_b.repository.disk_path + '.git'), + Rails.root.join(storages['test_second_storage'].legacy_disk_path, project_b.repository.disk_path + '.git') + ) + expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout tar_contents, exit_status = Gitlab::Popen.popen( diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb index 48ac1b8a1a4..1026ae5b4bf 100644 --- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb +++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb @@ -12,8 +12,8 @@ describe MailScheduler::IssueDueWorker do create(:issue, :opened, project: project, due_date: 2.days.from_now) # due on another day create(:issue, :opened, due_date: Date.tomorrow) # different project - expect_any_instance_of(NotificationService).to receive(:issue_due).with(issue1) - expect_any_instance_of(NotificationService).to receive(:issue_due).with(issue2) + expect(worker.notification_service).to receive(:issue_due).with(issue1) + expect(worker.notification_service).to receive(:issue_due).with(issue2) worker.perform(project.id) end diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb new file mode 100644 index 00000000000..f725c8763a0 --- /dev/null +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe MailScheduler::NotificationServiceWorker do + let(:worker) { described_class.new } + let(:method) { 'new_key' } + set(:key) { create(:personal_key) } + + def serialize(*args) + ActiveJob::Arguments.serialize(args) + end + + describe '#perform' do + it 'deserializes arguments from global IDs' do + expect(worker.notification_service).to receive(method).with(key) + + worker.perform(method, *serialize(key)) + end + + context 'when the arguments cannot be deserialized' do + it 'does nothing' do + expect(worker.notification_service).not_to receive(method) + + worker.perform(method, key.to_global_id.to_s.succ) + end + end + + context 'when the method is not a public method' do + it 'raises NoMethodError' do + expect { worker.perform('notifiable?', *serialize(key)) }.to raise_error(NoMethodError) + end + end + end + + describe '.perform_async' do + it 'serializes arguments as global IDs when scheduling' do + Sidekiq::Testing.fake! do + described_class.perform_async(method, key) + + expect(described_class.jobs.count).to eq(1) + expect(described_class.jobs.first).to include('args' => [method, *serialize(key)]) + end + end + end +end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index 479d9396eca..eec110dfbfb 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -22,13 +22,11 @@ describe NamespacelessProjectDestroyWorker do end end - # Only possible with schema 20180222043024 and lower. - # Project#namespace_id has not null constraint since then - context 'project has no namespace', :migration, schema: 20180222043024 do - let!(:project) do - project = build(:project, namespace_id: nil) - project.save(validate: false) - project + context 'project has no namespace' do + let!(:project) { create(:project) } + + before do + allow_any_instance_of(Project).to receive(:namespace).and_return(nil) end context 'project not a fork of another project' do @@ -61,8 +59,7 @@ describe NamespacelessProjectDestroyWorker do let!(:parent_project) { create(:project) } let(:project) do namespaceless_project = fork_project(parent_project) - namespaceless_project.namespace_id = nil - namespaceless_project.save(validate: false) + namespaceless_project.save namespaceless_project end diff --git a/vendor/assets/javascripts/peek.performance_bar.js b/vendor/assets/javascripts/peek.performance_bar.js deleted file mode 100644 index 6ed86dce2f2..00000000000 --- a/vendor/assets/javascripts/peek.performance_bar.js +++ /dev/null @@ -1,182 +0,0 @@ -var PerformanceBar, ajaxStart, renderPerformanceBar, updateStatus; - -PerformanceBar = (function() { - PerformanceBar.prototype.appInfo = null; - - PerformanceBar.prototype.width = null; - - PerformanceBar.formatTime = function(value) { - if (value >= 1000) { - return ((value / 1000).toFixed(3)) + "s"; - } else { - return (value.toFixed(0)) + "ms"; - } - }; - - function PerformanceBar(options) { - var k, v; - if (options == null) { - options = {}; - } - this.el = $('#peek-view-performance-bar .performance-bar'); - for (k in options) { - v = options[k]; - this[k] = v; - } - if (this.width == null) { - this.width = this.el.width(); - } - if (this.timing == null) { - this.timing = window.performance.timing; - } - } - - PerformanceBar.prototype.render = function(serverTime) { - var networkTime, perfNetworkTime; - if (serverTime == null) { - serverTime = 0; - } - this.el.empty(); - this.addBar('frontend', '#90d35b', 'domLoading', 'domInteractive'); - perfNetworkTime = this.timing.responseEnd - this.timing.requestStart; - if (serverTime && serverTime <= perfNetworkTime) { - networkTime = perfNetworkTime - serverTime; - this.addBar('latency / receiving', '#f1faff', this.timing.requestStart + serverTime, this.timing.requestStart + serverTime + networkTime); - this.addBar('app', '#90afcf', this.timing.requestStart, this.timing.requestStart + serverTime, this.appInfo); - } else { - this.addBar('backend', '#c1d7ee', 'requestStart', 'responseEnd'); - } - this.addBar('tcp / ssl', '#45688e', 'connectStart', 'connectEnd'); - this.addBar('redirect', '#0c365e', 'redirectStart', 'redirectEnd'); - this.addBar('dns', '#082541', 'domainLookupStart', 'domainLookupEnd'); - return this.el; - }; - - PerformanceBar.prototype.isLoaded = function() { - return this.timing.domInteractive; - }; - - PerformanceBar.prototype.start = function() { - return this.timing.navigationStart; - }; - - PerformanceBar.prototype.end = function() { - return this.timing.domInteractive; - }; - - PerformanceBar.prototype.total = function() { - return this.end() - this.start(); - }; - - PerformanceBar.prototype.addBar = function(name, color, start, end, info) { - var bar, left, offset, time, title, width; - if (typeof start === 'string') { - start = this.timing[start]; - } - if (typeof end === 'string') { - end = this.timing[end]; - } - if (!((start != null) && (end != null))) { - return; - } - time = end - start; - offset = start - this.start(); - left = this.mapH(offset); - width = this.mapH(time); - title = name + ": " + (PerformanceBar.formatTime(time)); - bar = $('<li></li>', { - 'data-title': title, - 'data-toggle': 'tooltip', - 'data-container': 'body' - }); - bar.css({ - width: width + "px", - left: left + "px", - background: color - }); - return this.el.append(bar); - }; - - PerformanceBar.prototype.mapH = function(offset) { - return offset * (this.width / this.total()); - }; - - return PerformanceBar; - -})(); - -renderPerformanceBar = function() { - var bar, resp, span, time; - resp = $('#peek-server_response_time'); - time = Math.round(resp.data('time') * 1000); - bar = new PerformanceBar; - bar.render(time); - span = $('<span>', { - 'data-toggle': 'tooltip', - 'data-title': 'Total navigation time for this page.', - 'data-container': 'body' - }).text(PerformanceBar.formatTime(bar.total())); - return updateStatus(span); -}; - -updateStatus = function(html) { - return $('#serverstats').html(html); -}; - -ajaxStart = null; - -$(document).on('pjax:start page:fetch turbolinks:request-start', function(event) { - return ajaxStart = event.timeStamp; -}); - -$(document).on('pjax:end page:load turbolinks:load', function(event, xhr) { - var ajaxEnd, serverTime, total; - if (ajaxStart == null) { - return; - } - ajaxEnd = event.timeStamp; - total = ajaxEnd - ajaxStart; - serverTime = xhr ? parseInt(xhr.getResponseHeader('X-Runtime')) : 0; - return setTimeout(function() { - var bar, now, span, tech; - now = new Date().getTime(); - bar = new PerformanceBar({ - timing: { - requestStart: ajaxStart, - responseEnd: ajaxEnd, - domLoading: ajaxEnd, - domInteractive: now - }, - isLoaded: function() { - return true; - }, - start: function() { - return ajaxStart; - }, - end: function() { - return now; - } - }); - bar.render(serverTime); - if ($.fn.pjax != null) { - tech = 'PJAX'; - } else { - tech = 'Turbolinks'; - } - span = $('<span>', { - 'data-toggle': 'tooltip', - 'data-title': tech + " navigation time", - 'data-container': 'body' - }).text(PerformanceBar.formatTime(total)); - updateStatus(span); - return ajaxStart = null; - }, 0); -}); - -$(function() { - if (window.performance) { - return renderPerformanceBar(); - } else { - return $('#peek-view-performance-bar').remove(); - } -}); diff --git a/yarn.lock b/yarn.lock index 7aca6b0d427..395e2cfb0ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -58,6 +58,10 @@ version "1.18.0" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.18.0.tgz#7829f0e6de0647dace54c1fcd597ee3424afb233" +"@sindresorhus/is@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" + "@types/jquery@^2.0.40": version "2.0.48" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef" @@ -228,6 +232,12 @@ ansi-styles@^3.2.0: dependencies: color-convert "^1.9.0" +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -1505,6 +1515,18 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" + dependencies: + clone-response "1.0.2" + get-stream "3.0.0" + http-cache-semantics "3.8.1" + keyv "3.0.0" + lowercase-keys "1.0.0" + normalize-url "2.0.1" + responselike "1.0.2" + cached-path-relative@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7" @@ -1596,6 +1618,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1: escape-string-regexp "^1.0.5" supports-color "^5.2.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" + chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -1723,6 +1753,12 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +clone-response@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" @@ -1999,9 +2035,10 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.1.tgz#817f2c2039347a1e9bf7d090c0923e53f749ca82" + version "2.2.2" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" dependencies: + is-directory "^0.3.1" js-yaml "^3.4.3" minimist "^1.2.0" object-assign "^4.1.0" @@ -2373,7 +2410,7 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" -decompress-response@^3.2.0: +decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" dependencies: @@ -3498,7 +3535,7 @@ fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" -from2@^2.1.0: +from2@^2.1.0, from2@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" dependencies: @@ -3598,7 +3635,7 @@ get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" -get-stream@^3.0.0: +get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -3750,23 +3787,26 @@ got@^6.7.1: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -got@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" +got@^8.0.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.0.tgz#6ba26e75f8a6cc4c6b3eb1fe7ce4fec7abac8533" dependencies: - decompress-response "^3.2.0" + "@sindresorhus/is" "^0.7.0" + cacheable-request "^2.1.1" + decompress-response "^3.3.0" duplexer3 "^0.1.4" get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" + into-stream "^3.1.0" + is-retry-allowed "^1.1.0" isurl "^1.0.0-alpha5" lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" + mimic-response "^1.0.0" + p-cancelable "^0.4.0" + p-timeout "^2.0.1" + pify "^3.0.0" + safe-buffer "^5.1.1" + timed-out "^4.0.1" + url-parse-lax "^3.0.0" url-to-options "^1.0.1" graceful-fs@^4.1.11, graceful-fs@^4.1.2: @@ -4030,6 +4070,10 @@ htmlparser2@^3.8.2, htmlparser2@^3.9.0: inherits "^2.0.1" readable-stream "^2.0.2" +http-cache-semantics@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -4277,6 +4321,13 @@ interpret@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" +into-stream@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" + dependencies: + from2 "^2.1.1" + p-is-promise "^1.1.0" + invariant@^2.2.0, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -4378,6 +4429,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -4515,7 +4570,7 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: +is-plain-obj@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -4563,7 +4618,7 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" -is-retry-allowed@^1.0.0: +is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" @@ -4772,13 +4827,20 @@ 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.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0: +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" dependencies: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.4.3: + 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" + js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -4798,6 +4860,10 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + json-loader@^0.5.4: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" @@ -4950,6 +5016,12 @@ katex@^0.8.3: dependencies: match-at "^0.1.0" +keyv@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" + dependencies: + json-buffer "3.0.0" + killable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" @@ -5255,7 +5327,7 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lowercase-keys@^1.0.0: +lowercase-keys@1.0.0, lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -5799,6 +5871,14 @@ normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" +normalize-url@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" + dependencies: + prepend-http "^2.0.0" + query-string "^5.0.1" + sort-keys "^2.0.0" + normalize-url@^1.4.0: version "1.9.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" @@ -5979,14 +6059,18 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" +p-cancelable@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" +p-is-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + p-limit@^1.0.0, p-limit@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" @@ -6003,9 +6087,9 @@ p-map@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" -p-timeout@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c" +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" dependencies: p-finally "^1.0.0" @@ -6528,7 +6612,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, postcss@^6.0.8: +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: @@ -6536,6 +6620,14 @@ postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.8: source-map "^0.6.1" supports-color "^5.2.0" +postcss@^6.0.8: + version "6.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.21.tgz#8265662694eddf9e9a5960db6da33c39e4cd069d" + dependencies: + chalk "^2.3.2" + source-map "^0.6.1" + supports-color "^5.3.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -6544,14 +6636,22 @@ prepend-http@^1.0.0, prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@1.11.1, prettier@^1.7.0: +prettier@1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75" +prettier@^1.7.0: + version "1.12.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" + prismjs@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.6.0.tgz#118d95fb7a66dba2272e343b345f5236659db365" @@ -6686,6 +6786,14 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0, querystring-es3@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -7154,12 +7262,24 @@ resolve@1.1.7, resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.2.0, resolve@^1.4.0: +resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.2.0: version "1.5.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" dependencies: path-parse "^1.0.5" +resolve@^1.4.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" + dependencies: + path-parse "^1.0.5" + +responselike@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" @@ -7583,6 +7703,12 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" @@ -7895,6 +8021,12 @@ supports-color@^5.1.0, supports-color@^5.2.0: dependencies: has-flag "^3.0.0" +supports-color@^5.3.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + svg4everybody@2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d" @@ -8018,7 +8150,7 @@ timeago.js@^3.0.2: dependencies: "@types/jquery" "^2.0.40" -timed-out@^4.0.0: +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" @@ -8308,6 +8440,12 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + dependencies: + prepend-http "^2.0.0" + url-parse@1.0.x: version "1.0.5" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" @@ -8429,12 +8567,12 @@ vue-eslint-parser@^2.0.1: lodash "^4.17.4" vue-hot-reload-api@^2.2.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.2.4.tgz#683bd1d026c0d3b3c937d5875679e9a87ec6cd8f" + version "2.3.0" + resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926" vue-loader@^14.1.1: - version "14.1.1" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.1.1.tgz#331f197fcea790d6b8662c29b850806e7eb29342" + version "14.2.2" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.2.2.tgz#c8cf3c2e29b6fb2ee595248a2aa6005038a125b3" dependencies: consolidate "^0.14.0" hash-sum "^1.0.2" @@ -8450,26 +8588,26 @@ vue-loader@^14.1.1: vue-style-loader "^4.0.1" vue-template-es2015-compiler "^1.6.0" -vue-resource@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.3.5.tgz#021d8713e9d86a77e83169dfdd8eab6047369a71" +vue-resource@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.5.0.tgz#ba0c6ef7af2eeace03cf24a91f529471be974c72" dependencies: - got "^7.1.0" + got "^8.0.3" vue-router@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9" vue-style-loader@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.0.2.tgz#e89aa4702a0c6b9630d8de70b1cbddb06b9ad254" + version "4.1.0" + resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863" dependencies: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.5.13: - version "2.5.13" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.13.tgz#12a2aa0ecd6158ac5e5f14d294b0993f399c3d38" +vue-template-compiler@^2.5.16: + version "2.5.16" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.16.tgz#93b48570e56c720cdf3f051cc15287c26fbd04cb" dependencies: de-indent "^1.0.2" he "^1.1.0" @@ -8482,9 +8620,9 @@ vue-virtual-scroll-list@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a" -vue@^2.5.13: - version "2.5.13" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.13.tgz#95bd31e20efcf7a7f39239c9aa6787ce8cf578e1" +vue@^2.5.16: + version "2.5.16" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.16.tgz#07edb75e8412aaeed871ebafa99f4672584a0085" vuex@^3.0.1: version "3.0.1" |