diff options
372 files changed, 3051 insertions, 2429 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index f851e3b67e6..b9c5973d7ac 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -71,7 +71,3 @@ rules: body: 1 ## Destructuring: https://eslint.org/docs/rules/prefer-destructuring prefer-destructuring: off - ## no-restricted-globals: https://eslint.org/docs/rules/no-restricted-globals - no-restricted-globals: off - ## no-multi-assign: https://eslint.org/docs/rules/no-multi-assign - no-multi-assign: off diff --git a/.flayignore b/.flayignore index 7faa6c7bb90..3e5063674ff 100644 --- a/.flayignore +++ b/.flayignore @@ -14,3 +14,12 @@ lib/gitlab/gitaly_client/ref_service.rb lib/gitlab/gitaly_client/commit_service.rb lib/gitlab/git/commit.rb lib/gitlab/git/tag.rb + +ee/db/**/* +ee/app/serializers/ee/merge_request_widget_entity.rb +ee/lib/api/epics.rb +ee/lib/api/geo_nodes.rb +ee/lib/ee/gitlab/ldap/sync/admin_users.rb +ee/app/workers/geo/file_download_dispatch_worker/job_artifact_job_finder.rb +ee/app/workers/geo/file_download_dispatch_worker/lfs_object_job_finder.rb +ee/spec/**/* diff --git a/.gitignore b/.gitignore index 51b77d5ac9e..21dc67384aa 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ eslint-report.html /.rspec /plugins/* /.gitlab_pages_secret +package-lock.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9a0102c65fd..e7304b9c057 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -264,10 +264,10 @@ package-and-qa: <<: *single-script-job variables: <<: *single-script-job-variables - SCRIPT_NAME: trigger-build-omnibus + SCRIPT_NAME: trigger-build retry: 0 script: - - ./$SCRIPT_NAME + - ./$SCRIPT_NAME omnibus when: manual only: - //@gitlab-org/gitlab-ce diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 1fb352306d7..ccf301e6c78 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -487,7 +487,7 @@ Style/EmptyLiteral: - 'lib/gitlab/fogbugz_import/importer.rb' - 'lib/gitlab/git/diff_collection.rb' - 'lib/gitlab/gitaly_client.rb' - - 'scripts/trigger-build-omnibus' + - 'scripts/trigger-build' - 'spec/features/merge_requests/versions_spec.rb' - 'spec/helpers/merge_requests_helper_spec.rb' - 'spec/lib/gitlab/request_context_spec.rb' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fb78973a727..5a66e04247e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,25 +27,26 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ - [Helping others](#helping-others) - [I want to contribute!](#i-want-to-contribute) - [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, ~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 (~P1, ~P2, ~P3 , ~P4)](#bug-priority-labels-p1-p2-p3-p4) - - [Severity labels (~S1, ~S2, ~S3 , ~S4)](#bug-severity-labels-s1-s2-s3-s4) - - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) -- [Implement design & UI elements](#implement-design--ui-elements) + - [Type labels](#type-labels) + - [Subject labels](#subject-labels) + - [Team labels](#team-labels) + - [Milestone labels](#milestone-labels) + - [Bug Priority labels](#bug-priority-labels) + - [Bug Severity labels](#bug-severity-labels) + - [Severity impact guidance](#severity-impact-guidance) + - [Label for community contributors](#label-for-community-contributors) +- [Implement design & UI elements](#implement-design-ui-elements) - [Issue tracker](#issue-tracker) - - [Issue triaging](#issue-triaging) - - [Feature proposals](#feature-proposals) - - [Issue tracker guidelines](#issue-tracker-guidelines) - - [Issue weight](#issue-weight) - - [Regression issues](#regression-issues) - - [Technical and UX debt](#technical-and-ux-debt) - - [Stewardship](#stewardship) + - [Issue triaging](#issue-triaging) + - [Feature proposals](#feature-proposals) + - [Issue tracker guidelines](#issue-tracker-guidelines) + - [Issue weight](#issue-weight) + - [Regression issues](#regression-issues) + - [Technical and UX debt](#technical-and-ux-debt) + - [Stewardship](#stewardship) - [Merge requests](#merge-requests) - - [Merge request guidelines](#merge-request-guidelines) - - [Contribution acceptance criteria](#contribution-acceptance-criteria) + - [Merge request guidelines](#merge-request-guidelines) + - [Contribution acceptance criteria](#contribution-acceptance-criteria) - [Definition of done](#definition-of-done) - [Style guides](#style-guides) - [Code of conduct](#code-of-conduct) @@ -145,7 +146,7 @@ labels, you can _always_ add the team and type, and often also the subject. [milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones [labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels -### Type labels (~"feature proposal", ~bug, ~customer, etc.) +### Type labels Type labels are very important. They define what kind of issue this is. Every issue should have one or more. @@ -161,28 +162,41 @@ already reserved for subject labels). The descriptions on the [labels page][labels-page] explain what falls under each type label. -### Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.) +### Subject labels Subject labels are labels that define what area or feature of GitLab this issue hits. They are not always necessary, but very convenient. +Examples of subject labels are ~wiki, ~ldap, ~api, +~issues, ~"merge requests", ~labels, and ~"container registry". + If you are an expert in a particular area, it makes it easier to find issues to work on. You can also subscribe to those labels to receive an email each time an issue is labeled with a subject label corresponding to your expertise. -Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, -~issues, ~"merge requests", ~labels, and ~"container registry". - Subject labels are always all-lowercase. -### Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.) +### Team labels 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 ~Distribution, ~"CI/CD", ~Discussion, ~Documentation, ~Quality, -~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products", ~"Configuration", and ~"UX". +The current team labels are: + +- ~Configuration +- ~"CI/CD" +- ~Discussion +- ~Distribution +- ~Documentation +- ~Geo +- ~Gitaly +- ~Monitoring +- ~Platform +- ~Quality +- ~Release +- ~"Security Products" +- ~UX The descriptions on the [labels page][labels-page] explain what falls under the responsibility of each team. @@ -193,7 +207,7 @@ indicate if an issue needs backend work, frontend work, or both. Team labels are always capitalized so that they show up as the first label for any issue. -### Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release") +### Milestone labels Milestone labels help us clearly communicate expectations of the work for the release. There are three levels of Milestone labels: @@ -211,9 +225,9 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable 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, ~P4) +### Bug Priority labels -Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. +Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. If there are multiple defects, the priority decides which defect has to be fixed immediately versus later. This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes. @@ -224,7 +238,7 @@ This label documents the planned timeline & urgency which is used to measure aga | ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | | | ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented | -### Bug Severity labels (~S1, ~S2, ~S3, ~S4) +### Bug Severity labels Severity labels help us clearly communicate the impact of a ~bug on users. @@ -240,11 +254,11 @@ Severity labels help us clearly communicate the impact of a ~bug on users. | Label | Security Impact | Availability / Performance Impact | |-------|---------------------------------------------------------------------|--------------------------------------------------------------| | ~S1 | >50% users impacted (possible company extinction level event) | | -| ~S2 | Many users or multiple paid customers impacted (but not apocalyptic)| The issue is (almost) guaranteed to occur in the near future | +| ~S2 | Many users or multiple paid customers impacted (but not apocalyptic)| The issue is (almost) guaranteed to occur in the near future | | ~S3 | A few users or a single paid customer impacted | The issue is likely to occur in the near future | | ~S4 | No paid users/customer impacted, or expected impact within 30 days | The issue _may_ occur but it's not likely | -### Label for community contributors (~"Accepting Merge Requests") +### Label for community contributors Issues that are beneficial to our users, 'nice to haves', that we currently do not have the capacity for or want to give the priority to, are labeled as @@ -300,20 +314,29 @@ For guidance on UX implementation at GitLab, please refer to our [Design System] The UX team uses labels to manage their workflow. -The ~"UX" label on an issue is a signal to the UX team that it will need UX attention. +The ~"UX" label on an issue is a signal to the UX team that it will need UX attention. To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/engineering/ux) of the handbook. -Once an issue has been worked on and is ready for development, a UXer applies the ~"UX ready" label to that issue. +Once an issue has been worked on and is ready for development, a UXer removes the ~"UX" label and applies the ~"UX ready" label to that issue. -The UX team has a special type label called ~"design artifact". This label indicates that the final output -for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone. -Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is +The UX team has a special type label called ~"design artifact". This label indicates that the final output +for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone. +Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is needed until the solution has been decided. ~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone. -Once the ~"design artifact" issue has been completed, the UXer removes the ~"design artifact" label and applies the ~"UX ready" label. The Product Manager can use the -existing issue or decide to create a whole new issue for the purpose of development. +To prevent the misunderstanding that a feature will be be delivered in the +assigned milestone, when only UX design is planned for that milestone, the +Product Manager should create a separate issue for the ~"design artifact", +assign the ~UX, ~"design artifact" and ~"Deliverable" labels, add a milestone +and use a title that makes it clear that the scheduled issue is design only +(e.g. `Design exploration for XYZ`). + +When the ~"design artifact" issue has been completed, the UXer removes the ~UX +label, adds the ~"UX ready" label and closes the issue. This indicates the +design artifact is complete. The UXer will also copy the designs to related +issues for implementation in an upcoming milestone. ## Issue tracker diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 952e27df29d..223717f1818 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -299,7 +299,7 @@ GEM flowdock (~> 0.7) gitlab-grit (>= 2.4.1) multi_json - gitlab-gollum-lib (4.2.7.2) + gitlab-gollum-lib (4.2.7.4) gemojione (~> 3.2) github-markup (~> 1.6) gollum-grit_adapter (~> 1.0) @@ -307,7 +307,7 @@ GEM rouge (~> 3.1) sanitize (~> 2.1) stringex (~> 2.6) - gitlab-gollum-rugged_adapter (0.4.4) + gitlab-gollum-rugged_adapter (0.4.4.1) mime-types (>= 1.15) rugged (~> 0.25) gitlab-grit (2.8.2) diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 75cf90de0b5..1ea6dd909e9 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -119,7 +119,7 @@ const gfmRules = { return el.outerHTML; }, 'dl'(el, text) { - let lines = text.trim().split('\n'); + let lines = text.replace(/\n\n/g, '\n').trim().split('\n'); // Add two spaces to the front of subsequent list items lines, // or leave the line entirely blank. lines = lines.map((l) => { @@ -129,9 +129,13 @@ const gfmRules = { return ` ${line}`; }); - return `<dl>\n${lines.join('\n')}\n</dl>`; + return `<dl>\n${lines.join('\n')}\n</dl>\n`; }, - 'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr, summary, details'(el, text) { + 'dt, dd, summary, details'(el, text) { + const tag = el.nodeName.toLowerCase(); + return `<${tag}>${text}</${tag}>\n`; + }, + 'sup, sub, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) { const tag = el.nodeName.toLowerCase(); return `<${tag}>${text}</${tag}>`; }, @@ -215,22 +219,22 @@ const gfmRules = { return text.replace(/^- /mg, '1. '); }, 'h1'(el, text) { - return `# ${text.trim()}`; + return `# ${text.trim()}\n`; }, 'h2'(el, text) { - return `## ${text.trim()}`; + return `## ${text.trim()}\n`; }, 'h3'(el, text) { - return `### ${text.trim()}`; + return `### ${text.trim()}\n`; }, 'h4'(el, text) { - return `#### ${text.trim()}`; + return `#### ${text.trim()}\n`; }, 'h5'(el, text) { - return `##### ${text.trim()}`; + return `##### ${text.trim()}\n`; }, 'h6'(el, text) { - return `###### ${text.trim()}`; + return `###### ${text.trim()}\n`; }, 'strong'(el, text) { return `**${text}**`; @@ -241,11 +245,13 @@ const gfmRules = { 'del'(el, text) { return `~~${text}~~`; }, - 'sup'(el, text) { - return `^${text}`; - }, 'hr'(el) { - return '-----'; + // extra leading \n is to ensure that there is a blank line between + // a list followed by an hr, otherwise this breaks old redcarpet rendering + return '\n-----\n'; + }, + 'p'(el, text) { + return `${text.trim()}\n`; }, 'table'(el) { const theadEl = el.querySelector('thead'); @@ -263,7 +269,9 @@ const gfmRules = { let before = ''; let after = ''; - switch (cell.style.textAlign) { + const alignment = cell.align || cell.style.textAlign; + + switch (alignment) { case 'center': before = ':'; after = ':'; diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index f61c0be9230..5485248cfaf 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -70,7 +70,7 @@ export default class BlobViewer { const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)'); let initialViewerName = initialViewer.getAttribute('data-type'); - if (this.switcher && location.hash.indexOf('#L') === 0) { + if (this.switcher && window.location.hash.indexOf('#L') === 0) { initialViewerName = 'simple'; } diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 86b888c66c8..7920e08e4d8 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -1,6 +1,5 @@ /* eslint-disable comma-dangle, space-before-function-paren, one-var */ -import $ from 'jquery'; import Sortable from 'sortablejs'; import Vue from 'vue'; import AccessorUtilities from '../../lib/utils/accessor'; @@ -57,40 +56,6 @@ gl.issueBoards.Board = Vue.extend({ }); }, deep: true, - }, - detailIssue: { - handler () { - if (!Object.keys(this.detailIssue.issue).length) return; - - const issue = this.list.findIssue(this.detailIssue.issue.id); - - if (issue) { - const offsetLeft = this.$el.offsetLeft; - const boardsList = document.querySelectorAll('.boards-list')[0]; - const left = boardsList.scrollLeft - offsetLeft; - let right = (offsetLeft + this.$el.offsetWidth); - - if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) { - // -290 here because width of boardsList is animating so therefore - // getting the width here is incorrect - // 290 is the width of the sidebar - right -= (boardsList.offsetWidth - 290); - } else { - right -= boardsList.offsetWidth; - } - - if (right - boardsList.scrollLeft > 0) { - $(boardsList).animate({ - scrollLeft: right - }, this.sortableOptions.animation); - } else if (left > 0) { - $(boardsList).animate({ - scrollLeft: offsetLeft - }, this.sortableOptions.animation); - } - } - }, - deep: true } }, mounted () { diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js index 4482b3b3e70..4dd9aebeed9 100644 --- a/app/assets/javascripts/boards/components/board_delete.js +++ b/app/assets/javascripts/boards/components/board_delete.js @@ -17,7 +17,7 @@ gl.issueBoards.BoardDelete = Vue.extend({ deleteBoard () { $(this.$el).tooltip('hide'); - if (confirm('Are you sure you want to delete this list?')) { + if (window.confirm('Are you sure you want to delete this list?')) { this.list.destroy(); } } diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 7dc83843e9b..ffe86468b12 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -145,6 +145,6 @@ gl.issueBoards.BoardsStore = { return filteredList[0]; }, updateFiltersUrl () { - history.pushState(null, null, `?${this.filter.path}`); + window.history.pushState(null, null, `?${this.filter.path}`); } }; diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 7e2a3573f81..9a3ea7a55b6 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -45,7 +45,7 @@ export default class CommitsList { this.content.fadeTo('fast', 1.0); // Change url so if user reload a page - search results are saved - history.replaceState({ + window.history.replaceState({ page: commitsUrl, }, document.title, commitsUrl); }) diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 2cfa13fdc75..d91e4809126 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -98,7 +98,7 @@ export default { }, disableKey(deployKey, callback) { // eslint-disable-next-line no-alert - if (confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))) { + if (window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))) { this.service .disableKey(deployKey.id) .then(this.fetchKeys) diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue index faaaf899a0d..eba58bedd6d 100644 --- a/app/assets/javascripts/environments/components/environment_stop.vue +++ b/app/assets/javascripts/environments/components/environment_stop.vue @@ -40,7 +40,7 @@ methods: { onClick() { // eslint-disable-next-line no-alert - if (confirm('Are you sure you want to stop this environment?')) { + if (window.confirm('Are you sure you want to stop this environment?')) { this.isLoading = true; $(this.$el).tooltip('dispose'); diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue index 3d59410cbc2..d0fb0e3d99e 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -34,6 +34,10 @@ export default { type: String, required: true, }, + actionBtnIcon: { + type: String, + required: true, + }, itemActionComponent: { type: String, required: true, @@ -53,26 +57,21 @@ export default { required: true, }, }, - data() { - return { - showActionButton: false, - }; - }, computed: { titleText() { return sprintf(__('%{title} changes'), { title: this.title, }); }, + filesLength() { + return this.fileList.length; + }, }, methods: { ...mapActions(['stageAllChanges', 'unstageAllChanges']), actionBtnClicked() { this[this.action](); }, - setShowActionButton(show) { - this.showActionButton = show; - }, }, }; </script> @@ -83,8 +82,6 @@ export default { > <header class="multi-file-commit-panel-header" - @mouseenter="setShowActionButton(true)" - @mouseleave="setShowActionButton(false)" > <div class="multi-file-commit-panel-header-title" @@ -95,24 +92,40 @@ export default { :size="18" /> {{ titleText }} - <span - v-show="!showActionButton" - class="ide-commit-file-count" - > - {{ fileList.length }} - </span> - <button - v-show="showActionButton" - type="button" - class="btn btn-blank btn-link ide-staged-action-btn" - @click="actionBtnClicked" - > - {{ actionBtnText }} - </button> + <div class="d-flex ml-auto"> + <button + v-tooltip + v-show="filesLength" + :class="{ + 'd-flex': filesLength + }" + :title="actionBtnText" + type="button" + class="btn btn-default ide-staged-action-btn p-0 order-1 align-items-center" + data-placement="bottom" + data-container="body" + data-boundary="viewport" + @click="actionBtnClicked" + > + <icon + :name="actionBtnIcon" + :size="12" + class="ml-auto mr-auto" + /> + </button> + <span + :class="{ + 'rounded-right': !filesLength + }" + class="ide-commit-file-count order-0 rounded-left text-center" + > + {{ filesLength }} + </span> + </div> </div> </header> <ul - v-if="fileList.length" + v-if="filesLength" class="multi-file-commit-list list-unstyled append-bottom-0" > <li diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue index 2254271c679..d376a004e84 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue @@ -38,14 +38,17 @@ export default { return this.modifiedFilesLength ? 'multi-file-modified' : ''; }, additionsTooltip() { - return sprintf(n__('1 %{type} addition', '%d %{type} additions', this.addedFilesLength), { + return sprintf(n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength), { type: this.title.toLowerCase(), + count: this.addedFilesLength, }); }, modifiedTooltip() { return sprintf( - n__('1 %{type} modification', '%d %{type} modifications', this.modifiedFilesLength), - { type: this.title.toLowerCase() }, + n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength), { + type: this.title.toLowerCase(), + count: this.modifiedFilesLength, + }, ); }, titleTooltip() { diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue index 2ecf9af4bf0..5cda7967130 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue @@ -1,5 +1,6 @@ <script> import { mapActions } from 'vuex'; +import tooltip from '~/vue_shared/directives/tooltip'; import Icon from '~/vue_shared/components/icon.vue'; import StageButton from './stage_button.vue'; import UnstageButton from './unstage_button.vue'; @@ -11,6 +12,9 @@ export default { StageButton, UnstageButton, }, + directives: { + tooltip, + }, props: { file: { type: Object, @@ -50,6 +54,9 @@ export default { isActive() { return this.activeFileKey === this.fullKey; }, + tooltipTitle() { + return this.file.path === this.file.name ? '' : this.file.path; + }, }, methods: { ...mapActions([ @@ -81,29 +88,30 @@ export default { </script> <template> - <div - :class="{ - 'is-active': isActive - }" - class="multi-file-commit-list-item" - > + <div class="multi-file-commit-list-item position-relative"> <button + v-tooltip + :title="tooltipTitle" + :class="{ + 'is-active': isActive + }" type="button" - class="multi-file-commit-list-path" + class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0" @dblclick="fileAction" @click="openFileInEditor" > - <span class="multi-file-commit-list-file-path"> + <span class="multi-file-commit-list-file-path d-flex align-items-center"> <icon :name="iconName" :size="16" :css-classes="iconClass" - />{{ file.path }} + />{{ file.name }} </span> </button> <component :is="actionComponent" :path="file.path" + class="d-flex position-absolute" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue index a786ec80ac2..7014b9f605e 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue @@ -25,15 +25,17 @@ export default { <template> <div v-once - class="multi-file-discard-btn" + class="multi-file-discard-btn dropdown" > <button v-tooltip :aria-label="__('Stage changes')" :title="__('Stage changes')" type="button" - class="btn btn-blank append-right-5" + class="btn btn-blank append-right-5 d-flex align-items-center" data-container="body" + data-boundary="viewport" + data-placement="bottom" @click.stop="stageChange(path)" > <icon @@ -43,17 +45,31 @@ export default { </button> <button v-tooltip - :aria-label="__('Discard changes')" - :title="__('Discard changes')" + :title="__('More actions')" type="button" - class="btn btn-blank" + class="btn btn-blank d-flex align-items-center" data-container="body" - @click.stop="discardFileChanges(path)" + data-boundary="viewport" + data-placement="bottom" + data-toggle="dropdown" + data-display="static" > <icon :size="12" - name="remove" + name="more" /> </button> + <div class="dropdown-menu dropdown-menu-right"> + <ul> + <li> + <button + type="button" + @click.stop="discardFileChanges(path)" + > + {{ __('Discard changes') }} + </button> + </li> + </ul> + </div> </div> </template> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue index 34b366f63ac..9cec73ec00e 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue @@ -32,8 +32,10 @@ export default { :aria-label="__('Unstage changes')" :title="__('Unstage changes')" type="button" - class="btn btn-blank" + class="btn btn-blank d-flex align-items-center" data-container="body" + data-boundary="viewport" + data-placement="bottom" @click="unstageChange(path)" > <icon diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 4d29e9f8060..5757dfdc925 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -94,7 +94,7 @@ export default { <p class="append-bottom-0"> {{ __('Found errors in your .gitlab-ci.yml:') }} </p> - <p class="append-bottom-0"> + <p class="append-bottom-0 break-word"> {{ latestPipeline.yamlError }} </p> <p diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index 01df0019fd4..c2c678ff0be 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -93,23 +93,25 @@ export default { :title="__('Unstaged')" :key-prefix="$options.stageKeys.unstaged" :file-list="changedFiles" - :action-btn-text="__('Stage all')" + :action-btn-text="__('Stage all changes')" :active-file-key="activeFileKey" - class="is-first" - icon-name="unstaged" action="stageAllChanges" + action-btn-icon="mobile-issue-close" item-action-component="stage-button" + class="is-first" + icon-name="unstaged" /> <commit-files-list :title="__('Staged')" :key-prefix="$options.stageKeys.staged" :file-list="stagedFiles" - :action-btn-text="__('Unstage all')" + :action-btn-text="__('Unstage all changes')" :staged-list="true" :active-file-key="activeFileKey" - icon-name="staged" action="unstageAllChanges" + action-btn-icon="history" item-action-component="unstage-button" + icon-name="staged" /> </template> <empty-state diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 24b6a4fdea1..08ee12fd98f 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -20,7 +20,13 @@ export default { }, }, computed: { - ...mapState(['rightPanelCollapsed', 'viewer', 'panelResizing', 'currentActivityView']), + ...mapState([ + 'rightPanelCollapsed', + 'viewer', + 'panelResizing', + 'currentActivityView', + 'rightPane', + ]), ...mapGetters([ 'currentMergeRequest', 'getStagedFile', @@ -88,6 +94,9 @@ export default { this.editor.updateDimensions(); } }, + rightPane() { + this.editor.updateDimensions(); + }, }, beforeDestroy() { this.editor.dispose(); diff --git a/app/assets/javascripts/ide/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js index e74c4046330..f09930e8158 100644 --- a/app/assets/javascripts/ide/lib/diff/diff_worker.js +++ b/app/assets/javascripts/ide/lib/diff/diff_worker.js @@ -1,8 +1,10 @@ import { computeDiff } from './diff'; +// eslint-disable-next-line no-restricted-globals self.addEventListener('message', (e) => { const data = e.data; + // eslint-disable-next-line no-restricted-globals self.postMessage({ path: data.path, changes: computeDiff(data.originalContent, data.newContent), diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js index 9f895d49f2e..e35595ab1fd 100644 --- a/app/assets/javascripts/ide/lib/editor_options.js +++ b/app/assets/javascripts/ide/lib/editor_options.js @@ -12,5 +12,6 @@ export const defaultEditorOptions = { export default [ { readOnly: model => !!model.file.file_lock, + quickSuggestions: model => !(model.language === 'markdown'), }, ]; diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index e8b51f2b516..da9de25302a 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -9,7 +9,7 @@ export default { return Vue.http.get(endpoint, { params: { format: 'json' } }); }, getFileData(endpoint) { - return Vue.http.get(endpoint, { params: { format: 'json' } }); + return Vue.http.get(endpoint, { params: { format: 'json', viewer: 'none' } }); }, getRawFileData(file) { if (file.tempFile) { diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index 0a0db4033c8..7219abc4185 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -49,31 +49,6 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => { commit(rootTypes.SET_LAST_COMMIT_MSG, commitMsg, { root: true }); }; -export const checkCommitStatus = ({ rootState }) => - service - .getBranchData(rootState.currentProjectId, rootState.currentBranchId) - .then(({ data }) => { - const { id } = data.commit; - const selectedBranch = - rootState.projects[rootState.currentProjectId].branches[rootState.currentBranchId]; - - if (selectedBranch.workingReference !== id) { - return true; - } - - return false; - }) - .catch(() => - flash( - __('Error checking branch data. Please try again.'), - 'alert', - document, - null, - false, - true, - ), - ); - export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data }) => { const selectedProject = rootState.projects[rootState.currentProjectId]; const lastCommit = { @@ -128,24 +103,17 @@ export const updateFilesAfterCommit = ({ commit, dispatch, rootState }, { data } export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => { const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH; - const payload = createCommitPayload(getters.branchName, newBranch, state, rootState); - const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus'); + const payload = createCommitPayload({ + branch: getters.branchName, + newBranch, + state, + rootState, + }); commit(types.UPDATE_LOADING, true); - return getCommitStatus - .then( - branchChanged => - new Promise(resolve => { - if (branchChanged) { - // show the modal with a Bootstrap call - $('#ide-create-branch-modal').modal('show'); - } else { - resolve(); - } - }), - ) - .then(() => service.commit(rootState.currentProjectId, payload)) + return service + .commit(rootState.currentProjectId, payload) .then(({ data }) => { commit(types.UPDATE_LOADING, false); @@ -220,12 +188,16 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo ); }) .catch(err => { - let errMsg = __('Error committing changes. Please try again.'); - if (err.response.data && err.response.data.message) { - errMsg += ` (${stripHtml(err.response.data.message)})`; + if (err.response.status === 400) { + $('#ide-create-branch-modal').modal('show'); + } else { + let errMsg = __('Error committing changes. Please try again.'); + if (err.response.data && err.response.data.message) { + errMsg += ` (${stripHtml(err.response.data.message)})`; + } + flash(errMsg, 'alert', document, null, false, true); + window.dispatchEvent(new Event('resize')); } - flash(errMsg, 'alert', document, null, false, true); - window.dispatchEvent(new Event('resize')); commit(types.UPDATE_LOADING, false); }); diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js index 4e1df80b3a2..551dd322c9b 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js @@ -31,15 +31,16 @@ export const openMergeRequest = ({ commit, dispatch }, { projectPath, id }) => { commit(rootTypes.CLEAR_PROJECTS, null, { root: true }); commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { root: true }); commit(rootTypes.RESET_OPEN_FILES, null, { root: true }); - dispatch('pipelines/resetLatestPipeline', null, { root: true }); dispatch('setCurrentBranchId', '', { root: true }); dispatch('pipelines/stopPipelinePolling', null, { root: true }) .then(() => { + dispatch('pipelines/resetLatestPipeline', null, { root: true }); dispatch('pipelines/clearEtagPoll', null, { root: true }); }) .catch(e => { throw e; }); + dispatch('setRightPane', null, { root: true }); router.push(`/project/${projectPath}/merge_requests/${id}`); }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 6718f7eae4e..fe1dc9ac8f8 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -106,7 +106,9 @@ export const fetchJobTrace = ({ dispatch, state }) => { .catch(() => dispatch('receiveJobTraceError')); }; -export const resetLatestPipeline = ({ commit }) => +export const resetLatestPipeline = ({ commit }) => { commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, null); + commit(types.SET_DETAIL_JOB, null); +}; export default () => {}; diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js index 5826f6cb828..46547820425 100644 --- a/app/assets/javascripts/ide/stores/mutations/file.js +++ b/app/assets/javascripts/ide/stores/mutations/file.js @@ -47,6 +47,7 @@ export default { baseRaw: null, html: data.html, size: data.size, + lastCommitSha: data.last_commit_sha, }); }, [types.SET_FILE_RAW_DATA](state, { file, raw }) { diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js index a04a33cd12d..10368a4d97c 100644 --- a/app/assets/javascripts/ide/stores/utils.js +++ b/app/assets/javascripts/ide/stores/utils.js @@ -17,6 +17,7 @@ export const dataStructure = () => ({ changed: false, staged: false, lastCommitPath: '', + lastCommitSha: '', lastCommit: { id: '', url: '', @@ -104,7 +105,7 @@ export const setPageTitle = title => { document.title = title; }; -export const createCommitPayload = (branch, newBranch, state, rootState) => ({ +export const createCommitPayload = ({ branch, newBranch, state, rootState }) => ({ branch, commit_message: state.commitMessage, actions: rootState.stagedFiles.map(f => ({ @@ -112,6 +113,7 @@ export const createCommitPayload = (branch, newBranch, state, rootState) => ({ file_path: f.path, content: f.content, encoding: f.base64 ? 'base64' : 'text', + last_commit_id: newBranch ? undefined : f.lastCommitSha, })), start_branch: newBranch ? rootState.currentBranchId : undefined, }); 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 0a1c253c637..fa35c215880 100644 --- a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js +++ b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js @@ -1,6 +1,7 @@ import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils'; import { decorateData, sortTree } from '../utils'; +// eslint-disable-next-line no-restricted-globals self.addEventListener('message', e => { const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data; @@ -89,6 +90,7 @@ self.addEventListener('message', e => { return acc; }, {}); + // eslint-disable-next-line no-restricted-globals self.postMessage({ entries, treeList: sortTree(treeList), diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index e87a8ed7fea..b6364318537 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -226,7 +226,7 @@ .then(res => res.data) .then(data => this.checkForSpam(data)) .then((data) => { - if (location.pathname !== data.web_url) { + if (window.location.pathname !== data.web_url) { visitUrl(data.web_url); } diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index 8cb0ab22bfb..597c6d69a81 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -38,7 +38,7 @@ }, deleteIssuable() { // eslint-disable-next-line no-alert - if (confirm('Issue will be removed! Are you sure?')) { + if (window.confirm('Issue will be removed! Are you sure?')) { this.deleteLoading = true; eventHub.$emit('delete.issuable'); diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue index 1c2789f154a..ad0d40faf32 100644 --- a/app/assets/javascripts/issue_show/components/locked_warning.vue +++ b/app/assets/javascripts/issue_show/components/locked_warning.vue @@ -2,7 +2,7 @@ export default { computed: { currentPath() { - return location.pathname; + return window.location.pathname; }, }, }; diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js index dbbf1637a47..9482d131344 100644 --- a/app/assets/javascripts/lazy_loader.js +++ b/app/assets/javascripts/lazy_loader.js @@ -44,8 +44,8 @@ export default class LazyLoader { requestAnimationFrame(() => this.checkElementsInView()); } checkElementsInView() { - const scrollTop = pageYOffset; - const visHeight = scrollTop + innerHeight + SCROLL_THRESHOLD; + const scrollTop = window.pageYOffset; + const visHeight = scrollTop + window.innerHeight + SCROLL_THRESHOLD; // Loading Images which are in the current viewport or close to them this.lazyImages = this.lazyImages.filter((selectedImage) => { diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js index 3873f4528ce..c28ed04f94f 100644 --- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js +++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js @@ -93,7 +93,7 @@ export default class LinkedTabs { const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`; - history.replaceState({ + window.history.replaceState({ url: newState, }, document.title, newState); return newState; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index d55d0585031..d0b0e5e1ba1 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -197,7 +197,10 @@ export const insertText = (target, text) => { // eslint-disable-next-line no-param-reassign target.value = newText; // eslint-disable-next-line no-param-reassign - target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; + target.selectionStart = selectionStart + insertedText.length; + + // eslint-disable-next-line no-param-reassign + target.selectionEnd = selectionStart + insertedText.length; // Trigger autosave target.dispatchEvent(new Event('input')); diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index a02c79b787e..f086d962221 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -12,7 +12,7 @@ export function formatRelevantDigits(number) { let digitsLeft = ''; let relevantDigits = 0; let formattedNumber = ''; - if (!isNaN(Number(number))) { + if (!Number.isNaN(Number(number))) { digitsLeft = number.toString().split('.')[0]; switch (digitsLeft.length) { case 1: diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index f2323f57455..303c5d8a894 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -35,7 +35,7 @@ const LineHighlighter = function(options = {}) { options.highlightLineClass = options.highlightLineClass || 'hll'; options.fileHolderSelector = options.fileHolderSelector || '.file-holder'; options.scrollFileHolder = options.scrollFileHolder || false; - options.hash = options.hash || location.hash; + options.hash = options.hash || window.location.hash; this.options = options; this._hash = options.hash; @@ -145,6 +145,8 @@ LineHighlighter.prototype.highlightRange = function(range) { var i, lineNumber, ref, ref1, results; if (range[1]) { results = []; + + // eslint-disable-next-line no-multi-assign for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) { results.push(this.highlightLine(lineNumber)); } @@ -170,7 +172,7 @@ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) { // // This method is stubbed in tests. LineHighlighter.prototype.__setLocationHash__ = function(value) { - return history.pushState({ + return window.history.pushState({ url: value // We're using pushState instead of assigning location.hash directly to // prevent the page from scrolling on the hashchange event diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js index 326d4523cce..491858c3602 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js @@ -1,13 +1,9 @@ -/* eslint-disable new-cap, comma-dangle, no-new */ - import $ from 'jquery'; import Vue from 'vue'; -import Flash from '../flash'; +import createFlash from '../flash'; import initIssuableSidebar from '../init_issuable_sidebar'; import './merge_conflict_store'; import MergeConflictsService from './merge_conflict_service'; -import './mixins/line_conflict_utils'; -import './mixins/line_conflict_actions'; import './components/diff_file_editor'; import './components/inline_conflict_lines'; import './components/parallel_conflict_lines'; @@ -19,7 +15,7 @@ export default function initMergeConflicts() { const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; const mergeConflictsService = new MergeConflictsService({ conflictsPath: conflictsEl.dataset.conflictsPath, - resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath + resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath, }); initIssuableSidebar(); @@ -29,17 +25,26 @@ export default function initMergeConflicts() { components: { 'diff-file-editor': gl.mergeConflicts.diffFileEditor, 'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines, - 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines + 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines, }, data: mergeConflictsStore.state, computed: { - conflictsCountText() { return mergeConflictsStore.getConflictsCountText(); }, - readyToCommit() { return mergeConflictsStore.isReadyToCommit(); }, - commitButtonText() { return mergeConflictsStore.getCommitButtonText(); }, - showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); } + conflictsCountText() { + return mergeConflictsStore.getConflictsCountText(); + }, + readyToCommit() { + return mergeConflictsStore.isReadyToCommit(); + }, + commitButtonText() { + return mergeConflictsStore.getCommitButtonText(); + }, + showDiffViewTypeSwitcher() { + return mergeConflictsStore.fileTextTypePresent(); + }, }, created() { - mergeConflictsService.fetchConflictsData() + mergeConflictsService + .fetchConflictsData() .then(({ data }) => { if (data.type === 'error') { mergeConflictsStore.setFailedRequest(data.message); @@ -87,9 +92,9 @@ export default function initMergeConflicts() { }) .catch(() => { mergeConflictsStore.setSubmitState(false); - new Flash('Failed to save merge conflicts resolutions. Please try again!'); + createFlash('Failed to save merge conflicts resolutions. Please try again!'); }); - } - } + }, + }, }); } diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index 325fa570f37..6da04020881 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -18,13 +18,13 @@ export default class Milestone { return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => { const $target = $(e.target); - location.hash = $target.attr('href'); + window.location.hash = $target.attr('href'); this.loadTab($target); }); } // eslint-disable-next-line class-methods-use-this loadInitialTab() { - const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`); + const $target = $(`.js-milestone-tabs a[href="${window.location.hash}"]`); if ($target.length) { $target.tab('show'); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index d269c45203a..334279137d8 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -16,10 +16,10 @@ export default class MilestoneSelect { typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; } - this.init(els, options); + MilestoneSelect.init(els, options); } - init(els, options) { + static init(els, options) { let $els = $(els); if (!els) { @@ -224,7 +224,6 @@ export default class MilestoneSelect { $selectBox.hide(); $value.css('display', ''); if (data.milestone != null) { - data.milestone.full_path = this.currentProject.full_path; data.milestone.remaining = timeFor(data.milestone.due_date); data.milestone.name = data.milestone.title; $value.html(milestoneLinkTemplate(data.milestone)); diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 21934021852..e1c8b6a6d4a 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -139,7 +139,7 @@ export default { this.updateAspectRatio = true; }, toggleAspectRatio() { - this.updatedAspectRatios = this.updatedAspectRatios += 1; + this.updatedAspectRatios += 1; if (this.store.getMetricsCount() === this.updatedAspectRatios) { this.updateAspectRatio = !this.updateAspectRatio; this.updatedAspectRatios = 0; diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index 20400154100..e5680a0499f 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -154,7 +154,7 @@ export default { point.x = e.clientX; point.y = e.clientY; point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse()); - point.x = point.x += 7; + point.x += 7; const firstTimeSeries = this.timeSeries[0]; const timeValueOverlay = firstTimeSeries.timeSeriesScaleX.invert(point.x); const overlayIndex = bisectDate(firstTimeSeries.values, timeValueOverlay, 1); diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue index 282c5c24384..92fe98508ad 100644 --- a/app/assets/javascripts/monitoring/components/graph/flag.vue +++ b/app/assets/javascripts/monitoring/components/graph/flag.vue @@ -97,7 +97,7 @@ export default { ? this.deploymentFlagData.seriesIndex : indexFromCoordinates; const value = series.values[index] && series.values[index].value; - if (isNaN(value)) { + if (Number.isNaN(value)) { return '-'; } return `${formatRelevantDigits(value)}${this.unitOfDisplay}`; diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index 4d3f1f1a7cc..ed3a27dd68b 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -73,7 +73,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom timeSeriesScaleX.ticks(d3.timeMinute, 60); timeSeriesScaleY.domain(yDom); - const defined = d => !isNaN(d.value) && d.value != null; + const defined = d => !Number.isNaN(d.value) && d.value != null; const lineFunction = d3 .line() diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index bd007c707f2..e4096ddb00d 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -112,6 +112,8 @@ export default (function() { fill: "#444" }); ref = this.days; + + // eslint-disable-next-line no-multi-assign for (mm = j = 0, len = ref.length; j < len; mm = (j += 1)) { day = ref[mm]; if (cuday !== day[0] || cumonth !== day[1]) { @@ -285,6 +287,8 @@ export default (function() { r = this.r; ref = commit.parents; results = []; + + // eslint-disable-next-line no-multi-assign for (i = j = 0, len = ref.length; j < len; i = (j += 1)) { parent = ref[i]; parentCommit = this.preparedCommits[parent[0]]; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index b2c1a26bbae..27c5dedcf0b 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -315,7 +315,7 @@ export default class Notes { if (discussionNoteForm.length) { if ($textarea.val() !== '') { if ( - !confirm('Are you sure you want to cancel creating this comment?') + !window.confirm('Are you sure you want to cancel creating this comment?') ) { return; } @@ -329,7 +329,7 @@ export default class Notes { newText = $textarea.val(); if (originalText !== newText) { if ( - !confirm('Are you sure you want to cancel editing this comment?') + !window.confirm('Are you sure you want to cancel editing this comment?') ) { return; } @@ -1675,7 +1675,7 @@ export default class Notes { <div class="note-header"> <div class="note-header-info"> <a href="/${_.escape(currentUsername)}"> - <span class="d-none d-sm-block">${_.escape( + <span class="d-none d-sm-inline-block">${_.escape( currentUsername, )}</span> <span class="note-headline-light">${_.escape( @@ -1694,7 +1694,7 @@ export default class Notes { </li>`, ); - $tempNote.find('.d-none.d-sm-block').text(_.escape(currentUserFullname)); + $tempNote.find('.d-none.d-sm-inline-block').text(_.escape(currentUserFullname)); $tempNote .find('.note-headline-light') .text(`@${_.escape(currentUsername)}`); diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index f9f5041a9f9..3f865431155 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -152,7 +152,7 @@ export default { const msg = 'Are you sure you want to cancel creating this comment?'; // eslint-disable-next-line no-alert - if (!confirm(msg)) { + if (!window.confirm(msg)) { return; } } diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index ec3ee407f0a..713f93456b1 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -77,7 +77,7 @@ export default { }, deleteHandler() { // eslint-disable-next-line no-alert - if (confirm('Are you sure you want to delete this comment?')) { + if (window.confirm('Are you sure you want to delete this comment?')) { this.isDeleting = true; this.deleteNote(this.note) @@ -129,7 +129,7 @@ export default { formCancelHandler(shouldConfirm, isDirty) { if (shouldConfirm && isDirty) { // eslint-disable-next-line no-alert - if (!confirm('Are you sure you want to cancel editing this comment?')) + if (!window.confirm('Are you sure you want to cancel editing this comment?')) return; } this.$refs.noteBody.resetAutoSave(); diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js index 653e2502d01..37336a8cb69 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js @@ -36,7 +36,9 @@ export default (function() { var author_graph, author_header; author_header = _this.create_author_header(d); $(".contributors-list").append(author_header); - _this.authors[d.author_name] = author_graph = new ContributorsAuthorGraph(d.dates); + + author_graph = new ContributorsAuthorGraph(d.dates); + _this.authors[d.author_name] = author_graph; return author_graph.draw(); }; })(this)); diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js index 77135ad1f0e..165446a4db6 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js @@ -111,10 +111,15 @@ export default { parse_log_entry: function(log_entry, field, date_range) { var parsed_entry; parsed_entry = {}; + parsed_entry.author_name = log_entry.author_name; parsed_entry.author_email = log_entry.author_email; parsed_entry.dates = {}; - parsed_entry.commits = parsed_entry.additions = parsed_entry.deletions = 0; + + parsed_entry.commits = 0; + parsed_entry.additions = 0; + parsed_entry.deletions = 0; + _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) { return function(value, key) { if (_this.in_range(value.date, date_range)) { diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 9404b06615e..a2ca03536f2 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -180,14 +180,14 @@ export default class UserTabs { } toggleLoading(status) { - return this.$parentEl.find('.loading-status .loading').toggleClass('hidden', !status); + return this.$parentEl.find('.loading-status .loading').toggleClass('hide', !status); } setCurrentAction(source) { let newState = source; newState = newState.replace(/\/+$/, ''); newState += this.windowLocation.search + this.windowLocation.hash; - history.replaceState( + window.history.replaceState( { url: newState, }, diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index d765ff3b41c..dc7d6d29b8f 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -56,7 +56,7 @@ export default { <gl-modal :id="`modal-peek-${metric}-details`" :header-title-text="header" - modal-size="lg" + modal-size="xl" class="performance-bar-modal" > <table @@ -71,7 +71,7 @@ export default { <td v-for="key in keys" :key="key" - class="break-word all-words" + class="break-word" > {{ item[key] }} </td> diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index db0505a55fe..1f152ed438d 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -91,6 +91,7 @@ export default { class="js-ci-action btn btn-blank btn-transparent ci-action-icon-container ci-action-icon-wrapper" data-container="body" + data-boundary="viewport" @click="onClickAction" > <icon :name="actionIcon"/> diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 04120d45834..e047d10ac93 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -87,6 +87,7 @@ export default { data-toggle="dropdown" data-container="body" data-boundary="viewport" + data-display="static" class="dropdown-menu-toggle build-content" > diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 4dd2fb9fbed..b9231c002fd 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -165,6 +165,7 @@ export default { class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button" data-placement="top" data-toggle="dropdown" + data-display="static" type="button" aria-haspopup="true" aria-expanded="false" diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index 8f93156cdd1..ba120c4bbdf 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -139,6 +139,8 @@ import _ from 'underscore'; var array, binary, i, k, len, v; binary = atob(dataURL.split(',')[1]); array = []; + + // eslint-disable-next-line no-multi-assign for (k = i = 0, len = binary.length; i < len; k = (i += 1)) { v = binary[k]; array.push(binary.charCodeAt(k)); diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 4c4acd487f8..17497283695 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -91,6 +91,8 @@ export default class ProjectFindFile { var blobItemUrl, filePath, html, i, j, len, matches, results; this.element.find(".tree-table > tbody").empty(); results = []; + + // eslint-disable-next-line no-multi-assign for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) { filePath = filePaths[i]; if (i === 20) { @@ -150,7 +152,7 @@ export default class ProjectFindFile { } goToTree() { - return location.href = this.options.treeUrl; + return window.location.href = this.options.treeUrl; } goToBlob() { diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js index d2d26d6f67e..5a0d2b642eb 100644 --- a/app/assets/javascripts/project_import.js +++ b/app/assets/javascripts/project_import.js @@ -2,7 +2,7 @@ import { visitUrl } from './lib/utils/url_utility'; export default function projectImport() { setTimeout(() => { - visitUrl(location.href); + visitUrl(window.location.href); }, 5000); } diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 888b1d6ce33..002edb4663c 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -90,7 +90,7 @@ const bindEvents = () => { function chooseTemplate() { $('.template-option').hide(); $projectFieldsForm.addClass('selected'); - $selectedIcon.removeClass('active'); + $selectedIcon.removeClass('d-block'); const value = $(this).val(); const templates = { rails: { @@ -109,7 +109,7 @@ const bindEvents = () => { const selectedTemplate = templates[value]; $selectedTemplateText.text(selectedTemplate.text); - $(selectedTemplate.icon).addClass('active'); + $(selectedTemplate.icon).addClass('d-block'); $templateProjectNameInput.focus(); } diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js index 1a75fdd75db..078ccbbbac2 100644 --- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js +++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js @@ -107,7 +107,7 @@ export default class PrometheusMetrics { if (data && data.success) { stop(data); } else { - this.backOffRequestCounter = this.backOffRequestCounter += 1; + this.backOffRequestCounter += 1; if (this.backOffRequestCounter < 3) { next(); } else { diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 2da022fde63..ef3c71eeafe 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -438,7 +438,7 @@ export default class SearchAutocomplete { } onClick(item, $el, e) { - if (location.pathname.indexOf(item.url) !== -1) { + if (window.location.pathname.indexOf(item.url) !== -1) { if (!e.metaKey) e.preventDefault(); if (!this.badgePresent) { if (item.category === 'Projects') { diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js index eecde4550f9..37b4a2a4c63 100644 --- a/app/assets/javascripts/settings_panels.js +++ b/app/assets/javascripts/settings_panels.js @@ -42,8 +42,8 @@ export default function initSettingsPanels() { } }); - if (location.hash) { - const $target = $(location.hash); + if (window.location.hash) { + const $target = $(window.location.hash); if ($target.length && $target.hasClass('settings')) { expandSection($target); } diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js index 1e246a56b85..8658081c6c2 100644 --- a/app/assets/javascripts/shortcuts_find_file.js +++ b/app/assets/javascripts/shortcuts_find_file.js @@ -13,8 +13,8 @@ export default class ShortcutsFindFile extends ShortcutsNavigation { element === this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter') ) { - // when press up/down key in textbox, cusor prevent to move to home/end - event.preventDefault(); + // when press up/down key in textbox, cursor prevent to move to home/end + e.preventDefault(); return false; } diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue index 1b866cca4b1..2b8d6207dea 100644 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -54,7 +54,7 @@ export default { updateConfidentialAttribute(confidential) { this.service .update('issue', { confidential }) - .then(() => location.reload()) + .then(() => window.location.reload()) .catch(() => { Flash( __( diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue index 10bedf8af1f..8bbc59f623a 100644 --- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue @@ -76,7 +76,7 @@ export default { .update(this.issuableType, { discussion_locked: locked, }) - .then(() => location.reload()) + .then(() => window.location.reload()) .catch(() => Flash( this.__( diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index d86557e870a..d9ca5e46770 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -80,7 +80,7 @@ export default class SidebarMediator { return this.service.moveIssue(this.store.moveToProjectId) .then(response => response.json()) .then((data) => { - if (location.pathname !== data.web_url) { + if (window.location.pathname !== data.web_url) { visitUrl(data.web_url); } }); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 1afff0dba38..ae27c676fa0 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -11,7 +11,7 @@ import syntaxHighlight from './syntax_highlight'; const WRAPPER = '<div class="diff-content"></div>'; const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; -const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; +const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <button class="click-to-expand btn btn-link">Click to expand it.</button></div>'; export default class SingleFileDiff { constructor(file) { diff --git a/app/assets/javascripts/snippet/snippet_embed.js b/app/assets/javascripts/snippet/snippet_embed.js index 81ec483f2d9..873a506a92f 100644 --- a/app/assets/javascripts/snippet/snippet_embed.js +++ b/app/assets/javascripts/snippet/snippet_embed.js @@ -1,5 +1,5 @@ export default () => { - const { protocol, host, pathname } = location; + const { protocol, host, pathname } = window.location; const shareBtn = document.querySelector('.js-share-btn'); const embedBtn = document.querySelector('.js-embed-btn'); const snippetUrlArea = document.querySelector('.js-snippet-url-area'); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index cd954f75613..349614460e1 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -259,6 +259,7 @@ function UsersSelect(currentUser, els, options = {}) { showDivider = 0; if (firstUser) { // Move current user to the front of the list + // eslint-disable-next-line no-multi-assign for (index = j = 0, len = users.length; j < len; index = (j += 1)) { obj = users[index]; if (obj.username === firstUser) { @@ -561,6 +562,8 @@ function UsersSelect(currentUser, els, options = {}) { if (firstUser) { // Move current user to the front of the list ref = data.results; + + // eslint-disable-next-line no-multi-assign for (index = j = 0, len = ref.length; j < len; index = (j += 1)) { obj = ref[index]; if (obj.username === firstUser) { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue index f012f9c6772..5e76f6b1cac 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue @@ -105,7 +105,7 @@ export default { MRWidgetService.fetchMetrics(this.metricsUrl) .then((res) => { if (res.status === statusCodes.NO_CONTENT) { - this.backOffRequestCounter = this.backOffRequestCounter += 1; + this.backOffRequestCounter += 1; /* eslint-disable no-unused-expressions */ this.backOffRequestCounter < 3 ? next() : stop(res); } else { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 985f44dee97..1a444c04a1d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -173,7 +173,7 @@ </a> <clipboard-button :title="__('Copy commit SHA to clipboard')" - :text="mr.shortMergeCommitSha" + :text="mr.mergeCommitSha" css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha" /> </p> diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 134aaacf9d2..c881cd496d1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -26,6 +26,7 @@ export default class MergeRequestStore { this.mergeStatus = data.merge_status; this.commitMessage = data.merge_commit_message; this.shortMergeCommitSha = data.short_merge_commit_sha; + this.mergeCommitSha = data.merge_commit_sha; this.commitMessageWithDescription = data.merge_commit_message_with_description; this.commitsCount = data.commits_count; this.divergedCommitsCount = data.diverged_commits_count; diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue index c03a342e777..b298b989203 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -1,6 +1,6 @@ <script> const buttonVariants = ['danger', 'primary', 'success', 'warning']; -const sizeVariants = ['sm', 'md', 'lg']; +const sizeVariants = ['sm', 'md', 'lg', 'xl']; export default { name: 'GlModal', diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue index eccba61a8c0..38115f268bb 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue @@ -54,7 +54,7 @@ <div class="note-header"> <div class="note-header-info"> <a :href="getUserData.path"> - <span class="d-none d-sm-block">{{ getUserData.name }}</span> + <span class="d-none d-sm-inline-block">{{ getUserData.name }}</span> <span class="note-headline-light">@{{ getUserData.username }}</span> </a> </div> diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index 810ed5bb0a6..0d8e867f41d 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -128,11 +128,6 @@ table { border-spacing: 0; } -.tooltip { - // Fix bootstrap4 bug whereby tooltips flicker when they are hovered over their borders - pointer-events: none; -} - .popover { font-size: 14px; } @@ -178,7 +173,10 @@ table { display: none; } -.badge { +// Add to .label so that old system notes that are saved to the db +// will still receive the correct styling +.badge, +.label { padding: 4px 5px; font-size: 12px; font-style: normal; @@ -213,6 +211,15 @@ table { &:not(:last-of-type) { border-bottom: 1px solid $well-inner-border; } + + p, + ol, + ul, + .form-group { + &:last-of-type { + margin-bottom: 0; + } + } } .badge.badge-gray { @@ -264,15 +271,36 @@ pre code { white-space: pre-wrap; } +.alert, +.flash-notice { + border-radius: 0; +} + +.alert-success { + background-color: $green-500; + border-color: $green-500; +} + +.alert-info { + background-color: $blue-500; + border-color: $blue-500; +} + +.alert-warning { + background-color: $orange-500; + border-color: $orange-500; +} + .alert-danger { background-color: $red-500; border-color: $red-500; } +.alert-success, +.alert-info, .alert-warning, .alert-danger, .flash-notice { - border-radius: 0; color: $white-light; h4, @@ -293,3 +321,9 @@ input[type=color].form-control { color: $gl-text-color-secondary; } } + +.project-templates-buttons { + .btn { + vertical-align: unset; + } +} diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index c5be27f2d29..0de05548c68 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -16,6 +16,7 @@ .click-to-expand { cursor: pointer; + vertical-align: initial; } } } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index e5197e27b82..326499125fc 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -440,10 +440,6 @@ img.emoji { .break-word { word-wrap: break-word; - - &.all-words { - word-break: break-word; - } } /** COMMON CLASSES **/ diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 52c3f18a682..a6e324036ae 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -10,6 +10,20 @@ @extend .alert; background-color: $blue-500; margin: 0; + + &.flash-notice-persistent { + background-color: $blue-100; + color: $gl-text-color; + + a { + color: $gl-link-color; + + &:hover { + color: $gl-link-hover-color; + text-decoration: none; + } + } + } } .flash-warning { diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss index e378e84ca1b..1cf12b1a015 100644 --- a/app/assets/stylesheets/framework/gfm.scss +++ b/app/assets/stylesheets/framework/gfm.scss @@ -19,6 +19,7 @@ .gfm-color_chip { display: inline-block; + line-height: 1; margin: 0 0 2px 4px; vertical-align: middle; border-radius: 3px; diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 55c0bc76f23..52b5f059f20 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -54,10 +54,6 @@ body { &.limit-container-width { max-width: $limited-layout-width; } - - &.limit-container-width-sm { - max-width: $limited-layout-width-sm; - } } .alert-wrapper { diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index a7896cc3fc3..ffb40166c15 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -1,3 +1,7 @@ +.modal-xl { + max-width: 98%; +} + .modal-header { background-color: $modal-body-bg; diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 2d9e9e6a67d..9dbb04e5443 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -347,7 +347,7 @@ .empty-state .project-item-select-holder.btn-group { float: none; - display: inline-block; + justify-content: center; .btn { // overrides styles applied to plain `.empty-state .btn` diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 0826bfd0035..f30f296d41f 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -244,10 +244,11 @@ $tooltip-font-size: 12px; /* * Padding */ -$gl-padding-24: 24px; -$gl-padding: 16px; -$gl-padding-8: 8px; $gl-padding-4: 4px; +$gl-padding-8: 8px; +$gl-padding: 16px; +$gl-padding-24: 24px; +$gl-padding-32: 32px; $gl-col-padding: 15px; $gl-input-padding: 10px; $gl-vert-padding: 6px; @@ -265,7 +266,6 @@ $header-height: 40px; $ide-statusbar-height: 25px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; -$limited-layout-width-sm: 790px; $container-text-max-width: 540px; $gl-avatar-size: 40px; $error-exclamation-point: $red-500; @@ -832,3 +832,6 @@ $input-border-color: $theme-gray-200; $input-color: $gl-text-color; $font-family-sans-serif: $regular_font; $font-family-monospace: $monospace_font; +$input-line-height: 20px; +$btn-line-height: 20px; +$table-accent-bg: $gray-light; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index a4ca82de90e..dc8842212e0 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -193,6 +193,10 @@ display: inline-flex; } + .ci-status-icon svg { + vertical-align: text-bottom; + } + > .ci-status-link, > .btn, > .commit-sha-group { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 7b36bcb3c7d..2e007c52592 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -23,7 +23,8 @@ position: relative; line-height: 35px; display: flex; - flex-grow: 1; + flex: 1 1; + min-width: 0; @include media-breakpoint-up(sm) { padding-left: 0; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index b42c232fd91..f9fd9f1ab8b 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -698,6 +698,8 @@ font-size: 14px; line-height: 24px; align-self: center; + overflow: hidden; + text-overflow: ellipsis; } .js-issuable-selector-wrap { diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 6882b4adb15..79cac7f4ff0 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -220,7 +220,7 @@ .label-link { display: inline-flex; - vertical-align: top; + vertical-align: text-bottom; &:hover .color-label { text-decoration: underline; @@ -280,7 +280,7 @@ width: 150px; flex-shrink: 0; - .label { + .badge { overflow: hidden; text-overflow: ellipsis; max-width: 100%; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 30428fd198d..52332ac97dd 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -36,6 +36,7 @@ } .table-holder { + overflow: unset; width: 100%; } @@ -1000,7 +1001,7 @@ button.mini-pipeline-graph-dropdown-toggle { /** * Center dropdown menu in mini graph */ - &.dropdown-menu { + .dropdown &.dropdown-menu { transform: translate(-80%, 0); @media (min-width: map-get($grid-breakpoints, md)) { diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index babe81cb0f7..a353f301d07 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -16,7 +16,7 @@ .application-theme { label { - margin: 0 $gl-padding $gl-padding 0; + margin: 0 $gl-padding-32 $gl-padding 0; text-align: center; } @@ -24,7 +24,7 @@ font-size: 0; height: 48px; border-radius: 4px; - min-width: 135px; + min-width: 112px; margin-bottom: $gl-padding-8; &.ui-indigo { @@ -75,7 +75,8 @@ .syntax-theme { label { - margin-right: 20px; + margin-right: $gl-padding-32; + margin-bottom: $gl-padding; text-align: center; .preview { @@ -84,7 +85,6 @@ img { border-radius: 4px; - max-width: 100%; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index caafda5fb05..aa83e5bdebc 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -497,6 +497,12 @@ &:not(:first-child) { border-top: 1px solid $border-color; } + + .btn-template-icon { + position: absolute; + left: $gl-padding; + top: $gl-padding; + } } .template-title { @@ -514,12 +520,6 @@ } } - svg { - position: absolute; - left: $gl-padding; - top: $gl-padding; - } - .project-fields-form { display: none; @@ -530,34 +530,23 @@ } .template-input-group { - position: relative; - - @include media-breakpoint-up(sm) { - display: flex; - } - - .input-group-prepend, - .input-group-append { + .input-group-prepend { flex: 1; - text-align: left; - padding-left: ($gl-padding * 3); - background-color: $white-light; } - .selected-template { - line-height: 20px; + .input-group-text { + width: 100%; + background-color: $white-light; } .selected-icon { + padding-right: $gl-padding; + svg { display: none; top: 7px; height: 20px; width: 20px; - - &.active { - display: block; - } } } } @@ -869,7 +858,6 @@ pre.light-well { .git-clone-holder { width: 380px; - height: 28px; .btn-clipboard { border: 1px solid $border-color; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 4b8a3db1d06..0a56153203c 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -540,36 +540,12 @@ margin-right: -$grid-size; min-height: 60px; - .multi-file-commit-list-item { - margin-left: 0; - margin-right: 0; - } - &.form-text.text-muted { margin-left: 0; right: 0; } } -.multi-file-commit-list-item { - &.is-active { - background-color: $white-normal; - } - - .multi-file-discard-btn { - display: none; - margin-top: -2px; - margin-left: auto; - color: $gl-link-color; - } - - &:hover { - .multi-file-discard-btn { - display: flex; - } - } -} - .multi-file-addition, .multi-file-addition-solid { color: $green-500; @@ -599,7 +575,7 @@ } } -.multi-file-commit-list-item, +.multi-file-commit-list-path, .ide-file-list .file { display: flex; align-items: center; @@ -616,11 +592,9 @@ } .multi-file-commit-list-path { - padding: 0; - background: none; - border: 0; - text-align: left; - width: 100%; + &.is-active { + background-color: $white-normal; + } &:hover, &:focus { @@ -635,7 +609,7 @@ } .multi-file-commit-list-file-path { - @include str-truncated(100%); + @include str-truncated(calc(100% - 30px)); &:hover { text-decoration: underline; @@ -646,6 +620,16 @@ } } +.multi-file-discard-btn { + top: 4px; + right: 8px; + bottom: 4px; + + svg { + top: 0; + } +} + .multi-file-commit-form { position: relative; background-color: $white-light; @@ -840,18 +824,20 @@ } .ide-staged-action-btn { - margin-left: auto; - line-height: 22px; + width: 22px; + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + > svg { + top: 0; + } } .ide-commit-file-count { min-width: 22px; - margin-left: auto; background-color: $gray-light; - border-radius: $border-radius-default; border: 1px solid $white-dark; - line-height: 20px; - text-align: center; } .ide-commit-radios { diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 1f8e61257a9..2f28031b9c8 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -52,7 +52,7 @@ .settings-content { max-height: 1px; - overflow-y: scroll; + overflow-y: hidden; padding-right: 110px; animation: collapseMaxHeight 300ms ease-out; // Keep the section from expanding when we scroll over it @@ -127,13 +127,6 @@ color: $gl-danger; } -.service-settings { - input[type="radio"], - input[type="checkbox"] { - margin-top: 10px; - } -} - .integration-settings-form { .card.card-body, .info-well { @@ -296,7 +289,8 @@ } .btn-clipboard { - margin-left: 5px; + background-color: $white-light; + border: 1px solid $theme-gray-200; } .deploy-token-help-block { diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 8cdf2275551..5127ddfde6e 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -107,12 +107,12 @@ } .performance-bar-modal { - .modal-footer { - display: none; + .modal-body { + padding: 0; } - .modal-dialog { - width: 860px; + .modal-footer { + display: none; } } } diff --git a/app/controllers/concerns/internal_redirect.rb b/app/controllers/concerns/internal_redirect.rb index 7409b2e89a5..10b9852e329 100644 --- a/app/controllers/concerns/internal_redirect.rb +++ b/app/controllers/concerns/internal_redirect.rb @@ -23,6 +23,10 @@ module InternalRedirect nil end + def sanitize_redirect(url_or_path) + safe_redirect_path(url_or_path) || safe_redirect_path_for_url(url_or_path) + end + def host_allowed?(uri) uri.host == request.host && uri.port == request.port diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index b6eb7d292fc..9d58656773d 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -1,6 +1,7 @@ module IssuesAction extend ActiveSupport::Concern include IssuableCollections + include IssuesCalendar # rubocop:disable Gitlab/ModuleWithInstanceVariables def issues @@ -17,18 +18,9 @@ module IssuesAction end # rubocop:enable Gitlab/ModuleWithInstanceVariables - # rubocop:disable Gitlab/ModuleWithInstanceVariables def issues_calendar - @issues = issuables_collection - .non_archived - .with_due_date - .limit(100) - - respond_to do |format| - format.ics { response.headers['Content-Disposition'] = 'inline' } - end + render_issues_calendar(issuables_collection) end - # rubocop:enable Gitlab/ModuleWithInstanceVariables private diff --git a/app/controllers/concerns/issues_calendar.rb b/app/controllers/concerns/issues_calendar.rb new file mode 100644 index 00000000000..671a204621d --- /dev/null +++ b/app/controllers/concerns/issues_calendar.rb @@ -0,0 +1,24 @@ +module IssuesCalendar + extend ActiveSupport::Concern + + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def render_issues_calendar(issuables) + @issues = issuables + .non_archived + .with_due_date + .limit(100) + + respond_to do |format| + format.ics do + # NOTE: with text/calendar as Content-Type, the browser always downloads + # the content as a file (even ignoring the Content-Disposition + # header). We want to display the content inline when accessed + # from GitLab, similarly to the RSS feed. + if request.referer&.start_with?(::Settings.gitlab.base_url) + response.headers['Content-Type'] = 'text/plain' + end + end + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables +end diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index 170bca8b56f..16374146ae4 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -1,9 +1,15 @@ module UploadsActions + extend ActiveSupport::Concern + include Gitlab::Utils::StrongMemoize include SendFileUpload UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze + included do + prepend_before_action :set_html_format, only: :show + end + def create link_to_file = UploadService.new(model, params[:file], uploader_class).execute @@ -41,6 +47,13 @@ module UploadsActions private + # Explicitly set the format. + # Otherwise rails 5 will set it from a file extension. + # See https://github.com/rails/rails/commit/84e8accd6fb83031e4c27e44925d7596655285f7#diff-2b8f2fbb113b55ca8e16001c393da8f1 + def set_html_format + request.format = :html + end + def uploader_class raise NotImplementedError end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index abc283d7aa9..6484a713f8e 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -7,6 +7,7 @@ class Projects::ArtifactsController < Projects::ApplicationController before_action :authorize_read_build! before_action :authorize_update_build!, only: [:keep] before_action :extract_ref_name_and_path + before_action :set_request_format, only: [:file] before_action :validate_artifacts! before_action :entry, only: [:file] @@ -101,4 +102,12 @@ class Projects::ArtifactsController < Projects::ApplicationController render_404 unless @entry.exists? end + + def set_request_format + request.format = :html if set_request_format? + end + + def set_request_format? + request.format != :json + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 0c1c286a0a4..a8c0a68fc17 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -7,6 +7,7 @@ class Projects::BlobController < Projects::ApplicationController prepend_before_action :authenticate_user!, only: [:edit] + before_action :set_request_format, only: [:edit, :show, :update] before_action :require_non_empty_project, except: [:new, :create] before_action :authorize_download_code! @@ -188,6 +189,18 @@ class Projects::BlobController < Projects::ApplicationController .last_for_path(@repository, @ref, @path).sha end + # In Rails 4.2 if params[:format] is empty, Rails set it to :html + # But since Rails 5.0 the framework now looks for an extension. + # E.g. for `blob/master/CHANGELOG.md` in Rails 4 the format would be `:html`, but in Rails 5 on it'd be `:md` + # This before_action explicitly sets the `:html` format for all requests unless `:format` is set by a client e.g. by JS for XHR requests. + def set_request_format + request.format = :html if set_request_format? + end + + def set_request_format? + params[:id].present? && params[:format].blank? && request.format != "json" + end + def show_html environment_params = @repository.branch_exists?(@ref) ? { ref: @ref } : { commit: @commit } @environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last @@ -197,15 +210,14 @@ class Projects::BlobController < Projects::ApplicationController end def show_json - json = blob_json(@blob) - return render_404 unless json - + set_last_commit_sha path_segments = @path.split('/') path_segments.pop tree_path = path_segments.join('/') - render json: json.merge( + json = { id: @blob.id, + last_commit_sha: @last_commit_sha, path: blob.path, name: blob.name, extension: blob.extension, @@ -221,6 +233,10 @@ class Projects::BlobController < Projects::ApplicationController commits_path: project_commits_path(project, @id), tree_path: project_tree_path(project, File.join(@ref, tree_path)), permalink: project_blob_path(project, File.join(@commit.id, @path)) - ) + } + + json.merge!(blob_json(@blob) || {}) unless params[:viewer] == 'none' + + render json: json end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index b7b36f770f5..cd7250b10fc 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -31,7 +31,10 @@ class Projects::BranchesController < Projects::ApplicationController end end - render + # https://gitlab.com/gitlab-org/gitlab-ce/issues/48097 + Gitlab::GitalyClient.allow_n_plus_1_calls do + render + end end format.json do branches = BranchesFinder.new(@repository, params).execute diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 35c36c725e2..7c897b2d86c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -4,6 +4,7 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableActions include ToggleAwardEmoji include IssuableCollections + include IssuesCalendar include SpammableActions prepend_before_action :authenticate_user!, only: [:new] @@ -40,14 +41,7 @@ class Projects::IssuesController < Projects::ApplicationController end def calendar - @issues = @issuables - .non_archived - .with_due_date - .limit(100) - - respond_to do |format| - format.ics { response.headers['Content-Disposition'] = 'inline' } - end + render_issues_calendar(@issuables) end def new diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index b452bfd7e6f..38918b3cd52 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -115,7 +115,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end format.json do - render json: @merge_request.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) + render json: serializer.represent(@merge_request, serializer: 'basic') end end rescue ActiveRecord::StaleObjectError diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 2494b56981d..f85dcfe6bfc 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -123,9 +123,9 @@ class Projects::MilestonesController < Projects::ApplicationController def search_params if request.format.json? && @project.group && can?(current_user, :read_group, @project.group) - groups = @project.group.self_and_ancestors + groups = @project.group.self_and_ancestors_ids end - params.permit(:state).merge(project_ids: @project.id, group_ids: groups&.select(:id)) + params.permit(:state).merge(project_ids: @project.id, group_ids: groups) end end diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index d498a2d6d11..9d3772d7541 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -54,7 +54,10 @@ class SnippetsFinder < UnionFinder end def authorized_snippets - Snippet.where(feature_available_projects.or(not_project_related)) + # This query was intentionally converted to a raw one to get it work in Rails 5.0. + # In Rails 5.0 and 5.1 there's a bug: https://github.com/rails/arel/issues/531 + # Please convert it back when on rails 5.2 as it works again as expected since 5.2. + Snippet.where("#{feature_available_projects} OR #{not_project_related}") .public_or_visible_to_user(current_user) end @@ -86,18 +89,20 @@ class SnippetsFinder < UnionFinder def feature_available_projects # Don't return any project related snippets if the user cannot read cross project - return table[:id].eq(nil) unless Ability.allowed?(current_user, :read_cross_project) + return table[:id].eq(nil).to_sql unless Ability.allowed?(current_user, :read_cross_project) projects = projects_for_user do |part| part.with_feature_available_for_user(:snippets, current_user) end.select(:id) - arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql) - table[:project_id].in(arel_query) + # This query was intentionally converted to a raw one to get it work in Rails 5.0. + # In Rails 5.0 and 5.1 there's a bug: https://github.com/rails/arel/issues/531 + # Please convert it back when on rails 5.2 as it works again as expected since 5.2. + "snippets.project_id IN (#{projects.to_sql})" end def not_project_related - table[:project_id].eq(nil) + table[:project_id].eq(nil).to_sql end def table diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb index b1857ab09f7..9f2d348e95f 100644 --- a/app/graphql/resolvers/merge_request_resolver.rb +++ b/app/graphql/resolvers/merge_request_resolver.rb @@ -1,15 +1,14 @@ module Resolvers class MergeRequestResolver < BaseResolver - prepend FullPathResolver - - type Types::ProjectType, null: true - argument :iid, GraphQL::ID_TYPE, required: true, description: 'The IID of the merge request, e.g., "1"' - def resolve(full_path:, iid:) - project = model_by_full_path(Project, full_path) + type Types::MergeRequestType, null: true + + alias_method :project, :object + + def resolve(iid:) return unless project.present? BatchLoader.for(iid.to_s).batch(key: project.id) do |iids, loader| diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 9e885d5845a..d9058ae7431 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -61,5 +61,12 @@ module Types field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true + + field :merge_request, + Types::MergeRequestType, + null: true, + resolver: Resolvers::MergeRequestResolver do + authorize :read_merge_request + end end end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index be79c78bf67..010ec2d7942 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -9,13 +9,6 @@ module Types authorize :read_project end - field :merge_request, Types::MergeRequestType, - null: true, - resolver: Resolvers::MergeRequestResolver, - description: "Find a merge request" do - authorize :read_merge_request - end - field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new end end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index d42284868c7..9f501ea55fb 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -157,7 +157,7 @@ module IssuablesHelper output = "" output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block", tooltip: true) + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline", tooltip: true) author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none") end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index cdbb572f80a..daad829faa2 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -171,11 +171,12 @@ module ProjectsHelper key = [ project.route.cache_key, project.cache_key, + project.last_activity_date, controller.controller_name, controller.action_name, Gitlab::CurrentSettings.cache_key, "cross-project:#{can?(current_user, :read_cross_project)}", - 'v2.5' + 'v2.6' ] key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status? diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index db8cf322ef7..9f6358cecbe 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -114,7 +114,7 @@ module CacheMarkdownField end def latest_cached_markdown_version - return CacheMarkdownField::CACHE_REDCARPET_VERSION unless cached_markdown_version + return CacheMarkdownField::CACHE_COMMONMARK_VERSION unless cached_markdown_version if cached_markdown_version < CacheMarkdownField::CACHE_COMMONMARK_VERSION_START CacheMarkdownField::CACHE_REDCARPET_VERSION diff --git a/app/models/project.rb b/app/models/project.rb index f0d8c40bfea..0d777515536 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -25,6 +25,7 @@ class Project < ActiveRecord::Base include FastDestroyAll::Helpers include WithUploads include BatchDestroyDependentAssociations + extend Gitlab::Cache::RequestCache extend Gitlab::ConfigHelper @@ -68,7 +69,7 @@ class Project < ActiveRecord::Base add_authentication_token_field :runners_token - before_validation :mark_remote_mirrors_for_removal, if: -> { ActiveRecord::Base.connection.table_exists?(:remote_mirrors) } + before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } before_save :ensure_runners_token @@ -2013,6 +2014,11 @@ class Project < ActiveRecord::Base @gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token end + def any_lfs_file_locks? + lfs_file_locks.any? + end + request_cache(:any_lfs_file_locks?) { self.id } + private def storage diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index ae0debbd3ac..a60b4c7fd0d 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -155,6 +155,7 @@ class ChatNotificationService < Service end def notify_for_ref?(data) + return true if data[:object_kind] == 'tag_push' return true if data.dig(:object_attributes, :tag) return true unless notify_only_default_branch? diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 84248f9590b..8a6b0ed1a5f 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -43,13 +43,18 @@ class GemnasiumService < Service def execute(data) return unless supported_events.include?(data[:object_kind]) + # Gitaly: this class will be removed https://gitlab.com/gitlab-org/gitlab-ee/issues/6010 + repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.repository.path_to_repo + end + Gemnasium::GitlabService.execute( ref: data[:ref], before: data[:before], after: data[:after], token: token, api_key: api_key, - repo: project.repository.path_to_repo # Gitaly: fixed by https://gitlab.com/gitlab-org/security-products/gemnasium-migration/issues/9 + repo: repo_path ) end end diff --git a/app/serializers/merge_request_basic_entity.rb b/app/serializers/merge_request_basic_entity.rb index e4aec977f01..1c06691026d 100644 --- a/app/serializers/merge_request_basic_entity.rb +++ b/app/serializers/merge_request_basic_entity.rb @@ -5,4 +5,8 @@ class MergeRequestBasicEntity < IssuableSidebarEntity expose :state expose :source_branch_exists?, as: :source_branch_exists expose :rebase_in_progress?, as: :rebase_in_progress + expose :milestone, using: API::Entities::Milestone + expose :labels, using: LabelEntity + expose :assignee, using: API::Entities::UserBasic + expose :task_status, :task_status_short end diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 925775aea0b..9bdbb2c0d99 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -25,14 +25,12 @@ module Ci valid = true - if Feature.enabled?('ci_job_request_with_tags_matcher') - # pick builds that does not have other tags than runner's one - builds = builds.matches_tag_ids(runner.tags.ids) + # pick builds that does not have other tags than runner's one + builds = builds.matches_tag_ids(runner.tags.ids) - # pick builds that have at least one tag - unless runner.run_untagged? - builds = builds.with_any_tags - end + # pick builds that have at least one tag + unless runner.run_untagged? + builds = builds.with_any_tags end builds.find do |build| diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 3e38a8a12d4..aa60661f7f2 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -11,7 +11,7 @@ module Projects order: { due_date: :asc, title: :asc } } - finder_params[:group_ids] = @project.group.self_and_ancestors.select(:id) if @project.group + finder_params[:group_ids] = @project.group.self_and_ancestors_ids if @project.group MilestonesFinder.new(finder_params).execute.select([:iid, :title]) end diff --git a/app/uploaders/favicon_uploader.rb b/app/uploaders/favicon_uploader.rb index 09afc63a5aa..3639375d474 100644 --- a/app/uploaders/favicon_uploader.rb +++ b/app/uploaders/favicon_uploader.rb @@ -1,17 +1,6 @@ class FaviconUploader < AttachmentUploader EXTENSION_WHITELIST = %w[png ico].freeze - include CarrierWave::MiniMagick - - version :favicon_main do - process resize_to_fill: [32, 32] - process convert: 'png' - - def full_filename(filename) - filename_for_different_format(super(filename), 'png') - end - end - def extension_whitelist EXTENSION_WHITELIST end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 94db374040c..a0861870ba4 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -25,7 +25,7 @@ = f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label' .col-sm-10 - if @appearance.favicon? - = image_tag @appearance.favicon.favicon_main.url, class: 'appearance-light-logo-preview' + = image_tag @appearance.favicon_url, class: 'appearance-light-logo-preview' - if @appearance.persisted? %br = link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo" @@ -33,9 +33,9 @@ = f.hidden_field :favicon_cache = f.file_field :favicon, class: '' .hint - Maximum file size is 1MB. Allowed image formats are #{favicon_extension_whitelist}. + Maximum file size is 1MB. Image size must be 32x32px. Allowed image formats are #{favicon_extension_whitelist}. %br - The resulting favicons will be cropped to be square and scaled down to a size of 32x32 px. + Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior. %fieldset.sign-in %legend diff --git a/app/views/email_rejection_mailer/rejection.html.haml b/app/views/email_rejection_mailer/rejection.html.haml index 7f7d841fe21..c4ae7befe4e 100644 --- a/app/views/email_rejection_mailer/rejection.html.haml +++ b/app/views/email_rejection_mailer/rejection.html.haml @@ -2,3 +2,4 @@ Unfortunately, your email message to GitLab could not be processed. = markdown @reason += render_if_exists 'shared/additional_email_text' diff --git a/app/views/email_rejection_mailer/rejection.text.haml b/app/views/email_rejection_mailer/rejection.text.haml index af518b5b583..0e13b2a6473 100644 --- a/app/views/email_rejection_mailer/rejection.text.haml +++ b/app/views/email_rejection_mailer/rejection.text.haml @@ -1,3 +1,4 @@ Unfortunately, your email message to GitLab could not be processed. \ = @reason += render_if_exists 'shared/additional_email_text' diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 845f4046d0d..6abb56ba6d2 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,6 +1,6 @@ - if current_user .dropdown - %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown" } + %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' } = icon('globe') %span.light Visibility: - if params[:visibility_level].present? diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 8b0ef3cd87a..5a88619f769 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -18,7 +18,7 @@ - if can_create_subgroups .btn-group.new-project-subgroup.droplab-dropdown.js-new-project-subgroup{ data: { project_path: new_project_path(namespace_id: @group.id), subgroup_path: new_group_path(parent_id: @group.id) } } %input.btn.btn-success.dropdown-primary.js-new-group-child{ type: "button", value: new_project_label, data: { action: "new-project" } } - %button.btn.btn-success.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { "dropdown-trigger" => "#new-project-or-subgroup-dropdown" } } + %button.btn.btn-success.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { "dropdown-trigger" => "#new-project-or-subgroup-dropdown", 'display' => 'static' } } = icon("caret-down", class: "dropdown-btn-icon") %ul#new-project-or-subgroup-dropdown.dropdown-menu.dropdown-menu-right{ data: { dropdown: true } } %li.droplab-item-selected{ role: "button", data: { value: "new-project", text: new_project_label } } diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index de8369ed7b9..b32b602ceb3 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -443,8 +443,6 @@ .col-md-6 .alert.alert-success = lorem - .alert.alert-primary - = lorem .alert.alert-info = lorem .col-md-6 diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index f311ac98ac6..cc672a5ea7c 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -20,7 +20,7 @@ - else .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } - .input-group-text + .input-group-text.border-0 #{user_url(current_user.username)}/ = hidden_field_tag :namespace_id, value: current_user.namespace_id .form-group.col-12.col-sm-6.project-path diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index db8137cc248..d35df706036 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -1,5 +1,5 @@ %li.header-new.dropdown - = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do + = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do = sprite_icon('plus-square', size: 16) = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu.dropdown-menu-right diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 0a9adc6f243..dd6a84e503d 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -9,6 +9,8 @@ %p Assignee: #{@merge_request.assignee_name} += render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request + - if @merge_request.description %div = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author) diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index 7d98400e6fe..d5b8f8d764f 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -5,6 +5,6 @@ New Merge Request <%= @merge_request.to_reference %> <%= merge_path_description(@merge_request, 'to') %> Author: <%= @merge_request.author_name %> Assignee: <%= @merge_request.assignee_name %> +<%= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request %> <%= @merge_request.description %> - diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index cfbd0459e3e..6f957533287 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -16,7 +16,7 @@ - else .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } - .input-group-text + .input-group-text.border-0 #{user_url(current_user.username)}/ = f.hidden_field :namespace_id, value: current_user.namespace_id .form-group.project-path.col-sm-6 diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index 9d27f51926e..d08807b5135 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -10,16 +10,18 @@ %a.btn.btn-default{ href: template.preview, rel: 'noopener noreferrer', target: '_blank' } Preview .project-fields-form - .form-group - %label.label-light - Template - .input-group.template-input-group - .input-group-prepend - .input-group-text - .selected-icon - - Gitlab::ProjectTemplate.all.each do |template| - = custom_icon(template.logo) - .selected-template - %button.btn.btn-default.change-template{ type: "button" } Change template + .row + .form-group.col-sm-12 + %label.label-light + Template + .input-group.template-input-group + .input-group-prepend + .input-group-text + .selected-icon + - Gitlab::ProjectTemplate.all.each do |template| + = custom_icon(template.logo) + .selected-template + .input-group-append + %button.btn.btn-default.change-template{ type: "button" } Change template = render 'new_project_fields', f: f, project_name_id: "template-project-name" diff --git a/app/views/projects/_stat_anchor_list.html.haml b/app/views/projects/_stat_anchor_list.html.haml index 8bffd1396ae..15ec58289e3 100644 --- a/app/views/projects/_stat_anchor_list.html.haml +++ b/app/views/projects/_stat_anchor_list.html.haml @@ -5,4 +5,4 @@ - anchors.each do |anchor| %li.nav-item = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'nav-link stat-link' : "nav-link btn btn-#{anchor.class_modifier || 'missing'}" do - %span.stat-text= anchor.label + .stat-text= anchor.label diff --git a/app/views/projects/blob/_header.html.haml b/app/views/projects/blob/_header.html.haml index 1b150ec3e5c..0a0b3ce1d6f 100644 --- a/app/views/projects/blob/_header.html.haml +++ b/app/views/projects/blob/_header.html.haml @@ -11,6 +11,7 @@ = view_on_environment_button(@commit.sha, @path, @environment) if @environment .btn-group{ role: "group" }< + = render_if_exists 'projects/blob/header_file_locks_link' = edit_blob_button = ide_edit_button - if current_user @@ -18,3 +19,4 @@ = delete_blob_link = render 'projects/fork_suggestion' += render_if_exists 'projects/blob/header_file_locks', project: @project, path: @path diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index c75093c4c24..f7551434d47 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -3,7 +3,7 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - archive_prefix = "#{project.path}-#{ref.tr('/', '-')}" .project-action-button.dropdown.inline> - %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') } + %button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static' } = sprite_icon('download') = icon("caret-down") %span.sr-only= _('Select Archive Format') diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 84245d72f4a..8b9c52f0802 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -8,7 +8,7 @@ - if show_menu .project-action-button.dropdown.inline - %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') } + %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...'), 'data-display' => 'static' } = icon('plus') = icon("caret-down") %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown diff --git a/app/views/projects/clusters/_sidebar.html.haml b/app/views/projects/clusters/_sidebar.html.haml index 73cd7c50922..3d10348212f 100644 --- a/app/views/projects/clusters/_sidebar.html.haml +++ b/app/views/projects/clusters/_sidebar.html.haml @@ -1,7 +1,9 @@ +- clusters_help_url = help_page_path('user/project/clusters/index.md') +- help_link_start = "<a href=\"%{url}\" target=\"_blank\" rel=\"noopener noreferrer\">".html_safe +- help_link_end = '</a>'.html_safe %h4.prepend-top-0 = s_('ClusterIntegration|Kubernetes cluster integration') %p = s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') %p - - link = link_to(_('Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Learn more about %{link_to_documentation}').html_safe % { link_to_documentation: link } + = s_('ClusterIntegration|Learn more about %{help_link_start}Kubernetes%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: clusters_help_url }, help_link_end: help_link_end } diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml index 59c4eeec17a..b8e40b0a38b 100644 --- a/app/views/projects/clusters/gcp/_form.html.haml +++ b/app/views/projects/clusters/gcp/_form.html.haml @@ -1,4 +1,10 @@ = javascript_include_tag 'https://apis.google.com/js/api.js' +- external_link_icon = icon('external-link') +- zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones' +- machine_type_link_url = 'https://cloud.google.com/compute/docs/machine-types' +- pricing_link_url = 'https://cloud.google.com/compute/pricing#machinetype' +- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe +- help_link_end = ' %{external_link_icon}</a>'.html_safe % { external_link_icon: external_link_icon } %p - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') @@ -7,15 +13,15 @@ = form_for @cluster, html: { class: 'js-gke-cluster-creation prepend-top-20', data: { token: token_in_session } }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field| = form_errors(@cluster) .form-group - = field.label :name, s_('ClusterIntegration|Kubernetes cluster name') + = field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-light' = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name') .form-group - = field.label :environment_scope, s_('ClusterIntegration|Environment scope') + = field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-light' = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') = field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field| .form-group - = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project') + = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project'), class: 'label-light' .js-gcp-project-id-dropdown-entry-point{ data: { docsUrl: 'https://console.cloud.google.com/home/dashboard' } } = provider_gcp_field.hidden_field :gcp_project_id .dropdown @@ -26,8 +32,7 @@ %span.form-text.text-muted .form-group - = provider_gcp_field.label :zone, s_('ClusterIntegration|Zone') - = link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.label :zone, s_('ClusterIntegration|Zone'), class: 'label-light' .js-gcp-zone-dropdown-entry-point = provider_gcp_field.hidden_field :zone .dropdown @@ -35,13 +40,15 @@ %span.dropdown-toggle-text = _('Select project to choose zone') = icon('chevron-down') + %p.form-text.text-muted + = s_('ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: zones_link_url }, help_link_end: help_link_end } .form-group - = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes') + = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes'), class: 'label-light' = provider_gcp_field.text_field :num_nodes, class: 'form-control', placeholder: '3' .form-group - = provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type') + = provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type'), class: 'label-light' .js-gcp-machine-type-dropdown-entry-point = provider_gcp_field.hidden_field :machine_type .dropdown @@ -49,6 +56,8 @@ %span.dropdown-toggle-text = _('Select project and zone to choose machine type') = icon('chevron-down') + %p.form-text.text-muted + = s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end } .form-group = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml index 50e5950ced4..725720d2222 100644 --- a/app/views/projects/deploy_tokens/_index.html.haml +++ b/app/views/projects/deploy_tokens/_index.html.haml @@ -10,9 +10,8 @@ .settings-content - if @new_deploy_token.persisted? = render 'projects/deploy_tokens/new_deploy_token', deploy_token: @new_deploy_token - - else - %h5.prepend-top-0 - = s_('DeployTokens|Add a deploy token') - = render 'projects/deploy_tokens/form', project: @project, token: @new_deploy_token, presenter: @deploy_tokens - %hr + %h5.prepend-top-0 + = s_('DeployTokens|Add a deploy token') + = render 'projects/deploy_tokens/form', project: @project, token: @new_deploy_token, presenter: @deploy_tokens + %hr = render 'projects/deploy_tokens/table', project: @project, active_tokens: @deploy_tokens diff --git a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml index 1e715681e59..5dd9ffba074 100644 --- a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml +++ b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml @@ -1,14 +1,18 @@ -.created-deploy-token-container - %h5.prepend-top-0 - = s_('DeployTokens|Your New Deploy Token') +.created-deploy-token-container.info-well + .well-segment + %h5.prepend-top-0 + = s_('DeployTokens|Your New Deploy Token') - .form-group - = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus' - = clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username to clipboard'), placement: 'left') - %span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.") + .form-group + .input-group + = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus' + .input-group-append + = clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username to clipboard'), placement: 'left') + %span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.") - .form-group - = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus' - = clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token to clipboard'), placement: 'left') - %span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.") -%hr + .form-group + .input-group + = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus' + .input-group-append + = clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token to clipboard'), placement: 'left') + %span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.") diff --git a/app/views/projects/diffs/_collapsed.html.haml b/app/views/projects/diffs/_collapsed.html.haml index 5762f4d86d7..9bd1255fe00 100644 --- a/app/views/projects/diffs/_collapsed.html.haml +++ b/app/views/projects/diffs/_collapsed.html.haml @@ -2,4 +2,4 @@ - url = url_for(safe_params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier)) .nothing-here-block.diff-collapsed{ data: { diff_for_path: url } } This diff is collapsed. - %a.click-to-expand Click to expand it. + %button.click-to-expand.btn.btn-link Click to expand it. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 69753427d17..d47dc3d8143 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,3 +1,4 @@ +- @content_class = "limit-container-width" unless fluid_layout - @no_container = true - breadcrumb_title _("Details") @@ -6,7 +7,7 @@ = render "home_panel" .project-empty-note-panel - %div{ class: [container_class, ("limit-container-width-sm" unless fluid_layout)] } + %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } .prepend-top-20 %h4 = _('The repository for this project is empty') @@ -36,7 +37,7 @@ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons - if can?(current_user, :push_code, @project) - %div{ class: [container_class, ("limit-container-width-sm" unless fluid_layout)] } + %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } .prepend-top-20 .empty_wrapper %h3#repo-command-line-instructions.page-title-empty diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 76438fae663..acb1a446ec4 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -18,7 +18,7 @@ %button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } } = value - %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' } } } + %button.btn.create-merge-request-dropdown-toggle.dropdown-toggle.btn-success.btn-inverted.js-dropdown-toggle{ type: 'button', data: { dropdown: { trigger: '#create-merge-request-dropdown' }, display: 'static' } } = icon('caret-down') .droplab-dropdown diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml index ebcd99f2a9b..b2eacabc21a 100644 --- a/app/views/projects/merge_requests/creations/_new_submit.html.haml +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -25,7 +25,7 @@ = custom_icon ('illustration_no_commits') - else %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom - %li.commits-tab.active + %li.commits-tab = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do Commits %span.badge.badge-pill= @commits.size diff --git a/app/views/projects/merge_requests/diffs/_version_controls.html.haml b/app/views/projects/merge_requests/diffs/_version_controls.html.haml index 1c26f0405d2..52bf584d550 100644 --- a/app/views/projects/merge_requests/diffs/_version_controls.html.haml +++ b/app/views/projects/merge_requests/diffs/_version_controls.html.haml @@ -3,7 +3,7 @@ .mr-version-menus-container.content-block Changes between %span.dropdown.inline.mr-version-dropdown - %a.dropdown-toggle.btn.btn-default{ data: {toggle: :dropdown} } + %a.dropdown-toggle.btn.btn-default{ data: { toggle: :dropdown, display: 'static' } } %span - if @merge_request_diff.latest? latest version @@ -36,7 +36,7 @@ - if @merge_request_diff.base_commit_sha and %span.dropdown.inline.mr-version-compare-dropdown - %a.btn.btn-default.dropdown-toggle{ data: {toggle: :dropdown} } + %a.btn.btn-default.dropdown-toggle{ data: { toggle: :dropdown, display: 'static' } } - if @start_version version #{version_index(@start_version)} - else diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index b478fbbb15e..f7b04c436a6 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -69,6 +69,8 @@ .wiki = markdown_field(@milestone, :description) + = render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project + - if can?(current_user, :read_issue, @project) && @milestone.total_items_count(current_user).zero? .alert.alert-success.prepend-top-default %span Assign some issues to this milestone. diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index d07bb661615..506bf54b3f8 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -8,6 +8,8 @@ row.find("td.tree-time-ago").html('#{escape_javascript time_ago_with_tooltip(commit.committed_date)}'); row.find("td.tree-commit").html('#{escape_javascript render("projects/tree/tree_commit_column", commit: commit)}'); + = render_if_exists 'projects/refs/logs_tree_lock_label', lock_label: content_data[:lock_label] + - if @more_log_url :plain if($('#tree-slider').length) { diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml index acbab8b85c9..16e48814578 100644 --- a/app/views/projects/services/_index.html.haml +++ b/app/views/projects/services/_index.html.haml @@ -8,7 +8,7 @@ %colgroup %col %col - %col.d-none.d-sm-block + %col %col{ width: "120" } %thead %tr diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 35c7dc2984a..d80d2957466 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,4 +1,4 @@ -- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout +- @content_class = "limit-container-width" unless fluid_layout - page_title _("Edit"), @page.title.capitalize, _("Wiki") = wiki_page_errors(@error) diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 909987d8090..8c2cbd495a0 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,4 +1,4 @@ -- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout +- @content_class = "limit-container-width" unless fluid_layout - page_title s_("WikiClone|Git Access"), _("Wiki") .wiki-page-header.has-sidebar-toggle diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index ff72c8bb75d..a08973c7f32 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,4 +1,4 @@ -- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout +- @content_class = "limit-container-width" unless fluid_layout - breadcrumb_title @page.title.capitalize - wiki_breadcrumb_dropdown_links(@page.slug) - page_title @page.title.capitalize, _("Wiki") diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 5fc02ba3160..3655c2a1d42 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -11,7 +11,7 @@ %span = default_clone_protocol.upcase = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown + %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown %li = ssh_clone_button(project) %li diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index a112a9f1f7e..daee691e358 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -9,7 +9,7 @@ None %a{ href: "#", "v-for" => "label in issue.labels" } - %span.badge.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } + .badge.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } {{ label.title }} - if can_admin_issue? .selectbox diff --git a/app/views/shared/builds/_build_output.html.haml b/app/views/shared/builds/_build_output.html.haml index 07f1501fadd..0e18128a8f1 100644 --- a/app/views/shared/builds/_build_output.html.haml +++ b/app/views/shared/builds/_build_output.html.haml @@ -1,3 +1,3 @@ %pre.build-trace#build-trace %code.bash.js-build-output - .build-loader-animation.js-build-refresh + .build-loader-animation.js-build-refresh diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index 955b8866c2c..37625a4a163 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -1,6 +1,8 @@ - project = @target_project || @project - extra_class = extra_class || '' - show_menu_above = show_menu_above || false +- selected = local_assigns.fetch(:selected, nil) + - selected_text = selected.try(:title) || params[:milestone_title] - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone") - if selected.present? || params[:milestone_title].present? diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index ed3ef6155db..8a13c7a3b83 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -50,7 +50,7 @@ - data[:multi_select] = true - data['dropdown-title'] = title - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header'] - - data['max-select'] = dropdown_options[:data][:'max-select'] + - data['max-select'] = dropdown_options[:data][:'max-select'] if dropdown_options[:data][:'max-select'] - options[:data].merge!(data) = dropdown_tag(title, options: options) diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 01fbc163a14..bd87bb38e77 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -25,7 +25,10 @@ = form.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" + + = render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form + - if has_due_date .col-lg-6 .form-group.row diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index e99d8d0973f..09ddf732ada 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -6,14 +6,14 @@ .js-notification-toggle-btns %div{ class: ("btn-group" if notification_setting.custom?) } - if notification_setting.custom? - %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting) } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } } = icon('caret-down') .sr-only Toggle dropdown - else - %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), display: 'static' } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) = icon("caret-down") diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index f3c9e2b1582..ae5c5fac834 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -6,12 +6,6 @@ class GitGarbageCollectWorker # Timeout set to 24h LEASE_TIMEOUT = 86400 - GITALY_MIGRATED_TASKS = { - gc: :garbage_collect, - full_repack: :repack_full, - incremental_repack: :repack_incremental - }.freeze - def perform(project_id, task = :gc, lease_key = nil, lease_uuid = nil) project = Project.find(project_id) active_uuid = get_lease_uuid(lease_key) @@ -27,21 +21,7 @@ class GitGarbageCollectWorker end task = task.to_sym - cmd = command(task) - - gitaly_migrate(GITALY_MIGRATED_TASKS[task], status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_call(task, project.repository.raw_repository) - else - repo_path = project.repository.path_to_repo - description = "'#{cmd.join(' ')}' in #{repo_path}" - Gitlab::GitLogger.info(description) - - output, status = Gitlab::Popen.popen(cmd, repo_path) - - Gitlab::GitLogger.error("#{description} failed:\n#{output}") unless status.zero? - end - end + gitaly_call(task, project.repository.raw_repository) # Refresh the branch cache in case garbage collection caused a ref lookup to fail flush_ref_caches(project) if task == :gc @@ -82,21 +62,12 @@ class GitGarbageCollectWorker when :incremental_repack client.repack_incremental end - end - - def command(task) - case task - when :gc - git(write_bitmaps: bitmaps_enabled?) + %w[gc] - when :full_repack - git(write_bitmaps: bitmaps_enabled?) + %w[repack -A -d --pack-kept-objects] - when :incremental_repack - # Normal git repack fails when bitmaps are enabled. It is impossible to - # create a bitmap here anyway. - git(write_bitmaps: false) + %w[repack -d] - else - raise "Invalid gc task: #{task.inspect}" - end + rescue GRPC::NotFound => e + Gitlab::GitLogger.error("#{method} failed:\nRepository not found") + raise Gitlab::Git::Repository::NoRepository.new(e) + rescue GRPC::BadStatus => e + Gitlab::GitLogger.error("#{method} failed:\n#{e}") + raise Gitlab::Git::CommandError.new(e) end def flush_ref_caches(project) @@ -108,19 +79,4 @@ class GitGarbageCollectWorker def bitmaps_enabled? Gitlab::CurrentSettings.housekeeping_bitmaps_enabled end - - def git(write_bitmaps:) - config_value = write_bitmaps ? 'true' : 'false' - %W[git -c repack.writeBitmaps=#{config_value}] - end - - def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block) - Gitlab::GitalyClient.migrate(method, status: status, &block) - rescue GRPC::NotFound => e - Gitlab::GitLogger.error("#{method} failed:\nRepository not found") - raise Gitlab::Git::Repository::NoRepository.new(e) - rescue GRPC::BadStatus => e - Gitlab::GitLogger.error("#{method} failed:\n#{e}") - raise Gitlab::Git::CommandError.new(e) - end end diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index db48bb7e8b8..dbb215f1964 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -8,28 +8,12 @@ class RepositoryForkWorker target_project_id = args.shift target_project = Project.find(target_project_id) - # By v10.8, we should've drained the queue of all jobs using the old arguments. - # We can remove the else clause if we're no longer logging the message in that clause. - # See https://gitlab.com/gitlab-org/gitaly/issues/1110 - if args.empty? - source_project = target_project.forked_from_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 - Rails.logger.info("Project #{target_project.id} is being forked using old-style arguments.") - - source_repository_storage_path, source_disk_path = *args - - source_repository_storage_name = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - Gitlab.config.repositories.storages.find do |_, info| - info.legacy_disk_path == source_repository_storage_path - end&.first || raise("no shard found for path '#{source_repository_storage_path}'") - end - fork_repository(target_project, source_repository_storage_name, source_disk_path) + source_project = target_project.forked_from_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) end private diff --git a/changelogs/unreleased/45487-slack-tag-push-notifs.yml b/changelogs/unreleased/45487-slack-tag-push-notifs.yml new file mode 100644 index 00000000000..647000bd97c --- /dev/null +++ b/changelogs/unreleased/45487-slack-tag-push-notifs.yml @@ -0,0 +1,5 @@ +--- +title: Fix chat service tag notifications not sending when only default branch enabled +merge_request: 19864 +author: +type: fixed diff --git a/changelogs/unreleased/45557-machine-type-help-links.yml b/changelogs/unreleased/45557-machine-type-help-links.yml new file mode 100644 index 00000000000..870a650e10b --- /dev/null +++ b/changelogs/unreleased/45557-machine-type-help-links.yml @@ -0,0 +1,6 @@ +--- +title: Add machine type and pricing documentation links, add class to labels to make + bold +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/46429-creating-a-deploy-token-doesn-t-bring-back-to-the-creation-page.yml b/changelogs/unreleased/46429-creating-a-deploy-token-doesn-t-bring-back-to-the-creation-page.yml new file mode 100644 index 00000000000..b564fb0174f --- /dev/null +++ b/changelogs/unreleased/46429-creating-a-deploy-token-doesn-t-bring-back-to-the-creation-page.yml @@ -0,0 +1,5 @@ +--- +title: Allows you to create another deploy token dimmediately after creating one +merge_request: 19639 +author: +type: changed diff --git a/changelogs/unreleased/46861-issuable-title-with-longer-username.yml b/changelogs/unreleased/46861-issuable-title-with-longer-username.yml new file mode 100644 index 00000000000..9df6879deb6 --- /dev/null +++ b/changelogs/unreleased/46861-issuable-title-with-longer-username.yml @@ -0,0 +1,5 @@ +--- +title: Fix CSS for buttons not to be hidden on issues/MR title +merge_request: 19176 +author: Takuya Noguchi +type: fixed diff --git a/changelogs/unreleased/47646-ui-glitch.yml b/changelogs/unreleased/47646-ui-glitch.yml new file mode 100644 index 00000000000..384df4e2cc9 --- /dev/null +++ b/changelogs/unreleased/47646-ui-glitch.yml @@ -0,0 +1,5 @@ +--- +title: Line height fixed +merge_request: +author: Murat Dogan +type: fixed diff --git a/changelogs/unreleased/47672-set_inline_content_type_for_ics.yml b/changelogs/unreleased/47672-set_inline_content_type_for_ics.yml new file mode 100644 index 00000000000..4bc883d41bd --- /dev/null +++ b/changelogs/unreleased/47672-set_inline_content_type_for_ics.yml @@ -0,0 +1,5 @@ +--- +title: Render calendar feed inline when accessed from GitLab +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/47871-new-mr-tab-active-state.yml b/changelogs/unreleased/47871-new-mr-tab-active-state.yml new file mode 100644 index 00000000000..c3fc8672d82 --- /dev/null +++ b/changelogs/unreleased/47871-new-mr-tab-active-state.yml @@ -0,0 +1,5 @@ +--- +title: Fix active tab highlight when creating new merge request +merge_request: 19781 +author: Jan Beckmann +type: fixed diff --git a/changelogs/unreleased/48050-add-full-commit-sha.yml b/changelogs/unreleased/48050-add-full-commit-sha.yml new file mode 100644 index 00000000000..30376fe35e0 --- /dev/null +++ b/changelogs/unreleased/48050-add-full-commit-sha.yml @@ -0,0 +1,5 @@ +--- +title: Uses long sha version of the merged commit in MR widget copy to clipboard button +merge_request: 19955 +author: +type: other diff --git a/changelogs/unreleased/author-doc-fix.yml b/changelogs/unreleased/author-doc-fix.yml new file mode 100644 index 00000000000..83521543239 --- /dev/null +++ b/changelogs/unreleased/author-doc-fix.yml @@ -0,0 +1,5 @@ +--- +title: Fix fields for author & assignee in MR API docs. +merge_request: 19798 +author: gfyoung +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-expected-search-search-seed_project-got-nil.yml b/changelogs/unreleased/blackst0ne-rails5-expected-search-search-seed_project-got-nil.yml new file mode 100644 index 00000000000..e7bb2703b03 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-expected-search-search-seed_project-got-nil.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5] Fix sessions_controller_spec" +merge_request: 19936 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-expected-the-response-to-have-status-code-ok-but-it-was-404.yml b/changelogs/unreleased/blackst0ne-rails5-expected-the-response-to-have-status-code-ok-but-it-was-404.yml new file mode 100644 index 00000000000..fad15de2dd5 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-expected-the-response-to-have-status-code-ok-but-it-was-404.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5] Set request.format for artifacts_controller" +merge_request: 19937 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-fix-blob-requests-format.yml b/changelogs/unreleased/blackst0ne-rails5-fix-blob-requests-format.yml new file mode 100644 index 00000000000..a83aa03606a --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-fix-blob-requests-format.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5] Explicitly set request.format for blob_controller" +merge_request: 19876 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-fix-optimistic-lock-values.yml b/changelogs/unreleased/blackst0ne-rails5-fix-optimistic-lock-values.yml new file mode 100644 index 00000000000..1915dff73ab --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-fix-optimistic-lock-values.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5] Fix optimistic lock value" +merge_request: 19878 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-fix-pipeline-schedules-controller-spec.yml b/changelogs/unreleased/blackst0ne-rails5-fix-pipeline-schedules-controller-spec.yml new file mode 100644 index 00000000000..7a2b19ad681 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-fix-pipeline-schedules-controller-spec.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5] Fix pipeline_schedules_controller_spec" +merge_request: 19919 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-fix-snippets-finder.yml b/changelogs/unreleased/blackst0ne-rails5-fix-snippets-finder.yml new file mode 100644 index 00000000000..597b85de26f --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-fix-snippets-finder.yml @@ -0,0 +1,5 @@ +--- +title: "[Rails5] Fix snippets_finder arel queries" +merge_request: 19796 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/blackst0ne-rails5-invalid-single-table-inheritance-type-group-is-not-a-subclass-of-namespace.yml b/changelogs/unreleased/blackst0ne-rails5-invalid-single-table-inheritance-type-group-is-not-a-subclass-of-namespace.yml new file mode 100644 index 00000000000..92e6ce35941 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-rails5-invalid-single-table-inheritance-type-group-is-not-a-subclass-of-namespace.yml @@ -0,0 +1,6 @@ +--- +title: "[Rails5] Invalid single-table inheritance type: Group is not a subclass of + Namespace" +merge_request: 19918 +author: "@blackst0ne" +type: fixed diff --git a/changelogs/unreleased/bvl-graphql-nested-merge-request.yml b/changelogs/unreleased/bvl-graphql-nested-merge-request.yml new file mode 100644 index 00000000000..f0f0488d31a --- /dev/null +++ b/changelogs/unreleased/bvl-graphql-nested-merge-request.yml @@ -0,0 +1,5 @@ +--- +title: Allow querying a single merge request within a project +merge_request: 19853 +author: +type: changed diff --git a/changelogs/unreleased/bw-enable-commonmark.yml b/changelogs/unreleased/bw-enable-commonmark.yml new file mode 100644 index 00000000000..89252e5063d --- /dev/null +++ b/changelogs/unreleased/bw-enable-commonmark.yml @@ -0,0 +1,5 @@ +--- +title: Use CommonMark syntax and rendering for new Markdown content +merge_request: 19331 +author: +type: added diff --git a/changelogs/unreleased/cache-doc-fix.yml b/changelogs/unreleased/cache-doc-fix.yml new file mode 100644 index 00000000000..db4726a92e9 --- /dev/null +++ b/changelogs/unreleased/cache-doc-fix.yml @@ -0,0 +1,5 @@ +--- +title: 'Remove incorrect CI doc re: PowerShell' +merge_request: 19622 +author: gfyoung +type: fixed diff --git a/changelogs/unreleased/dm-blockquote-trailing-whitespace.yml b/changelogs/unreleased/dm-blockquote-trailing-whitespace.yml new file mode 100644 index 00000000000..98ecdde4f4c --- /dev/null +++ b/changelogs/unreleased/dm-blockquote-trailing-whitespace.yml @@ -0,0 +1,5 @@ +--- +title: Allow trailing whitespace on blockquote fence lines +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fix-groups-api-ordering.yml b/changelogs/unreleased/fix-groups-api-ordering.yml new file mode 100644 index 00000000000..3a6a7f84356 --- /dev/null +++ b/changelogs/unreleased/fix-groups-api-ordering.yml @@ -0,0 +1,4 @@ +title: Fixed pagination of groups API +merge_request: 19665 +author: Marko, Peter +type: added diff --git a/changelogs/unreleased/fix-web-ide-disable-markdown-autocomplete.yml b/changelogs/unreleased/fix-web-ide-disable-markdown-autocomplete.yml new file mode 100644 index 00000000000..6a4d9b6c8c4 --- /dev/null +++ b/changelogs/unreleased/fix-web-ide-disable-markdown-autocomplete.yml @@ -0,0 +1,5 @@ +--- +title: Disabled Web IDE autocomplete suggestions for Markdown files. +merge_request: +author: Isaac Smith +type: fixed diff --git a/changelogs/unreleased/ide-commit-actions-update.yml b/changelogs/unreleased/ide-commit-actions-update.yml new file mode 100644 index 00000000000..35bee94e156 --- /dev/null +++ b/changelogs/unreleased/ide-commit-actions-update.yml @@ -0,0 +1,5 @@ +--- +title: Improve Web IDE commit flow +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/more-group-api-sorting-options.yml b/changelogs/unreleased/more-group-api-sorting-options.yml new file mode 100644 index 00000000000..b29f76a65a9 --- /dev/null +++ b/changelogs/unreleased/more-group-api-sorting-options.yml @@ -0,0 +1,5 @@ +--- +title: Added id sorting option to GET groups and subgroups API +merge_request: 19665 +author: Marko, Peter +type: added diff --git a/changelogs/unreleased/optimise-paused-runners.yml b/changelogs/unreleased/optimise-paused-runners.yml new file mode 100644 index 00000000000..13097e507d3 --- /dev/null +++ b/changelogs/unreleased/optimise-paused-runners.yml @@ -0,0 +1,5 @@ +--- +title: Optimise paused runners to reduce amount of used requests +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/rails5-fix-46276.yml b/changelogs/unreleased/rails5-fix-46276.yml new file mode 100644 index 00000000000..cdca91a755d --- /dev/null +++ b/changelogs/unreleased/rails5-fix-46276.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 fix format in uploads actions +merge_request: 19907 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-47804.yml b/changelogs/unreleased/rails5-fix-47804.yml new file mode 100644 index 00000000000..3332ed3bbaa --- /dev/null +++ b/changelogs/unreleased/rails5-fix-47804.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 fix stack level too deep +merge_request: 19762 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-47805.yml b/changelogs/unreleased/rails5-fix-47805.yml new file mode 100644 index 00000000000..8bd8ad5488c --- /dev/null +++ b/changelogs/unreleased/rails5-fix-47805.yml @@ -0,0 +1,6 @@ +--- +title: 'Rails5 ActionController::ParameterMissing: param is missing or the value is + empty: application_setting' +merge_request: 19763 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-47835.yml b/changelogs/unreleased/rails5-fix-47835.yml new file mode 100644 index 00000000000..fe9cbf1a03a --- /dev/null +++ b/changelogs/unreleased/rails5-fix-47835.yml @@ -0,0 +1,6 @@ +--- +title: Rails5 fix no implicit conversion of Hash into String. ActionController::Parameters + no longer returns an hash in Rails 5 +merge_request: 19792 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-47836.yml b/changelogs/unreleased/rails5-fix-47836.yml new file mode 100644 index 00000000000..2aef2db607a --- /dev/null +++ b/changelogs/unreleased/rails5-fix-47836.yml @@ -0,0 +1,6 @@ +--- +title: Rails5 fix passing Group objects array into for_projects_and_groups milestone + scope +merge_request: 19863 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-47960.yml b/changelogs/unreleased/rails5-fix-47960.yml new file mode 100644 index 00000000000..2229511ccd6 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-47960.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 fix update_attribute usage not causing a save +merge_request: 19881 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-48009.yml b/changelogs/unreleased/rails5-fix-48009.yml new file mode 100644 index 00000000000..7ade9ef2b7d --- /dev/null +++ b/changelogs/unreleased/rails5-fix-48009.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 update Gemfile.rails5.lock +merge_request: 19921 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-48012.yml b/changelogs/unreleased/rails5-fix-48012.yml new file mode 100644 index 00000000000..953ccbd8af7 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-48012.yml @@ -0,0 +1,6 @@ +--- +title: Rails5 fix passing Group objects array into for_projects_and_groups milestone + scope +merge_request: 19920 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rails5-fix-db-check.yml b/changelogs/unreleased/rails5-fix-db-check.yml new file mode 100644 index 00000000000..ccac4619ea7 --- /dev/null +++ b/changelogs/unreleased/rails5-fix-db-check.yml @@ -0,0 +1,5 @@ +--- +title: Rails5 fix connection execute return integer instead of string +merge_request: 19901 +author: Jasper Maes +type: fixed diff --git a/changelogs/unreleased/rd-33733-showing-created-date-instead-of-updated-date-in-project-lists.yml b/changelogs/unreleased/rd-33733-showing-created-date-instead-of-updated-date-in-project-lists.yml new file mode 100644 index 00000000000..3934381b0cf --- /dev/null +++ b/changelogs/unreleased/rd-33733-showing-created-date-instead-of-updated-date-in-project-lists.yml @@ -0,0 +1,5 @@ +--- +title: Invalidate cache with project details when repository is updated +merge_request: 19774 +author: +type: fixed diff --git a/changelogs/unreleased/remove-ci_job_request_with_tags_matcher.yml b/changelogs/unreleased/remove-ci_job_request_with_tags_matcher.yml new file mode 100644 index 00000000000..b86512445d5 --- /dev/null +++ b/changelogs/unreleased/remove-ci_job_request_with_tags_matcher.yml @@ -0,0 +1,5 @@ +--- +title: Remove the ci_job_request_with_tags_matcher +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/remove-link-label-vertical-alignment-property.yml b/changelogs/unreleased/remove-link-label-vertical-alignment-property.yml new file mode 100644 index 00000000000..40ec3998b05 --- /dev/null +++ b/changelogs/unreleased/remove-link-label-vertical-alignment-property.yml @@ -0,0 +1,5 @@ +--- +title: Change label link vertical alignment property +merge_request: 18777 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/remove-small-container-width.yml b/changelogs/unreleased/remove-small-container-width.yml new file mode 100644 index 00000000000..1af8aafa87e --- /dev/null +++ b/changelogs/unreleased/remove-small-container-width.yml @@ -0,0 +1,5 @@ +--- +title: Remove small container width +merge_request: 19893 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/safari-scrollbar-bug.yml b/changelogs/unreleased/safari-scrollbar-bug.yml new file mode 100644 index 00000000000..792a66d1ada --- /dev/null +++ b/changelogs/unreleased/safari-scrollbar-bug.yml @@ -0,0 +1,5 @@ +--- +title: Remove scrollbar in Safari in repo settings page +merge_request: 19809 +author: gfyoung +type: fixed diff --git a/changelogs/unreleased/sh-optimize-locks-check-ce.yml b/changelogs/unreleased/sh-optimize-locks-check-ce.yml new file mode 100644 index 00000000000..933ec9b79bf --- /dev/null +++ b/changelogs/unreleased/sh-optimize-locks-check-ce.yml @@ -0,0 +1,5 @@ +--- +title: Eliminate N+1 queries in LFS file locks checks during a push +merge_request: +author: +type: performance diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb index 3e7111fd063..0861544c5a4 100644 --- a/config/initializers/active_record_locking.rb +++ b/config/initializers/active_record_locking.rb @@ -1,73 +1,80 @@ # rubocop:disable Lint/RescueException -# Remove this entire initializer when we are at rails 5.0. -# This file fixes the bug (see below) which has been fixed in the upstream. -unless Gitlab.rails5? - # This patch fixes https://github.com/rails/rails/issues/26024 - # TODO: Remove it when it's no longer necessary - - module ActiveRecord - module Locking - module Optimistic - # We overwrite this method because we don't want to have default value - # for newly created records - def _create_record(attribute_names = self.attribute_names, *) # :nodoc: - super - end +# Remove this monkey-patch when all lock_version values are converted from NULLs to zeros. +# See https://gitlab.com/gitlab-org/gitlab-ce/issues/25228 +module ActiveRecord + module Locking + module Optimistic + # We overwrite this method because we don't want to have default value + # for newly created records + def _create_record(attribute_names = self.attribute_names, *) # :nodoc: + super + end - def _update_record(attribute_names = self.attribute_names) #:nodoc: - return super unless locking_enabled? - return 0 if attribute_names.empty? + def _update_record(attribute_names = self.attribute_names) #:nodoc: + return super unless locking_enabled? + return 0 if attribute_names.empty? - lock_col = self.class.locking_column + lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend + previous_lock_value = send(lock_col).to_i # rubocop:disable GitlabSecurity/PublicSend - # This line is added as a patch - previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0 + # This line is added as a patch + previous_lock_value = nil if previous_lock_value == '0' || previous_lock_value == 0 - increment_lock + increment_lock - attribute_names += [lock_col] - attribute_names.uniq! + attribute_names += [lock_col] + attribute_names.uniq! - begin - relation = self.class.unscoped + begin + relation = self.class.unscoped - affected_rows = relation.where( - self.class.primary_key => id, - lock_col => previous_lock_value - ).update_all( - attributes_for_update(attribute_names).map do |name| - [name, _read_attribute(name)] - end.to_h - ) + affected_rows = relation.where( + self.class.primary_key => id, + lock_col => previous_lock_value + ).update_all( + attributes_for_update(attribute_names).map do |name| + [name, _read_attribute(name)] + end.to_h + ) - unless affected_rows == 1 - raise ActiveRecord::StaleObjectError.new(self, "update") - end + unless affected_rows == 1 + raise ActiveRecord::StaleObjectError.new(self, "update") + end - affected_rows + affected_rows - # If something went wrong, revert the version. - rescue Exception - send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend - raise - end + # If something went wrong, revert the version. + rescue Exception + send(lock_col + '=', previous_lock_value) # rubocop:disable GitlabSecurity/PublicSend + raise end + end - # This is patched because we need it to query `lock_version IS NULL` - # rather than `lock_version = 0` whenever lock_version is NULL. - def relation_for_destroy - return super unless locking_enabled? + # This is patched because we need it to query `lock_version IS NULL` + # rather than `lock_version = 0` whenever lock_version is NULL. + def relation_for_destroy + return super unless locking_enabled? - column_name = self.class.locking_column - super.where(self.class.arel_table[column_name].eq(self[column_name])) - end + column_name = self.class.locking_column + super.where(self.class.arel_table[column_name].eq(self[column_name])) end + end + + # This is patched because we want `lock_version` default to `NULL` + # rather than `0` + if Gitlab.rails5? + class LockingType + def deserialize(value) + super + end - # This is patched because we want `lock_version` default to `NULL` - # rather than `0` + def serialize(value) + super + end + end + else class LockingType < SimpleDelegator def type_cast_from_database(value) super diff --git a/config/initializers/active_record_migration.rb b/config/initializers/active_record_migration.rb new file mode 100644 index 00000000000..04c06be7834 --- /dev/null +++ b/config/initializers/active_record_migration.rb @@ -0,0 +1,10 @@ +require 'active_record/migration' + +module ActiveRecord + class Migration + # data_source_exists? is not available in 4.2.10, table_exists deprecated in 5.0 + def table_exists?(table_name) + ActiveRecord::Base.connection.data_source_exists?(table_name) + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 52726f94753..e0a9139b1b4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,12 @@ Rails.application.routes.draw do post :toggle_award_emoji, on: :member end + favicon_redirect = redirect do |_params, _request| + ActionController::Base.helpers.asset_url(Gitlab::Favicon.main) + end + get 'favicon.png', to: favicon_redirect + get 'favicon.ico', to: favicon_redirect + draw :sherlock draw :development draw :ci diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb index a96ea7d9db4..dc16d5c5169 100644 --- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb +++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb @@ -12,7 +12,9 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration end def repository_storage_path - Gitlab.config.repositories.storages[repository_storage].legacy_disk_path + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + Gitlab.config.repositories.storages[repository_storage].legacy_disk_path + end end def repository_path diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb index 8986cd8cb4b..133435523e1 100644 --- a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb +++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb @@ -64,7 +64,9 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration # we rename suffix instead of removing it path = path.sub(/\.git\z/, '_git') - check_routes(path.dup, 0, path) + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + check_routes(path.dup, 0, path) + end end def check_routes(base, counter, path) diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md index aa5e9513290..621d4f77d5e 100644 --- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md +++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md @@ -107,7 +107,7 @@ Global Admins GitLab.org/GitLab INT/Global Groups/Global Admins ## GitLab LDAP configuration -The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb` configuration file. Below is an example of a complete configuration using an Active Directory. +The initial configuration of LDAP in GitLab requires changes to the `gitlab.rb` configuration file (`/etc/gitlab/gitlab.rb`). Below is an example of a complete configuration using an Active Directory. The two Active Directory specific values are `active_directory: true` and `uid: 'sAMAccountName'`. `sAMAccountName` is an attribute returned by Active Directory used for GitLab usernames. See the example output from `ldapsearch` for a full list of attributes a "person" object (user) has in **AD** - [`ldapsearch` example](#using-ldapsearch-unix) diff --git a/doc/api/README.md b/doc/api/README.md index 1c756dc855f..6267618d3bc 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -104,7 +104,7 @@ 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 +### Current status Currently only API version v4 is available. Version v3 was removed in [GitLab 11.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/36819). diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md index dcd5377284c..59e27922f64 100644 --- a/doc/api/graphql/index.md +++ b/doc/api/graphql/index.md @@ -29,9 +29,7 @@ curl --data "value=100" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://g ## Available queries -A first iteration of a GraphQL API includes only 2 queries: `project` and -`merge_request` and only returns scalar fields, or fields of the type `Project` -or `MergeRequest`. +A first iteration of a GraphQL API includes a query for: `project`. Within a project it is also possible to fetch a `mergeRequest` by IID. ## GraphiQL diff --git a/doc/api/groups.md b/doc/api/groups.md index 96842ef330f..a48905f2f15 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -12,7 +12,7 @@ Parameters: | `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, `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` | +| `order_by` | string | no | Order groups by `name`, `path` or `id`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | @@ -96,7 +96,7 @@ Parameters: | `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, `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` | +| `order_by` | string | no | Order groups by `name`, `path` or `id`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | | `statistics` | boolean | no | Include group statistics (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 9f06e20f803..da74045b702 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -70,18 +70,18 @@ Parameters: "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, @@ -190,18 +190,18 @@ Parameters: "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, @@ -297,18 +297,18 @@ Parameters: "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, @@ -548,14 +548,16 @@ Parameters: "username": "jarrett", "id": 5, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/b95567800f828948baf5f4160ebb2473?s=40&d=identicon" + "avatar_url": "http://www.gravatar.com/avatar/b95567800f828948baf5f4160ebb2473?s=40&d=identicon", + "web_url" : "https://gitlab.example.com/jarrett" }, "assignee": { "name": "Administrator", "username": "root", "id": 1, "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon" + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon", + "web_url" : "https://gitlab.example.com/root" }, "source_project_id": 4, "target_project_id": 4, @@ -669,18 +671,18 @@ POST /projects/:id/merge_requests "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 3, "target_project_id": 4, @@ -761,18 +763,18 @@ Must include at least one non-required attribute from above. "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 3, "target_project_id": 4, @@ -870,18 +872,18 @@ Parameters: "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 4, "target_project_id": 4, @@ -949,18 +951,18 @@ Parameters: "author": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, "username": "admin", - "email": "admin@example.com", "name": "Administrator", "state": "active", - "created_at": "2012-04-29T08:46:00Z" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 4, "target_project_id": 4, diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index f946536701e..26dcf67fe23 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -88,18 +88,18 @@ The example below simply moves all files from the root of the project to the `public/` directory. The `.public` workaround is so `cp` doesn't also copy `public/` to itself in an infinite loop: -``` +```yaml pages: stage: deploy script: - - mkdir .public - - cp -r * .public - - mv .public public + - mkdir .public + - cp -r * .public + - mv .public public artifacts: paths: - - public + - public only: - - master + - master ``` Read more on [GitLab Pages user documentation](../../user/project/pages/index.md). @@ -131,15 +131,15 @@ if you set it per-job: ```yaml before_script: -- global before script + - global before script job: before_script: - - execute this instead of global before script + - execute this instead of global before script script: - - my command + - my command after_script: - - execute this after my script + - execute this after my script ``` ## `stages` @@ -409,18 +409,18 @@ fails, it will not stop the next stage from running, since it's marked with job1: stage: test script: - - execute_script_that_will_fail + - execute_script_that_will_fail allow_failure: true job2: stage: test script: - - execute_script_that_will_succeed + - execute_script_that_will_succeed job3: stage: deploy script: - - deploy_to_staging + - deploy_to_staging ``` ## `when` @@ -442,38 +442,38 @@ For example: ```yaml stages: -- build -- cleanup_build -- test -- deploy -- cleanup + - build + - cleanup_build + - test + - deploy + - cleanup build_job: stage: build script: - - make build + - make build cleanup_build_job: stage: cleanup_build script: - - cleanup build when failed + - cleanup build when failed when: on_failure test_job: stage: test script: - - make test + - make test deploy_job: stage: deploy script: - - make deploy + - make deploy when: manual cleanup_job: stage: cleanup script: - - cleanup after jobs + - cleanup after jobs when: always ``` @@ -734,8 +734,8 @@ rspec: script: test cache: paths: - - binaries/*.apk - - .config + - binaries/*.apk + - .config ``` Locally defined cache overrides globally defined options. The following `rspec` @@ -744,14 +744,14 @@ job will cache only `binaries/`: ```yaml cache: paths: - - my/files + - my/files rspec: script: test cache: key: rspec paths: - - binaries/ + - binaries/ ``` Note that since cache is shared between jobs, if you're using different @@ -786,7 +786,7 @@ For example, to enable per-branch caching: cache: key: "$CI_COMMIT_REF_SLUG" paths: - - binaries/ + - binaries/ ``` If you use **Windows Batch** to run your shell scripts you need to replace @@ -796,17 +796,7 @@ If you use **Windows Batch** to run your shell scripts you need to replace cache: key: "%CI_COMMIT_REF_SLUG%" paths: - - binaries/ -``` - -If you use **Windows PowerShell** to run your shell scripts you need to replace -`$` with `$env:`: - -```yaml -cache: - key: "$env:CI_COMMIT_REF_SLUG" - paths: - - binaries/ + - binaries/ ``` ### `cache:untracked` @@ -829,7 +819,7 @@ rspec: cache: untracked: true paths: - - binaries/ + - binaries/ ``` ### `cache:policy` @@ -907,8 +897,8 @@ Send all files in `binaries` and `.config`: ```yaml artifacts: paths: - - binaries/ - - .config + - binaries/ + - .config ``` To disable artifact passing, define the job with empty [dependencies](#dependencies): @@ -937,7 +927,7 @@ release-job: - mvn package -U artifacts: paths: - - target/*.war + - target/*.war only: - tags ``` @@ -959,7 +949,7 @@ job: artifacts: name: "$CI_JOB_NAME" paths: - - binaries/ + - binaries/ ``` To create an archive with a name of the current branch or tag including only @@ -970,7 +960,7 @@ job: artifacts: name: "$CI_COMMIT_REF_NAME" paths: - - binaries/ + - binaries/ ``` To create an archive with a name of the current job and the current branch or @@ -981,7 +971,7 @@ job: artifacts: name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" paths: - - binaries/ + - binaries/ ``` To create an archive with a name of the current [stage](#stages) and branch name: @@ -991,7 +981,7 @@ job: artifacts: name: "$CI_JOB_STAGE-$CI_COMMIT_REF_NAME" paths: - - binaries/ + - binaries/ ``` --- @@ -1004,7 +994,7 @@ job: artifacts: name: "%CI_JOB_STAGE%-%CI_COMMIT_REF_NAME%" paths: - - binaries/ + - binaries/ ``` If you use **Windows PowerShell** to run your shell scripts you need to replace @@ -1015,7 +1005,7 @@ job: artifacts: name: "$env:CI_JOB_STAGE-$env:CI_COMMIT_REF_NAME" paths: - - binaries/ + - binaries/ ``` ### `artifacts:untracked` @@ -1040,7 +1030,7 @@ Send all Git untracked files and files in `binaries`: artifacts: untracked: true paths: - - binaries/ + - binaries/ ``` ### `artifacts:when` @@ -1130,26 +1120,26 @@ build:osx: script: make build:osx artifacts: paths: - - binaries/ + - binaries/ build:linux: stage: build script: make build:linux artifacts: paths: - - binaries/ + - binaries/ test:osx: stage: test script: make test:osx dependencies: - - build:osx + - build:osx test:linux: stage: test script: make test:linux dependencies: - - build:linux + - build:linux deploy: stage: deploy @@ -1416,6 +1406,43 @@ variables: You can set it globally or per-job in the [`variables`](#variables) section. +### Custom build directories + +> [Introduced][gitlab-runner-876] in Gitlab Runner 11.1 + +NOTE: **Note:** +This can only be used when `custom_build_dir` is set to true in the [Runner's +configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html). + +By default, GitLab Runner clones the repository in the `/builds` directory, +but sometimes your project might require to have the code in a specific +directory, like the GO projects for example. In that case, you can specify +the `CI_PROJECT_DIR` variable to tell the Runner in which directory to clone +the repository: + +```yml +image: golang:1.10-alpine3.7 + +variables: + CI_PROJECT_DIR: /go/src/gitlab.com/namespace/project-name + +stages: + - test + +dir: + stage: test + script: + - pwd # /go/src/gitlab.com/namespace/project-name +``` + +The following executors may use this feature only when +[concurrent](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section) +is set to `1`: + +- `shell` +- `ssh` +- `docker`, `docker+machine` when the job's working directory is mounted as a host volume. + ## Special YAML features It's possible to use special YAML features like anchors (`&`), aliases (`*`) @@ -1609,5 +1636,6 @@ CI with various languages. [ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983 [ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447 [ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909 +[gitlab-runner-876]: https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/876 [schedules]: ../../user/project/pipelines/schedules.md [variables-expressions]: ../variables/README.md#variables-expressions diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md index eb18189282b..6395af6f815 100644 --- a/doc/development/new_fe_guide/style/prettier.md +++ b/doc/development/new_fe_guide/style/prettier.md @@ -43,3 +43,17 @@ yarn prettier-all-save Formats all files in the repository with Prettier. (This should only be used to test global rule updates otherwise you would end up with huge MR's). The source of these Yarn scripts can be found in `/scripts/frontend/prettier.js`. + +### Scripts during Conversion period + +``` +node ./scripts/frontend/prettier.js check ./vendor/ +``` + +This will go over all files in a specific folder check it. + +``` +node ./scripts/frontend/prettier.js save ./vendor/ +``` + +This will go over all files in a specific folder and save it. diff --git a/doc/integration/google.md b/doc/integration/google.md index ae1d848f439..8906f91b6b4 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -35,7 +35,12 @@ In Google's side: 1. You should now be able to see a Client ID and Client secret. Note them down or keep this page open as you will need them later. -1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google Kubernetes Engine API > Enable** +1. From the **Dashboard** select **ENABLE APIS AND SERVICES > Compute > Google+ API > Enable** +1. To enable projects to access [Google Kubernetes Engine](../user/project/clusters/index.md), you must also + enable these APIs: + - Google Kubernetes Engine API + - Cloud Resource Manager API + - Cloud Billing API On your GitLab server: diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 3edde3de83d..82e8fbdb93e 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -168,7 +168,7 @@ want their accounts to be upgraded to full internal accounts. >**Note:** The following information only applies for installations from source. -GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships +GitLab uses [Omniauth](https://github.com/omniauth/omniauth) for authentication and already ships with a few providers pre-installed (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider. diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md index 2ca2bf743fb..7707d56764e 100644 --- a/doc/topics/git/index.md +++ b/doc/topics/git/index.md @@ -17,7 +17,7 @@ We've gathered some resources to help you to get the best from Git with GitLab. - [How to install Git](how_to_install_git/index.md) - [Start using Git on the command line](../../gitlab-basics/start-using-git.md) - [Command Line basic commands](../../gitlab-basics/command-line-commands.md) -- [GitLab Git Cheat Sheet (download)](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf) +- [GitLab Git Cheat Sheet (download)](https://about.gitlab.com/images/press/git-cheat-sheet.pdf) - Commits - [Revert a commit](../../user/project/merge_requests/revert_changes.md#reverting-a-commit) - [Cherry-picking a commit](../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit) diff --git a/doc/university/README.md b/doc/university/README.md index aa68c841f92..595fc480887 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -15,11 +15,11 @@ Would you like to contribute to GitLab University? Then please take a look at ou The curriculum is composed of GitLab videos, screencasts, presentations, projects and external GitLab content hosted on other services and has been organized into the following sections. -1. [GitLab Beginner](#beginner) -1. [GitLab Intermediate](#intermediate) -1. [GitLab Advanced](#advanced) -1. [External Articles](#external) -1. [Resources for GitLab Team Members](#team) +1. [GitLab Beginner](#1-gitlab-beginner) +1. [GitLab Intermediate](#2-gitlab-intermediate) +1. [GitLab Advanced](#3-gitlab-advanced) +1. [External Articles](#4-external-articles) +1. [Resources for GitLab Team Members](#5-resources-for-gitlab-team-members) --- @@ -126,7 +126,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project 1. [IBM: Continuous Delivery vs Continuous Deployment - Video](https://www.youtube.com/watch?v=igwFj8PPSnw) 1. [Amazon: Transition to Continuous Delivery - Video](https://www.youtube.com/watch?v=esEFaY0FDKc) 2. [TechBeacon: Doing continuous delivery? Focus first on reducing release cycle times](https://techbeacon.com/doing-continuous-delivery-focus-first-reducing-release-cycle-times) -1. See **[Integrations](#integrations)** for integrations with other CI services. +1. See **[Integrations](#39-integrations)** for integrations with other CI services. #### 2.4. Workflow diff --git a/doc/university/high-availability/aws/README.md b/doc/university/high-availability/aws/README.md index f340164b882..dc045961ed7 100644 --- a/doc/university/high-availability/aws/README.md +++ b/doc/university/high-availability/aws/README.md @@ -2,6 +2,10 @@ comments: false --- +DANGER: This guide exists for reference of how an AWS deployment could work. +We are currently seeing very slow EFS access performance which causes GitLab to +be 5-10x slower than using NFS or Local disk. We _do not_ recommend follow this +guide at this time. # High Availability on AWS diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 5f976a8ad31..8e87c896a72 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -3,13 +3,15 @@ ## GitLab Flavored Markdown (GFM) > **Note:** -Not all of the GitLab-specific extensions to Markdown that are described in -this document currently work on our documentation website. +> Not all of the GitLab-specific extensions to Markdown that are described in +> this document currently work on our documentation website. > -For the best result, we encourage you to check this document out as rendered +> For the best result, we encourage you to check this document out as rendered by GitLab: [markdown.md] -_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._ +_GitLab uses (as of 11.1) the [CommonMark Ruby Library][commonmarker] for Markdown processing of all new issues, merge requests, comments, and other Markdown content in the GitLab system. Previous content and Markdown files `.md` in the repositories are still processed using the [Redcarpet Ruby library][redcarpet]._ + +_Where there are significant differences, we will try to call them out in this document._ GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). @@ -21,7 +23,7 @@ You can use GFM in the following areas: - milestones - snippets (the snippet must be named with a `.md` extension) - wiki pages -- markdown documents inside the repository +- markdown documents inside the repository (currently only rendered by Redcarpet) You can also use other rich text files in GitLab. You might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. @@ -394,14 +396,14 @@ Color written inside backticks will be followed by a color "chip". Examples: - `#F00` - `#F00A` - `#FF0000` - `#FF0000AA` - `RGB(0,255,0)` - `RGB(0%,100%,0%)` - `RGBA(0,255,0,0.7)` - `HSL(540,70%,50%)` + `#F00` + `#F00A` + `#FF0000` + `#FF0000AA` + `RGB(0,255,0)` + `RGB(0%,100%,0%)` + `RGBA(0,255,0,0.7)` + `HSL(540,70%,50%)` `HSLA(540,70%,50%,0.7)` Become: @@ -414,7 +416,7 @@ Become: `RGB(0%,100%,0%)` `RGBA(0,255,0,0.7)` `HSL(540,70%,50%)` -`HSLA(540,70%,50%,0.7)` +`HSLA(540,70%,50%,0.7)` #### Supported formats: @@ -500,6 +502,7 @@ For example: # This header has Unicode in it: 한글 ## This header has spaces in it ### This header has spaces in it +## This header has 3.5 in it (and parentheses) ``` Would generate the following link IDs: @@ -509,6 +512,7 @@ Would generate the following link IDs: 1. `this-header-has-unicode-in-it-한글` 1. `this-header-has-spaces-in-it` 1. `this-header-has-spaces-in-it-1` +1. `this-header-has-3-5-in-it-and-parentheses` Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. @@ -543,9 +547,9 @@ Examples: ```no-highlight 1. First ordered list item 2. Another item - * Unordered sub-list. + * Unordered sub-list. 1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list + 1. Ordered sub-list 4. And another item. * Unordered list can use asterisks @@ -557,9 +561,9 @@ Become: 1. First ordered list item 2. Another item - * Unordered sub-list. + * Unordered sub-list. 1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list + 1. Ordered sub-list 4. And another item. * Unordered list can use asterisks @@ -567,33 +571,36 @@ Become: + Or pluses If a list item contains multiple paragraphs, -each subsequent paragraph should be indented with four spaces. +each subsequent paragraph should be indented to the same level as the start of the list item text (_Redcarpet: paragraph should be indented with four spaces._) Example: ```no-highlight -1. First ordered list item +1. First ordered list item - Second paragraph of first item. -2. Another item + Second paragraph of first item. + +2. Another item ``` Becomes: 1. First ordered list item - Second paragraph of first item. + Paragraph of first item. + 2. Another item -If the second paragraph isn't indented with four spaces, -the second list item will be incorrectly labeled as `1`. +If the paragraph of the first item is not indented with the proper number of spaces, +the paragraph will appear outside the list, instead of properly indented under the list item. Example: ```no-highlight 1. First ordered list item - Second paragraph of first item. + Paragraph of first item. + 2. Another item ``` @@ -601,7 +608,8 @@ Becomes: 1. First ordered list item - Second paragraph of first item. + Paragraph of first item. + 2. Another item ### Links @@ -719,20 +727,24 @@ Content can be collapsed using HTML's [`<details>`](https://developer.mozilla.or <p> <details> <summary>Click me to collapse/fold.</summary> -These details will remain hidden until expanded. + +These details <em>will</em> remain <strong>hidden</strong> until expanded. <pre><code>PASTE LOGS HERE</code></pre> + </details> </p> -**Note:** Unfortunately Markdown is not supported inside these tags, as described by the [markdown specification](https://daringfireball.net/projects/markdown/syntax#html). You can work around this by using HTML, for example you can use `<pre><code>` tags instead of [code fences](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting). +**Note:** Markdown inside these tags is supported, as long as you have a blank link after the `</summary>` tag and before the `</details>` tag, as shown in the example. _Redcarpet does not support Markdown inside these tags. You can work around this by using HTML, for example you can use `<pre><code>` tags instead of [code fences](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting)._ ```html <details> <summary>Click me to collapse/fold.</summary> -These details will remain hidden until expanded. -<pre><code>PASTE LOGS HERE</code></pre> +These details _will_ remain **hidden** until expanded. + + PASTE LOGS HERE + </details> ``` @@ -774,7 +786,7 @@ Underscores ### Line Breaks -My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend. +A good way to learn how line breaks work is to experiment and discover -- hit <kbd>Enter</kbd> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. The "Preview" tab is your friend. Here are some things to try out: @@ -810,7 +822,7 @@ spaces. ### Tables -Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them. +Tables aren't part of the core Markdown spec, but they are part of GFM. Example: @@ -828,9 +840,7 @@ Becomes: | cell 1 | cell 2 | | cell 3 | cell 4 | -**Note** - -The row of dashes between the table header and body must have at least three dashes in each column. +**Note:** The row of dashes between the table header and body must have at least three dashes in each column. By including colons in the header row, you can align the text within that column. @@ -863,6 +873,18 @@ Becomes: You can add footnotes to your text as follows.[^2] +### Superscripts / Subscripts + +CommonMark and GFM currently do not support the superscript syntax ( `x^2` ) that Redcarpet does. You can use the standard HTML syntax for superscripts and subscripts. + +``` +The formula for water is H<sub>2</sub>O +while the equation for the theory of relativity is E = mc<sup>2</sup>. +``` + +The formula for water is H<sub>2</sub>O while the equation for the theory of relativity is E = mc<sup>2</sup>. + + ## Wiki-specific Markdown The following examples show how links inside wikis behave. @@ -954,3 +976,4 @@ A link starting with a `/` is relative to the wiki root. [katex]: https://github.com/Khan/KaTeX "KaTeX website" [katex-subset]: https://github.com/Khan/KaTeX/wiki/Function-Support-in-KaTeX "Macros supported by KaTeX" [asciidoctor-manual]: http://asciidoctor.org/docs/user-manual/#activating-stem-support "Asciidoctor user manual" +[commonmarker]: https://github.com/gjtorikian/commonmarker diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 16c19855136..b36b0b4f757 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -51,6 +51,9 @@ The following table depicts the various user permission levels in a project. | See a container registry | | ✓ | ✓ | ✓ | ✓ | | See environments | | ✓ | ✓ | ✓ | ✓ | | See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | +| Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ | +| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ | +| Lock merge request discussions | | | ✓ | ✓ | ✓ | | Create new environments | | | ✓ | ✓ | ✓ | | Stop environments | | | ✓ | ✓ | ✓ | | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | @@ -76,11 +79,12 @@ The following table depicts the various user permission levels in a project. | Edit project | | | | ✓ | ✓ | | Add deploy keys to project | | | | ✓ | ✓ | | Configure project hooks | | | | ✓ | ✓ | -| Manage runners | | | | ✓ | ✓ | +| Manage Runners | | | | ✓ | ✓ | | Manage job triggers | | | | ✓ | ✓ | | Manage variables | | | | ✓ | ✓ | -| Manage pages | | | | ✓ | ✓ | -| Manage pages domains and certificates | | | | ✓ | ✓ | +| Manage GitLab Pages | | | | ✓ | ✓ | +| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | +| Remove GitLab Pages | | | | | ✓ | | Manage clusters | | | | ✓ | ✓ | | Edit comments (posted by any user) | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | @@ -90,6 +94,7 @@ The following table depicts the various user permission levels in a project. | Remove pages | | | | | ✓ | | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | +| View project Audit Events | | | | ✓ | ✓ | ## Project features permissions @@ -127,17 +132,12 @@ and drag issues around. Read though the [documentation on Issue Boards permissions](project/issue_board.md#permissions) to learn more. -### File Locking permissions - -> Available in [GitLab Premium](https://about.gitlab.com/products/). +### File Locking permissions **[PREMIUM]** The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located. Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more. -File Locking is available in -[GitLab Premium](https://about.gitlab.com/products/) only. - ### Confidential Issues permissions Confidential issues can be accessed by reporters and higher permission levels, @@ -160,6 +160,12 @@ group. | Remove group | | | | | ✓ | | Manage group labels | | ✓ | ✓ | ✓ | ✓ | | Create/edit/delete group milestones | | | ✓ | ✓ | ✓ | +| View private group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | +| View internal group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | +| View public group epic **[ULTIMATE]** | ✓ | ✓ | ✓ | ✓ | ✓ | +| Create/edit group epic **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | +| Delete group epic **[ULTIMATE]** | | | | | ✓ | +| View group Audit Events | | | | | ✓ | ### Subgroup permissions @@ -194,6 +200,27 @@ will find the option to flag the user as external. By default new users are not set as external users. This behavior can be changed by an administrator under **Admin > Application Settings**. +## Auditor users **[PREMIUM ONLY]** + +>[Introduced][ee-998] in [GitLab Premium][eep] 8.17. + +Auditor users are given read-only access to all projects, groups, and other +resources on the GitLab instance. + +An Auditor user should be able to access all projects and groups of a GitLab instance +with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user). + +[Read more about Auditor users.](https://docs.gitlab.com/ee/administration/auditor_users.html) + +## Project features + +Project features like wiki and issues can be hidden from users depending on +which visibility level you select on project settings. + +- Disabled: disabled for everyone +- Only team members: only team members will see even if your project is public or internal +- Everyone with access: everyone can see depending on your project visibility level + ## GitLab CI/CD permissions GitLab CI/CD permissions rely on the role the user has in GitLab. There are four @@ -263,16 +290,6 @@ for details about the pipelines security model. Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user. Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/index.html#updating-user-permissions-new-feature) to learn more. -## Auditor users permissions - -> Available in [GitLab Premium](https://about.gitlab.com/products/). - -An Auditor user should be able to access all projects and groups of a GitLab instance -with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user). - -Auditor users are available in [GitLab Premium](https://about.gitlab.com/products/) -only. - [^1]: On public and internal projects, all users are able to perform this action [^2]: Guest users can only view the confidential issues they created themselves [^3]: If **Public pipelines** is enabled in **Project Settings > CI/CD** @@ -283,3 +300,5 @@ only. [ce-18994]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18994 [new-mod]: project/new_ci_build_permissions_model.md +[ee-998]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/998 +[eep]: https://about.gitlab.com/products/
\ No newline at end of file diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index aa2fcd82787..9ca1e6226c5 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -250,12 +250,12 @@ One group issue board per group was made available in GitLab 10.6 Core after mul Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table: -| Tier | Number of project issue boards | Board with configuration in project issue boards | Number of group issue boards | Board with configuration in group issue boards | -| --- | --- | --- | --- | --- | -| Core | 1 | No | 1 | No | -| Starter | Multiple | Yes | 1 | No | -| Premium | Multiple | Yes | Multiple | Yes | -| Ultimate | Multiple | Yes | Multiple | Yes | +| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Project Issue Boards | Configurable Group Issue Boards | Assignee Lists +| --- | --- | --- | --- | --- | --- | +| Core | 1 | 1 | No | No | No | +| Starter | Multiple | 1 | Yes | No | No | +| Premium | Multiple | Multiple | Yes | Yes | Yes | +| Ultimate | Multiple | Multiple | Yes | Yes | Yes | ## Tips diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index 64bb33be547..632253db94c 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -10,7 +10,6 @@ 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 requests of projects in [subgroups](../../group/subgroups/index.md). ## Creating milestones diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index 376f4e3cbe4..bda293bd00e 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -176,4 +176,12 @@ Lock your files to prevent any conflicting changes. You can access your repos via [repository API](../../../api/repositories.md). +## Clone in Apple Xcode + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45820) in GitLab 11.0 + +Projects that contain a `.xcodeproj` or `.xcworkspace` directory can now be cloned +in Xcode using the new **Open in Xcode** button, located next to the Git URL +used for cloning your project. The button is only shown on macOS. + [jupyter]: https://jupyter.org diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md index 6c1378560ef..918daee5d9f 100644 --- a/doc/user/reserved_names.md +++ b/doc/user/reserved_names.md @@ -58,6 +58,7 @@ Currently the following names are reserved as top level groups: - dashboard - deploy.html - explore +- favicon.ico - favicon.png - groups - header_logo_dark.png diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 03b6b30a0d8..c7f41aba854 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -32,7 +32,7 @@ module API optional :all_available, type: Boolean, desc: 'Show all group that you have access to' optional :search, type: String, desc: 'Search for a specific group' optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user' - optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path' + optional :order_by, type: String, values: %w[name path id], default: 'name', desc: 'Order by name, path or id' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' use :pagination end @@ -46,7 +46,9 @@ module API groups = GroupsFinder.new(current_user, find_params).execute groups = groups.search(params[:search]) if params[:search].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? - groups = groups.reorder(params[:order_by] => params[:sort]) + order_options = { params[:order_by] => params[:sort] } + order_options["id"] ||= "asc" + groups = groups.reorder(order_options) groups end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2ed331d4fd2..9c53b7c3fe7 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -272,7 +272,8 @@ module API attrs[key] = params_hash[key] end end - ActionController::Parameters.new(attrs).permit! + permitted_attrs = ActionController::Parameters.new(attrs).permit! + Gitlab.rails5? ? permitted_attrs.to_h : permitted_attrs end def filter_by_iid(items, iid) diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index b9ed68aa584..5d55224c1a7 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -10,9 +10,7 @@ module API detail "This feature was introduced in GitLab 11.0." end post do - # Explicitly set CommonMark as markdown engine to use. - # Remove this set when https://gitlab.com/gitlab-org/gitlab-ce/issues/43011 is done. - context = { markdown_engine: :common_mark, only_path: false } + context = { only_path: false } if params[:project] project = Project.find_by_full_path(params[:project]) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index dc102259ca8..96a02914faa 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -84,7 +84,11 @@ module API end post '/request' do authenticate_runner! - no_content! unless current_runner.active? + + unless current_runner.active? + header 'X-GitLab-Last-Update', current_runner.ensure_runner_queue_value + break no_content! + end if current_runner.runner_queue_value_latest?(params[:last_update]) header 'X-GitLab-Last-Update', params[:last_update] diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb index d2c4b1e4d76..fbfcd72c916 100644 --- a/lib/banzai/filter/blockquote_fence_filter.rb +++ b/lib/banzai/filter/blockquote_fence_filter.rb @@ -10,7 +10,7 @@ module Banzai ^``` .+? - \n```$ + \n```\ *$ ) | (?<html> @@ -19,9 +19,9 @@ module Banzai # Anything, including `>>>` blocks which are ignored by this filter # </tag> - ^<[^>]+?>\n + ^<[^>]+?>\ *\n .+? - \n<\/[^>]+?>$ + \n<\/[^>]+?>\ *$ ) | (?: @@ -30,14 +30,14 @@ module Banzai # Anything, including code and HTML blocks # >>> - ^>>>\n + ^>>>\ *\n (?<quote> (?: # Any character that doesn't introduce a code or HTML block (?! ^``` | - ^<[^>]+?>\n + ^<[^>]+?>\ *\n ) . | @@ -48,7 +48,7 @@ module Banzai \g<html> )+? ) - \n>>>$ + \n>>>\ *$ ) }mx.freeze diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb index c1e2b680240..944363f17d3 100644 --- a/lib/banzai/filter/markdown_filter.rb +++ b/lib/banzai/filter/markdown_filter.rb @@ -14,7 +14,7 @@ module Banzai private - DEFAULT_ENGINE = :redcarpet + DEFAULT_ENGINE = :common_mark def engine(engine_from_context) engine_from_context ||= DEFAULT_ENGINE diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb index 858e790005c..af8448937b3 100644 --- a/lib/banzai/filter/milestone_reference_filter.rb +++ b/lib/banzai/filter/milestone_reference_filter.rb @@ -65,7 +65,7 @@ module Banzai # We don't support IID lookups for group milestones, because IIDs can # clash between group and project milestones. if project.group && !params[:iid] - finder_params[:group_ids] = project.group.self_and_ancestors.select(:id) + finder_params[:group_ids] = project.group.self_and_ancestors_ids end MilestonesFinder.new(finder_params).find_by(params) diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb index 914a9e48a2f..522c69a0bb1 100644 --- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb +++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb @@ -54,7 +54,8 @@ module Gitlab def ensure_temporary_tracking_table_exists table_name = :untracked_files_for_uploads - unless UntrackedFile.connection.table_exists?(table_name) + + unless ActiveRecord::Base.connection.data_source_exists?(table_name) UntrackedFile.connection.create_table table_name do |t| t.string :path, limit: 600, null: false t.index :path, unique: true diff --git a/lib/gitlab/checks/commit_check.rb b/lib/gitlab/checks/commit_check.rb index 43a52b493bb..22310e313ac 100644 --- a/lib/gitlab/checks/commit_check.rb +++ b/lib/gitlab/checks/commit_check.rb @@ -37,7 +37,7 @@ module Gitlab def validate_lfs_file_locks? strong_memoize(:validate_lfs_file_locks) do - project.lfs_enabled? && project.lfs_file_locks.any? && newrev && oldrev + project.lfs_enabled? && newrev && oldrev && project.any_lfs_file_locks? end end diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb index c9c3050cfc2..87af4a90572 100644 --- a/lib/gitlab/checks/force_push.rb +++ b/lib/gitlab/checks/force_push.rb @@ -7,18 +7,10 @@ module Gitlab # Created or deleted branch return false if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) - GitalyClient.migrate(:force_push) do |is_enabled| - if is_enabled - !project - .repository - .gitaly_commit_client - .ancestor?(oldrev, newrev) - else - Gitlab::Git::RevList.new( - project.repository.raw, oldrev: oldrev, newrev: newrev - ).missed_ref.present? - end - end + !project + .repository + .gitaly_commit_client + .ancestor?(oldrev, newrev) end end end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index d49d055c3f2..4ad106e7b0a 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -188,8 +188,11 @@ module Gitlab end def self.cached_table_exists?(table_name) - # Rails 5 uses data_source_exists? instead of table_exists? - connection.schema_cache.table_exists?(table_name) + if Gitlab.rails5? + connection.schema_cache.data_source_exists?(table_name) + else + connection.schema_cache.table_exists?(table_name) + end end private_class_method :connection diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb index 62d4d0a92a6..26ae6966746 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb @@ -37,6 +37,7 @@ module Gitlab class Namespace < ActiveRecord::Base include MigrationClasses::Routable self.table_name = 'namespaces' + self.inheritance_column = :_type_disabled belongs_to :parent, class_name: "#{MigrationClasses.name}::Namespace" has_one :route, as: :source diff --git a/lib/gitlab/favicon.rb b/lib/gitlab/favicon.rb index 451c9daf761..faf7016d73a 100644 --- a/lib/gitlab/favicon.rb +++ b/lib/gitlab/favicon.rb @@ -2,7 +2,7 @@ module Gitlab class Favicon class << self def main - return appearance_favicon.favicon_main.url if appearance_favicon.exists? + return appearance_favicon.url if appearance_favicon.exists? image_name = if Gitlab::Utils.to_boolean(ENV['CANARY']) diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb index 40b65f6c0da..e25e15f5c80 100644 --- a/lib/gitlab/git/blame.rb +++ b/lib/gitlab/git/blame.rb @@ -22,24 +22,9 @@ module Gitlab private def load_blame - raw_output = @repo.gitaly_migrate(:blame, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - load_blame_by_gitaly - else - load_blame_by_shelling_out - end - end - - output = encode_utf8(raw_output) - process_raw_blame output - end - - def load_blame_by_gitaly - @repo.gitaly_commit_client.raw_blame(@sha, @path) - end + output = encode_utf8(@repo.gitaly_commit_client.raw_blame(@sha, @path)) - def load_blame_by_shelling_out - @repo.shell_blame(@sha, @path) + process_raw_blame(output) end def process_raw_blame(output) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 61ae42a116b..7ce90ce170f 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -120,13 +120,11 @@ module Gitlab # Default branch in the repository def root_ref - @root_ref ||= gitaly_migrate(:root_ref, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.default_branch_name - else - discover_default_branch - end - end + gitaly_ref_client.default_branch_name + rescue GRPC::NotFound => e + raise NoRepository.new(e.message) + rescue GRPC::Unknown => e + raise Gitlab::Git::CommandError.new(e.message) end def rugged @@ -152,23 +150,15 @@ module Gitlab # Returns an Array of branch names # sorted by name ASC def branch_names - gitaly_migrate(:branch_names, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.branch_names - else - branches.map(&:name) - end + wrapped_gitaly_errors do + gitaly_ref_client.branch_names end end # Returns an Array of Branches def branches - gitaly_migrate(:branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.branches - else - branches_filter - end + wrapped_gitaly_errors do + gitaly_ref_client.branches end end @@ -200,12 +190,8 @@ module Gitlab end def local_branches(sort_by: nil) - gitaly_migrate(:local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.local_branches(sort_by: sort_by) - else - branches_filter(filter: :local, sort_by: sort_by) - end + wrapped_gitaly_errors do + gitaly_ref_client.local_branches(sort_by: sort_by) end end @@ -245,18 +231,6 @@ module Gitlab # This refs by default not visible in project page and not cloned to client side. alias_method :has_visible_content?, :has_local_branches? - def has_local_branches_rugged? - rugged.branches.each(:local).any? do |ref| - begin - ref.name && ref.target # ensures the branch is valid - - true - rescue Rugged::ReferenceError - false - end - end - end - # Returns the number of valid tags def tag_count gitaly_migrate(:tag_names) do |is_enabled| @@ -270,12 +244,8 @@ module Gitlab # Returns an Array of tag names def tag_names - gitaly_migrate(:tag_names, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.tag_names - else - rugged.tags.map { |t| t.name } - end + wrapped_gitaly_errors do + gitaly_ref_client.tag_names end end @@ -283,12 +253,8 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/390 def tags - gitaly_migrate(:tags, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - tags_from_gitaly - else - tags_from_rugged - end + wrapped_gitaly_errors do + gitaly_ref_client.tags end end @@ -364,31 +330,6 @@ module Gitlab end.map(&:name) end - # Discovers the default branch based on the repository's available branches - # - # - If no branches are present, returns nil - # - If one branch is present, returns its name - # - If two or more branches are present, returns current HEAD or master or first branch - def discover_default_branch - names = branch_names - - return if names.empty? - - return names[0] if names.length == 1 - - if rugged_head - extracted_name = Ref.extract_branch_name(rugged_head.name) - - return extracted_name if names.include?(extracted_name) - end - - if names.include?('master') - 'master' - else - names[0] - end - end - def rugged_head rugged.head rescue Rugged::ReferenceError @@ -531,13 +472,21 @@ module Gitlab end def count_commits(options) - count_commits_options = process_count_commits_options(options) + options = process_count_commits_options(options.dup) - gitaly_migrate(:count_commits, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - count_commits_by_gitaly(count_commits_options) + wrapped_gitaly_errors do + if options[:left_right] + from = options[:from] + to = options[:to] + + right_count = gitaly_commit_client + .commit_count("#{from}..#{to}", options) + left_count = gitaly_commit_client + .commit_count("#{to}..#{from}", options) + + [left_count, right_count] else - count_commits_by_shelling_out(count_commits_options) + gitaly_commit_client.commit_count(options[:ref], options) end end end @@ -735,15 +684,9 @@ module Gitlab end # Return total commits count accessible from passed ref - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/330 def commit_count(ref) - gitaly_migrate(:commit_count, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_commit_client.commit_count(ref) - else - rugged_commit_count(ref) - end + wrapped_gitaly_errors do + gitaly_commit_client.commit_count(ref) end end @@ -1043,21 +986,7 @@ module Gitlab def info_attributes return @info_attributes if @info_attributes - content = - gitaly_migrate(:get_info_attributes, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_repository_client.info_attributes - else - attributes_path = File.join(File.expand_path(path), 'info', 'attributes') - - if File.exist?(attributes_path) - File.read(attributes_path) - else - "" - end - end - end - + content = gitaly_repository_client.info_attributes @info_attributes = AttributesParser.new(content) end @@ -1113,18 +1042,8 @@ module Gitlab end def license_short_name - gitaly_migrate(:license_short_name, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_repository_client.license_short_name - else - begin - # The licensee gem creates a Rugged object from the path: - # https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb - Licensee.license(path).try(:key) - rescue Rugged::Error - end - end + wrapped_gitaly_errors do + gitaly_repository_client.license_short_name end end @@ -1311,12 +1230,8 @@ module Gitlab end def rebase_in_progress?(rebase_id) - gitaly_migrate(:rebase_in_progress) do |is_enabled| - if is_enabled - gitaly_repository_client.rebase_in_progress?(rebase_id) - else - fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)) - end + wrapped_gitaly_errors do + gitaly_repository_client.rebase_in_progress?(rebase_id) end end @@ -1332,12 +1247,8 @@ module Gitlab end def squash_in_progress?(squash_id) - gitaly_migrate(:squash_in_progress, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_repository_client.squash_in_progress?(squash_id) - else - fresh_worktree?(worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)) - end + wrapped_gitaly_errors do + gitaly_repository_client.squash_in_progress?(squash_id) end end @@ -1453,6 +1364,16 @@ module Gitlab raise CommandError.new(e) end + def wrapped_gitaly_errors(&block) + yield block + rescue GRPC::NotFound => e + raise NoRepository.new(e) + rescue GRPC::InvalidArgument => e + raise ArgumentError.new(e) + rescue GRPC::BadStatus => e + raise CommandError.new(e) + end + def clean_stale_repository_files gitaly_migrate(:repository_cleanup, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| gitaly_repository_client.cleanup if is_enabled && exists? @@ -1563,10 +1484,6 @@ module Gitlab run_git!(args, lazy_block: block) end - def missed_ref(oldrev, newrev) - run_git!(['rev-list', '--max-count=1', oldrev, "^#{newrev}"]) - end - def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:) base_args = %w(worktree add --detach) @@ -1606,12 +1523,8 @@ module Gitlab private def uncached_has_local_branches? - gitaly_migrate(:has_local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_repository_client.has_local_branches? - else - has_local_branches_rugged? - end + wrapped_gitaly_errors do + gitaly_repository_client.has_local_branches? end end @@ -1674,21 +1587,6 @@ module Gitlab end end - # This function is duplicated in Gitaly-Go, don't change it! - # https://gitlab.com/gitlab-org/gitaly/merge_requests/698 - def fresh_worktree?(path) - File.exist?(path) && !clean_stuck_worktree(path) - end - - # This function is duplicated in Gitaly-Go, don't change it! - # https://gitlab.com/gitlab-org/gitaly/merge_requests/698 - def clean_stuck_worktree(path) - return false unless File.mtime(path) < 15.minutes.ago - - FileUtils.rm_rf(path) - true - end - # Adding a worktree means checking out the repository. For large repos, # this can be very expensive, so set up sparse checkout for the worktree # to only check out the files we're interested in. @@ -1731,20 +1629,6 @@ module Gitlab } end - # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'. - def branches_filter(filter: nil, sort_by: nil) - branches = rugged.branches.each(filter).map do |rugged_ref| - begin - target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) - rescue Rugged::ReferenceError - # Omit invalid branch - end - end.compact - - sort_branches(branches, sort_by) - end - def git_merged_branch_names(branch_names, root_sha) git_arguments = %W[branch --merged #{root_sha} @@ -1956,37 +1840,11 @@ module Gitlab end end - def tags_from_rugged - rugged.references.each("refs/tags/*").map do |ref| - message = nil - - if ref.target.is_a?(Rugged::Tag::Annotation) - tag_message = ref.target.message - - if tag_message.respond_to?(:chomp) - message = tag_message.chomp - end - end - - target_commit = Gitlab::Git::Commit.find(self, ref.target) - Gitlab::Git::Tag.new(self, { - name: ref.name, - target: ref.target, - target_commit: target_commit, - message: message - }) - end.sort_by(&:name) - end - def last_commit_for_path_by_rugged(sha, path) sha = last_commit_id_for_path_by_shelling_out(sha, path) commit(sha) end - def tags_from_gitaly - gitaly_ref_client.tags - end - def size_by_shelling_out popen(%w(du -sk), path).first.strip.to_i end @@ -1995,71 +1853,6 @@ module Gitlab gitaly_repository_client.repository_size end - def count_commits_by_gitaly(options) - if options[:left_right] - from = options[:from] - to = options[:to] - - right_count = gitaly_commit_client - .commit_count("#{from}..#{to}", options) - left_count = gitaly_commit_client - .commit_count("#{to}..#{from}", options) - - [left_count, right_count] - else - gitaly_commit_client.commit_count(options[:ref], options) - end - end - - def count_commits_by_shelling_out(options) - cmd = count_commits_shelling_command(options) - - raw_output, _status = run_git(cmd) - - process_count_commits_raw_output(raw_output, options) - end - - def count_commits_shelling_command(options) - cmd = %w[rev-list] - cmd << "--after=#{options[:after].iso8601}" if options[:after] - cmd << "--before=#{options[:before].iso8601}" if options[:before] - cmd << "--max-count=#{options[:max_count]}" if options[:max_count] - cmd << "--left-right" if options[:left_right] - cmd << '--count' - - cmd << if options[:all] - '--all' - elsif options[:ref] - options[:ref] - else - raise ArgumentError, "Please specify a valid ref or set the 'all' attribute to true" - end - - cmd += %W[-- #{options[:path]}] if options[:path].present? - cmd - end - - def process_count_commits_raw_output(raw_output, options) - if options[:left_right] - result = raw_output.scan(/\d+/).map(&:to_i) - - if result.sum != options[:max_count] - result - else # Reaching max count, right is not accurate - right_option = - process_count_commits_options(options - .except(:left_right, :from, :to) - .merge(ref: options[:to])) - - right = count_commits_by_shelling_out(right_option) - - [result.first, right] # left should be accurate in the first call - end - else - raw_output.to_i - end - end - def gitaly_ls_files(ref) gitaly_commit_client.ls_files(ref) end @@ -2486,16 +2279,6 @@ module Gitlab nil end - def rugged_commit_count(ref) - walker = Rugged::Walker.new(rugged) - walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE) - oid = rugged.rev_parse_oid(ref) - walker.push(oid) - walker.count - rescue Rugged::ReferenceError - 0 - end - def rev_list_param(spec) spec == :all ? ['--all'] : spec end diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index 4e661eceffb..5fdad077eea 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -1,5 +1,3 @@ -# Gitaly note: JV: will probably be migrated indirectly by migrating the call sites. - module Gitlab module Git class RevList @@ -45,13 +43,6 @@ module Gitlab &lazy_block) end - # This methods returns an array of missed references - # - # Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348. - def missed_ref - repository.missed_ref(oldrev, newrev).split("\n") - end - private def execute(args) diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index 1ab8c4e0229..8ee46b59830 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -27,63 +27,38 @@ module Gitlab end def write_page(name, format, content, commit_details) - @repository.gitaly_migrate(:wiki_write_page) do |is_enabled| - if is_enabled - gitaly_write_page(name, format, content, commit_details) - else - gollum_write_page(name, format, content, commit_details) - end + @repository.wrapped_gitaly_errors do + gitaly_write_page(name, format, content, commit_details) end end def delete_page(page_path, commit_details) - @repository.gitaly_migrate(:wiki_delete_page) do |is_enabled| - if is_enabled - gitaly_delete_page(page_path, commit_details) - else - gollum_delete_page(page_path, commit_details) - end + @repository.wrapped_gitaly_errors do + gitaly_delete_page(page_path, commit_details) end end def update_page(page_path, title, format, content, commit_details) - @repository.gitaly_migrate(:wiki_update_page) do |is_enabled| - if is_enabled - gitaly_update_page(page_path, title, format, content, commit_details) - else - gollum_update_page(page_path, title, format, content, commit_details) - end + @repository.wrapped_gitaly_errors do + gitaly_update_page(page_path, title, format, content, commit_details) end end def pages(limit: nil) - @repository.gitaly_migrate(:wiki_get_all_pages) do |is_enabled| - if is_enabled - gitaly_get_all_pages - else - gollum_get_all_pages(limit: limit) - end + @repository.wrapped_gitaly_errors do + gitaly_get_all_pages end end def page(title:, version: nil, dir: nil) - @repository.gitaly_migrate(:wiki_find_page, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_find_page(title: title, version: version, dir: dir) - else - gollum_find_page(title: title, version: version, dir: dir) - end + @repository.wrapped_gitaly_errors do + gitaly_find_page(title: title, version: version, dir: dir) end end def file(name, version) - @repository.gitaly_migrate(:wiki_find_file) do |is_enabled| - if is_enabled - gitaly_find_file(name, version) - else - gollum_find_file(name, version) - end + @repository.wrapped_gitaly_errors do + gitaly_find_file(name, version) end end @@ -92,24 +67,15 @@ module Gitlab # :per_page - The number of items per page. # :limit - Total number of items to return. def page_versions(page_path, options = {}) - @repository.gitaly_migrate(:wiki_page_versions) do |is_enabled| - if is_enabled - versions = gitaly_wiki_client.page_versions(page_path, options) - - # Gitaly uses gollum-lib to get the versions. Gollum defaults to 20 - # per page, but also fetches 20 if `limit` or `per_page` < 20. - # Slicing returns an array with the expected number of items. - slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page - versions[0..slice_bound] - else - current_page = gollum_page_by_path(page_path) - - commits_from_page(current_page, options).map do |gitlab_git_commit| - gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id) - Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format) - end - end + versions = @repository.wrapped_gitaly_errors do + gitaly_wiki_client.page_versions(page_path, options) end + + # Gitaly uses gollum-lib to get the versions. Gollum defaults to 20 + # per page, but also fetches 20 if `limit` or `per_page` < 20. + # Slicing returns an array with the expected number of items. + slice_bound = options[:limit] || options[:per_page] || Gollum::Page.per_page + versions[0..slice_bound] end def count_page_versions(page_path) @@ -131,46 +97,13 @@ module Gitlab def page_formatted_data(title:, dir: nil, version: nil) version = version&.id - @repository.gitaly_migrate(:wiki_page_formatted_data, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_wiki_client.get_formatted_data(title: title, dir: dir, version: version) - else - # We don't use #page because if wiki_find_page feature is enabled, we would - # get a page without formatted_data. - gollum_find_page(title: title, dir: dir, version: version)&.formatted_data - end + @repository.wrapped_gitaly_errors do + gitaly_wiki_client.get_formatted_data(title: title, dir: dir, version: version) end end - def gollum_wiki - @gollum_wiki ||= Gollum::Wiki.new(@repository.path) - end - private - # options: - # :page - The Integer page number. - # :per_page - The number of items per page. - # :limit - Total number of items to return. - def commits_from_page(gollum_page, options = {}) - unless options[:limit] - options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page - options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i - end - - @repository.log(ref: gollum_page.last_version.id, - path: gollum_page.path, - limit: options[:limit], - offset: options[:offset]) - end - - def gollum_page_by_path(page_path) - page_name = Gollum::Page.canonicalize_filename(page_path) - page_dir = File.split(page_path).first - - gollum_wiki.paged(page_name, page_dir) - end - def new_page(gollum_page) Gitlab::Git::WikiPage.new(gollum_page, new_version(gollum_page, gollum_page.version.id)) end @@ -199,65 +132,6 @@ module Gitlab @gitaly_wiki_client ||= Gitlab::GitalyClient::WikiService.new(@repository) end - def gollum_write_page(name, format, content, commit_details) - assert_type!(format, Symbol) - assert_type!(commit_details, CommitDetails) - - with_committer_with_hooks(commit_details) do |committer| - filename = File.basename(name) - dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir - - gollum_wiki.write_page(filename, format, content, { committer: committer }, dir) - end - rescue Gollum::DuplicatePageError => e - raise Gitlab::Git::Wiki::DuplicatePageError, e.message - end - - def gollum_delete_page(page_path, commit_details) - assert_type!(commit_details, CommitDetails) - - with_committer_with_hooks(commit_details) do |committer| - gollum_wiki.delete_page(gollum_page_by_path(page_path), committer: committer) - end - end - - def gollum_update_page(page_path, title, format, content, commit_details) - assert_type!(format, Symbol) - assert_type!(commit_details, CommitDetails) - - with_committer_with_hooks(commit_details) do |committer| - page = gollum_page_by_path(page_path) - # Instead of performing two renames if the title has changed, - # the update_page will only update the format and content and - # the rename_page will do anything related to moving/renaming - gollum_wiki.update_page(page, page.name, format, content, committer: committer) - gollum_wiki.rename_page(page, title, committer: committer) - end - end - - def gollum_find_page(title:, version: nil, dir: nil) - if version - version = Gitlab::Git::Commit.find(@repository, version).id - end - - gollum_page = gollum_wiki.page(title, version, dir) - return unless gollum_page - - new_page(gollum_page) - end - - def gollum_find_file(name, version) - version ||= self.class.default_ref - gollum_file = gollum_wiki.file(name, version) - return unless gollum_file - - Gitlab::Git::WikiFile.new(gollum_file) - end - - def gollum_get_all_pages(limit: nil) - gollum_wiki.pages(limit: limit).map { |gollum_page| new_page(gollum_page) } - end - def gitaly_write_page(name, format, content, commit_details) gitaly_wiki_client.write_page(name, format, content, commit_details) end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 36e9adf27da..620362b52a9 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -33,11 +33,6 @@ module Gitlab MAXIMUM_GITALY_CALLS = 35 CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze - # We have a mechanism to let GitLab automatically opt in to all Gitaly - # features. We want to be able to exclude some features from automatic - # opt-in. That is what EXPLICIT_OPT_IN_REQUIRED is for. - EXPLICIT_OPT_IN_REQUIRED = [Gitlab::GitalyClient::StorageSettings::DISK_ACCESS_DENIED_FLAG].freeze - MUTEX = Mutex.new class << self @@ -249,7 +244,7 @@ module Gitlab when MigrationStatus::OPT_OUT true when MigrationStatus::OPT_IN - opt_into_all_features? && !EXPLICIT_OPT_IN_REQUIRED.include?(feature_name) + opt_into_all_features? && !explicit_opt_in_required.include?(feature_name) else false end @@ -259,6 +254,13 @@ module Gitlab false end + # We have a mechanism to let GitLab automatically opt in to all Gitaly + # features. We want to be able to exclude some features from automatic + # opt-in. This function has an override in EE. + def self.explicit_opt_in_required + [] + end + # opt_into_all_features? returns true when the current environment # is one in which we opt into features automatically def self.opt_into_all_features? diff --git a/lib/gitlab/health_checks/db_check.rb b/lib/gitlab/health_checks/db_check.rb index e27e16ddaf6..08495c0a59e 100644 --- a/lib/gitlab/health_checks/db_check.rb +++ b/lib/gitlab/health_checks/db_check.rb @@ -17,7 +17,7 @@ module Gitlab def check catch_timeout 10.seconds do if Gitlab::Database.postgresql? - ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping') + ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping')&.to_s else ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.first&.to_s end diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index 3772ef11c7f..343487bc361 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -21,7 +21,8 @@ module Gitlab 'nl_NL' => 'Nederlands', 'tr_TR' => 'Türkçe', 'id_ID' => 'Bahasa Indonesia', - 'fil_PH' => 'Filipino' + 'fil_PH' => 'Filipino', + 'pl_PL' => 'Polski' }.freeze def available_locales diff --git a/lib/gitlab/i18n/metadata_entry.rb b/lib/gitlab/i18n/metadata_entry.rb index 35d57459a3d..36fc1bcdcb7 100644 --- a/lib/gitlab/i18n/metadata_entry.rb +++ b/lib/gitlab/i18n/metadata_entry.rb @@ -3,16 +3,25 @@ module Gitlab class MetadataEntry attr_reader :entry_data + # Avoid testing too many plurals if `nplurals` was incorrectly set. + # Based on info on https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html + # which mentions special cases for numbers ending in 2 digits + MAX_FORMS_TO_TEST = 101 + def initialize(entry_data) @entry_data = entry_data end - def expected_plurals + def expected_forms return nil unless plural_information plural_information['nplurals'].to_i end + def forms_to_test + @forms_to_test ||= [expected_forms, MAX_FORMS_TO_TEST].compact.min + end + private def plural_information diff --git a/lib/gitlab/i18n/po_linter.rb b/lib/gitlab/i18n/po_linter.rb index 7d3ff8c7f58..d8e7269a2c2 100644 --- a/lib/gitlab/i18n/po_linter.rb +++ b/lib/gitlab/i18n/po_linter.rb @@ -1,6 +1,8 @@ module Gitlab module I18n class PoLinter + include Gitlab::Utils::StrongMemoize + attr_reader :po_path, :translation_entries, :metadata_entry, :locale VARIABLE_REGEX = /%{\w*}|%[a-z]/.freeze @@ -34,7 +36,7 @@ module Gitlab end @translation_entries = entries.map do |entry_data| - Gitlab::I18n::TranslationEntry.new(entry_data, metadata_entry.expected_plurals) + Gitlab::I18n::TranslationEntry.new(entry_data, metadata_entry.expected_forms) end nil @@ -48,7 +50,7 @@ module Gitlab translation_entries.each do |entry| errors_for_entry = validate_entry(entry) - errors[join_message(entry.msgid)] = errors_for_entry if errors_for_entry.any? + errors[entry.msgid] = errors_for_entry if errors_for_entry.any? end errors @@ -62,6 +64,7 @@ module Gitlab validate_newlines(errors, entry) validate_number_of_plurals(errors, entry) validate_unescaped_chars(errors, entry) + validate_translation(errors, entry) errors end @@ -81,35 +84,39 @@ module Gitlab end def validate_number_of_plurals(errors, entry) - return unless metadata_entry&.expected_plurals + return unless metadata_entry&.expected_forms return unless entry.translated? - if entry.has_plural? && entry.all_translations.size != metadata_entry.expected_plurals - errors << "should have #{metadata_entry.expected_plurals} "\ - "#{'translations'.pluralize(metadata_entry.expected_plurals)}" + if entry.has_plural? && entry.all_translations.size != metadata_entry.expected_forms + errors << "should have #{metadata_entry.expected_forms} "\ + "#{'translations'.pluralize(metadata_entry.expected_forms)}" end end def validate_newlines(errors, entry) - if entry.msgid_contains_newlines? + if entry.msgid_has_multiple_lines? errors << 'is defined over multiple lines, this breaks some tooling.' end - if entry.plural_id_contains_newlines? + if entry.plural_id_has_multiple_lines? errors << 'plural is defined over multiple lines, this breaks some tooling.' end - if entry.translations_contain_newlines? + if entry.translations_have_multiple_lines? errors << 'has translations defined over multiple lines, this breaks some tooling.' end end def validate_variables(errors, entry) if entry.has_singular_translation? + validate_variables_in_message(errors, entry.msgid, entry.msgid) + validate_variables_in_message(errors, entry.msgid, entry.singular_translation) end if entry.has_plural? + validate_variables_in_message(errors, entry.plural_id, entry.plural_id) + entry.plural_translations.each do |translation| validate_variables_in_message(errors, entry.plural_id, translation) end @@ -117,41 +124,98 @@ module Gitlab end def validate_variables_in_message(errors, message_id, message_translation) - message_id = join_message(message_id) required_variables = message_id.scan(VARIABLE_REGEX) validate_unnamed_variables(errors, required_variables) - validate_translation(errors, message_id, required_variables) validate_variable_usage(errors, message_translation, required_variables) end - def validate_translation(errors, message_id, used_variables) + def validate_translation(errors, entry) + Gitlab::I18n.with_locale(locale) do + if entry.has_plural? + translate_plural(entry) + else + translate_singular(entry) + end + end + + # `sprintf` could raise an `ArgumentError` when invalid passing something + # other than a Hash when using named variables + # + # `sprintf` could raise `TypeError` when passing a wrong type when using + # unnamed variables + # + # FastGettext::Translation could raise `RuntimeError` (raised as a string), + # or as subclassess `NoTextDomainConfigured` & `InvalidFormat` + # + # `FastGettext::Translation` could raise `ArgumentError` as subclassess + # `InvalidEncoding`, `IllegalSequence` & `InvalidCharacter` + rescue ArgumentError, TypeError, RuntimeError => e + errors << "Failure translating to #{locale}: #{e.message}" + end + + def translate_singular(entry) + used_variables = entry.msgid.scan(VARIABLE_REGEX) variables = fill_in_variables(used_variables) - begin - Gitlab::I18n.with_locale(locale) do - translated = if message_id.include?('|') - FastGettext::Translation.s_(message_id) - else - FastGettext::Translation._(message_id) - end + translation = if entry.msgid.include?('|') + FastGettext::Translation.s_(entry.msgid) + else + FastGettext::Translation._(entry.msgid) + end - translated % variables + translation % variables if used_variables.any? + end + + def translate_plural(entry) + used_variables = entry.plural_id.scan(VARIABLE_REGEX) + variables = fill_in_variables(used_variables) + + numbers_covering_all_plurals.map do |number| + translation = FastGettext::Translation.n_(entry.msgid, entry.plural_id, number) + + translation % variables if used_variables.any? + end + end + + def numbers_covering_all_plurals + @numbers_covering_all_plurals ||= calculate_numbers_covering_all_plurals + end + + def calculate_numbers_covering_all_plurals + required_numbers = [] + discovered_indexes = [] + counter = 0 + + while discovered_indexes.size < metadata_entry.forms_to_test && counter < Gitlab::I18n::MetadataEntry::MAX_FORMS_TO_TEST + index_for_count = index_for_pluralization(counter) + + unless discovered_indexes.include?(index_for_count) + discovered_indexes << index_for_count + required_numbers << counter end - # `sprintf` could raise an `ArgumentError` when invalid passing something - # other than a Hash when using named variables - # - # `sprintf` could raise `TypeError` when passing a wrong type when using - # unnamed variables - # - # FastGettext::Translation could raise `RuntimeError` (raised as a string), - # or as subclassess `NoTextDomainConfigured` & `InvalidFormat` - # - # `FastGettext::Translation` could raise `ArgumentError` as subclassess - # `InvalidEncoding`, `IllegalSequence` & `InvalidCharacter` - rescue ArgumentError, TypeError, RuntimeError => e - errors << "Failure translating to #{locale} with #{variables}: #{e.message}" + counter += 1 + end + + required_numbers + end + + def index_for_pluralization(counter) + # This calls the C function that defines the pluralization rule, it can + # return a boolean (`false` represents 0, `true` represents 1) or an integer + # that specifies the plural form to be used for the given number + pluralization_result = Gitlab::I18n.with_locale(locale) do + FastGettext.pluralisation_rule.call(counter) + end + + case pluralization_result + when false + 0 + when true + 1 + else + pluralization_result end end @@ -172,14 +236,18 @@ module Gitlab end def validate_unnamed_variables(errors, variables) - if variables.size > 1 && variables.any? { |variable_name| unnamed_variable?(variable_name) } + unnamed_variables, named_variables = variables.partition { |name| unnamed_variable?(name) } + + if unnamed_variables.any? && named_variables.any? + errors << 'is combining named variables with unnamed variables' + end + + if unnamed_variables.size > 1 errors << 'is combining multiple unnamed variables' end end def validate_variable_usage(errors, translation, required_variables) - translation = join_message(translation) - # We don't need to validate when the message is empty. # In this case we fall back to the default, which has all the the # required variables. @@ -205,10 +273,6 @@ module Gitlab def validate_flags(errors, entry) errors << "is marked #{entry.flag}" if entry.flag end - - def join_message(message) - Array(message).join - end end end end diff --git a/lib/gitlab/i18n/translation_entry.rb b/lib/gitlab/i18n/translation_entry.rb index e6c95afca7e..54adb98f42d 100644 --- a/lib/gitlab/i18n/translation_entry.rb +++ b/lib/gitlab/i18n/translation_entry.rb @@ -11,11 +11,11 @@ module Gitlab end def msgid - entry_data[:msgid] + @msgid ||= Array(entry_data[:msgid]).join end def plural_id - entry_data[:msgid_plural] + @plural_id ||= Array(entry_data[:msgid_plural]).join end def has_plural? @@ -23,12 +23,11 @@ module Gitlab end def singular_translation - all_translations.first if has_singular_translation? + all_translations.first.to_s if has_singular_translation? end def all_translations - @all_translations ||= entry_data.fetch_values(*translation_keys) - .reject(&:empty?) + @all_translations ||= translation_entries.map { |translation| Array(translation).join } end def translated? @@ -54,16 +53,16 @@ module Gitlab nplurals > 1 || !has_plural? end - def msgid_contains_newlines? - msgid.is_a?(Array) + def msgid_has_multiple_lines? + entry_data[:msgid].is_a?(Array) end - def plural_id_contains_newlines? - plural_id.is_a?(Array) + def plural_id_has_multiple_lines? + entry_data[:msgid_plural].is_a?(Array) end - def translations_contain_newlines? - all_translations.any? { |translation| translation.is_a?(Array) } + def translations_have_multiple_lines? + translation_entries.any? { |translation| translation.is_a?(Array) } end def msgid_contains_unescaped_chars? @@ -84,6 +83,11 @@ module Gitlab private + def translation_entries + @translation_entries ||= entry_data.fetch_values(*translation_keys) + .reject(&:empty?) + end + def translation_keys @translation_keys ||= entry_data.keys.select { |key| key.to_s =~ /\Amsgstr(\[\d+\])?\z/ } end diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index e5191f5c7f9..61653044433 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -30,6 +30,7 @@ module Gitlab dashboard deploy.html explore + favicon.ico favicon.png files groups diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb index 18540e64d4c..ecff6ab5d5e 100644 --- a/lib/gitlab/profiler.rb +++ b/lib/gitlab/profiler.rb @@ -11,6 +11,7 @@ module Gitlab lib/gitlab/etag_caching/ lib/gitlab/metrics/ lib/gitlab/middleware/ + ee/lib/gitlab/middleware/ lib/gitlab/performance_bar/ lib/gitlab/request_profiler/ lib/gitlab/profiler.rb @@ -98,11 +99,7 @@ module Gitlab super - backtrace = Rails.backtrace_cleaner.clean(caller) - - backtrace.each do |caller_line| - next if caller_line.match(Regexp.union(IGNORE_BACKTRACES)) - + Gitlab::Profiler.clean_backtrace(caller).each do |caller_line| stripped_caller_line = caller_line.sub("#{Rails.root}/", '') super(" ↳ #{stripped_caller_line}") @@ -112,6 +109,12 @@ module Gitlab end end + def self.clean_backtrace(backtrace) + Array(Rails.backtrace_cleaner.clean(backtrace)).reject do |line| + line.match(Regexp.union(IGNORE_BACKTRACES)) + end + end + def self.with_custom_logger(logger) original_colorize_logging = ActiveSupport::LogSubscriber.colorize_logging original_activerecord_logger = ActiveRecord::Base.logger diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 4b8aae4f5a2..5cedd9e84c2 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -1,5 +1,4 @@ -# Gitaly note: JV: two sets of straightforward RPC's. 1 Hard RPC: fork_repository. -# SSH key operations are not part of Gitaly so will never be migrated. +# Gitaly note: SSH key operations are not part of Gitaly so will never be migrated. require 'securerandom' @@ -153,8 +152,6 @@ module Gitlab # # Ex. # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873 def mv_repository(storage, path, new_path) return false if path.empty? || new_path.empty? @@ -169,19 +166,11 @@ module Gitlab # # Ex. # fork_repository("nfs-file06", "gitlab/gitlab-ci", "nfs-file07", "new-namespace/gitlab-ci") - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/817 def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path) forked_from_relative_path = "#{forked_from_disk_path}.git" fork_args = [forked_to_storage, "#{forked_to_disk_path}.git"] - gitaly_migrate(:fork_repository, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - GitalyGitlabProjects.new(forked_from_storage, forked_from_relative_path).fork_repository(*fork_args) - else - gitlab_projects(forked_from_storage, forked_from_relative_path).fork_repository(*fork_args) - end - end + GitalyGitlabProjects.new(forked_from_storage, forked_from_relative_path).fork_repository(*fork_args) end # Removes a repository from file system, using rm_diretory which is an alias @@ -193,8 +182,6 @@ module Gitlab # # Ex. # remove_repository("/path/to/storage", "gitlab/gitlab-ci") - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873 def remove_repository(storage, name) return false if name.empty? diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb index da24a36603e..9beb442bfa3 100644 --- a/lib/peek/rblineprof/custom_controller_helpers.rb +++ b/lib/peek/rblineprof/custom_controller_helpers.rb @@ -41,7 +41,7 @@ module Peek ] end.sort_by{ |a,b,c,d,e,f| -f } - output = "<div class='modal-dialog modal-lg'><div class='modal-content'>" + output = "<div class='modal-dialog modal-xl'><div class='modal-content'>" output << "<div class='modal-header'>" output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>" output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>×</span></button>" diff --git a/lib/system_check/orphans/repository_check.rb b/lib/system_check/orphans/repository_check.rb index 5ef0b93ad08..2695c658874 100644 --- a/lib/system_check/orphans/repository_check.rb +++ b/lib/system_check/orphans/repository_check.rb @@ -5,16 +5,18 @@ module SystemCheck attr_accessor :orphans def multi_check - Gitlab.config.repositories.storages.each do |storage_name, repository_storage| - storage_path = repository_storage.legacy_disk_path + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + Gitlab.config.repositories.storages.each do |storage_name, repository_storage| + storage_path = repository_storage.legacy_disk_path - $stdout.puts - $stdout.puts "* Storage: #{storage_name} (#{storage_path})".color(:yellow) + $stdout.puts + $stdout.puts "* Storage: #{storage_name} (#{storage_path})".color(:yellow) - repositories = disk_repositories(storage_path) - orphans = (repositories - fetch_repositories(storage_name)) + repositories = disk_repositories(storage_path) + orphans = (repositories - fetch_repositories(storage_name)) - print_orphans(orphans, storage_name) + print_orphans(orphans, storage_name) + end end end diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb index d268f501b4a..99c9e984107 100644 --- a/lib/system_check/simple_executor.rb +++ b/lib/system_check/simple_executor.rb @@ -43,7 +43,7 @@ module SystemCheck # # @param [SystemCheck::BaseCheck] check_klass def run_check(check_klass) - $stdout.print "#{check_klass.display_name} ... " + print_display_name(check_klass) check = check_klass.new @@ -60,18 +60,18 @@ module SystemCheck end if check.check? - $stdout.puts check_klass.check_pass.color(:green) + print_check_pass(check_klass) else - $stdout.puts check_klass.check_fail.color(:red) + print_check_failure(check_klass) if check.can_repair? $stdout.print 'Trying to fix error automatically. ...' if check.repair! - $stdout.puts 'Success'.color(:green) + print_success return else - $stdout.puts 'Failed'.color(:red) + print_failure end end @@ -83,6 +83,26 @@ module SystemCheck private + def print_display_name(check_klass) + $stdout.print "#{check_klass.display_name} ... " + end + + def print_check_pass(check_klass) + $stdout.puts check_klass.check_pass.color(:green) + end + + def print_check_failure(check_klass) + $stdout.puts check_klass.check_fail.color(:red) + end + + def print_success + $stdout.puts 'Success'.color(:green) + end + + def print_failure + $stdout.puts 'Failed'.color(:red) + end + # Prints header content for the series of checks to be executed for this component # # @param [String] component name of the component relative to the checks being executed diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index 247d7be7d78..d52c419d66b 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -50,6 +50,32 @@ namespace :gettext do end end + task :updated_check do + # Removing all pre-translated files speeds up `gettext:find` as the + # files don't need to be merged. + `rm locale/*/gitlab.po` + + # `gettext:find` writes touches to temp files to `stderr` which would cause + # `static-analysis` to report failures. We can ignore these + silence_stream(STDERR) { Rake::Task['gettext:find'].invoke } + + changed_files = `git diff --name-only`.lines.map(&:strip) + + # reset the locale folder for potential next tasks + `git checkout -- locale` + + if changed_files.include?('locale/gitlab.pot') + raise <<~MSG + Newly translated strings found, please add them to `gitlab.pot` by running: + + bundle exec rake gettext:find; git checkout -- locale/*/gitlab.po; + + Then commit and push the resulting changes to `locale/gitlab.pot`. + + MSG + end + end + def report_errors_for_file(file, errors_for_file) puts "Errors in `#{file}`:" diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake index 8b86a5c72a5..b5a9cddaacb 100644 --- a/lib/tasks/lint.rake +++ b/lib/tasks/lint.rake @@ -27,6 +27,7 @@ unless Rails.env.production? scss_lint flay gettext:lint + gettext:updated_check lint:static_verification ].each do |task| pid = Process.fork do diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index e7aab50e42a..f69d204c579 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -22,3 +22,18 @@ task setup_postgresql: :environment do ProjectNameLowerIndex.new.up AddPathIndexToRedirectRoutes.new.up end + +desc 'GitLab | Generate PostgreSQL Password Hash' +task :postgresql_md5_hash do + require 'digest' + username = ENV.fetch('USERNAME') do |missing| + puts "You must provide an username with '#{missing}' ENV variable" + exit(1) + end + password = ENV.fetch('PASSWORD') do |missing| + puts "You must provide a password with '#{missing}' ENV variable" + exit(1) + end + hash = Digest::MD5.hexdigest("#{password}#{username}") + puts "The MD5 hash of your database password for user: #{username} -> #{hash}" +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e8a5a15f410..db5c183bac3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-06-12 18:57+1000\n" -"PO-Revision-Date: 2018-06-12 18:57+1000\n" +"POT-Creation-Date: 2018-06-13 14:05+0200\n" +"PO-Revision-Date: 2018-06-13 14:05+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -133,12 +133,12 @@ msgid "- show less" msgstr "" msgid "1 %{type} addition" -msgid_plural "%d %{type} additions" +msgid_plural "%{count} %{type} additions" msgstr[0] "" msgstr[1] "" msgid "1 %{type} modification" -msgid_plural "%d %{type} modifications" +msgid_plural "%{count} %{type} modifications" msgstr[0] "" msgstr[1] "" @@ -2252,10 +2252,10 @@ msgstr "" msgid "Gitaly" msgstr "" -msgid "Gitaly|Address" +msgid "Gitaly Servers" msgstr "" -msgid "Gitaly Servers" +msgid "Gitaly|Address" msgstr "" msgid "Go Back" @@ -2419,6 +2419,15 @@ msgstr "" msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>." msgstr "" +msgid "ImageDiffViewer|2-up" +msgstr "" + +msgid "ImageDiffViewer|Onion skin" +msgstr "" + +msgid "ImageDiffViewer|Swipe" +msgstr "" + msgid "Import" msgstr "" diff --git a/package.json b/package.json index 4e1ca905ee8..6a52af10dc3 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "3.0.0", "nodemon": "^1.17.3", - "prettier": "1.11.1", + "prettier": "1.12.1", "webpack-dev-server": "^3.1.4" } } diff --git a/public/favicon.png b/public/favicon.png Binary files differdeleted file mode 100644 index 845e0ec34a5..00000000000 --- a/public/favicon.png +++ /dev/null diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js index 39de77bc333..6e4e36b9b2d 100644 --- a/scripts/frontend/prettier.js +++ b/scripts/frontend/prettier.js @@ -9,6 +9,8 @@ const getStagedFiles = require('./frontend_script_utils').getStagedFiles; const mode = process.argv[2] || 'check'; const shouldSave = mode === 'save' || mode === 'save-all'; const allFiles = mode === 'check-all' || mode === 'save-all'; +let dirPath = process.argv[3] || ''; +if (dirPath && dirPath.charAt(dirPath.length - 1) !== '/') dirPath += '/'; const config = { patterns: ['**/*.js', '**/*.vue', '**/*.scss'], @@ -39,9 +41,10 @@ prettierIgnore.add( const availableExtensions = Object.keys(config.parsers); -console.log(`Loading ${allFiles ? 'All' : 'Staged'} Files ...`); +console.log(`Loading ${allFiles ? 'All' : 'Selected'} Files ...`); -const stagedFiles = allFiles ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`)); +const stagedFiles = + allFiles || dirPath ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`)); if (stagedFiles) { if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) { @@ -60,6 +63,13 @@ if (allFiles) { const patterns = config.patterns; const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`; files = glob.sync(globPattern, { ignore }).filter(f => allFiles || stagedFiles.includes(f)); +} else if (dirPath) { + const ignore = config.ignore; + const patterns = config.patterns.map(item => { + return dirPath + item; + }); + const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`; + files = glob.sync(globPattern, { ignore }); } else { files = stagedFiles.filter(f => availableExtensions.includes(f.split('.').pop())); } @@ -73,12 +83,11 @@ if (!files.length) { console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`); -prettier - .resolveConfig('.') - .then(options => { - console.log('Found options : ', options); - files.forEach(file => { - try { +files.forEach(file => { + try { + prettier + .resolveConfig(file) + .then(options => { const fileExtension = file.split('.').pop(); Object.assign(options, { parser: config.parsers[fileExtension], @@ -101,17 +110,17 @@ prettier } console.log(`Prettify Manually : ${file}`); } - } catch (error) { - didError = true; - console.log(`\n\nError with ${file}: ${error.message}`); - } - }); - - if (didWarn || didError) { - process.exit(1); - } - }) - .catch(e => { - console.log(`Error on loading the Config File: ${e.message}`); - process.exit(1); - }); + }) + .catch(e => { + console.log(`Error on loading the Config File: ${e.message}`); + process.exit(1); + }); + } catch (error) { + didError = true; + console.log(`\n\nError with ${file}: ${error.message}`); + } +}); + +if (didWarn || didError) { + process.exit(1); +} diff --git a/scripts/trigger-build b/scripts/trigger-build new file mode 100755 index 00000000000..526f5164ede --- /dev/null +++ b/scripts/trigger-build @@ -0,0 +1,181 @@ +#!/usr/bin/env ruby + +require 'net/http' +require 'json' +require 'cgi' + +module Trigger + OMNIBUS_PROJECT_PATH = 'gitlab-org/omnibus-gitlab'.freeze + CNG_PROJECT_PATH = 'gitlab-org/build/CNG'.freeze + TOKEN = ENV['BUILD_TRIGGER_TOKEN'] + + def self.ee? + ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md') + end + + class Omnibus + def initialize + @uri = URI("https://gitlab.com/api/v4/projects/#{CGI.escape(Trigger::OMNIBUS_PROJECT_PATH)}/trigger/pipeline") + @params = env_params.merge(file_params).merge(token: Trigger::TOKEN) + end + + def invoke! + res = Net::HTTP.post_form(@uri, @params) + id = JSON.parse(res.body)['id'] + project = Trigger::OMNIBUS_PROJECT_PATH + + if id + puts "Triggered https://gitlab.com/#{project}/pipelines/#{id}" + puts "Waiting for downstream pipeline status" + else + raise "Trigger failed! The response from the trigger is: #{res.body}" + end + + Trigger::Pipeline.new(project, id) + end + + private + + def env_params + { + "ref" => ENV["OMNIBUS_BRANCH"] || "master", + "variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"], + "variables[ALTERNATIVE_SOURCES]" => true, + "variables[ee]" => Trigger.ee? ? 'true' : 'false', + "variables[TRIGGERED_USER]" => ENV["GITLAB_USER_NAME"], + "variables[TRIGGER_SOURCE]" => "https://gitlab.com/gitlab-org/#{ENV['CI_PROJECT_NAME']}/-/jobs/#{ENV['CI_JOB_ID']}" + } + end + + def file_params + Hash.new.tap do |params| + Dir.glob("*_VERSION").each do |version_file| + params["variables[#{version_file}]"] = File.read(version_file).strip + end + end + end + end + + class CNG + def initialize + @uri = URI("https://gitlab.com/api/v4/projects/#{CGI.escape(Trigger::CNG_PROJECT_PATH)}/trigger/pipeline") + @ref_name = ENV['CI_COMMIT_REF_NAME'] + @username = ENV['GITLAB_USER_NAME'] + @project_name = ENV['CI_PROJECT_NAME'] + @job_id = ENV['CI_JOB_ID'] + @params = env_params.merge(file_params).merge(token: Trigger::TOKEN) + end + + # + # Trigger a pipeline + # + def invoke! + res = Net::HTTP.post_form(@uri, @params) + id = JSON.parse(res.body)['id'] + project = Trigger::CNG_PROJECT_PATH + + if id + puts "Triggered https://gitlab.com/#{project}/pipelines/#{id}" + puts "Waiting for downstream pipeline status" + else + raise "Trigger failed! The response from the trigger is: #{res.body}" + end + + Trigger::Pipeline.new(project, id) + end + + private + + def env_params + params = { + "ref" => ENV["CNG_BRANCH"] || "master", + "variables[TRIGGERED_USER]" => @username, + "variables[TRIGGER_SOURCE]" => "https://gitlab.com/gitlab-org/#{@project_name}/-/jobs/#{@job_id}" + } + + if Trigger.ee? + params["variables[GITLAB_EE_VERSION]"] = @ref_name + params["variables[EE_PIPELINE]"] = 'true' + else + params["variables[GITLAB_CE_VERSION]"] = @ref_name + params["variables[CE_PIPELINE]"] = 'true' + end + + params + end + + # Read version files from all components + def file_params + Dir.glob("*_VERSION").each_with_object({}) do |version_file, params| + raw_version = File.read(version_file).strip + # if the version matches semver format, treat it as a tag and prepend `v` + version = if raw_version =~ Regexp.compile(/^\d+\.\d+\.\d+(-rc\d+)?(-ee)?$/) + "v#{raw_version}" + else + raw_version + end + + params["variables[#{version_file}]"] = version + end + end + end + + class Pipeline + INTERVAL = 60 # seconds + MAX_DURATION = 3600 * 3 # 3 hours + + def initialize(project, id) + @start = Time.now.to_i + @uri = URI("https://gitlab.com/api/v4/projects/#{CGI.escape(project)}/pipelines/#{id}") + end + + def wait! + loop do + raise "Pipeline timed out after waiting for #{duration} minutes!" if timeout? + + case status + when :created, :pending, :running + print "." + sleep INTERVAL + when :success + puts "Pipeline succeeded in #{duration} minutes!" + break + else + raise "Pipeline did not succeed!" + end + + STDOUT.flush + end + end + + def timeout? + Time.now.to_i > (@start + MAX_DURATION) + end + + def duration + (Time.now.to_i - @start) / 60 + end + + def status + req = Net::HTTP::Get.new(@uri) + req['PRIVATE-TOKEN'] = ENV['GITLAB_QA_ACCESS_TOKEN'] + + res = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: true) do |http| + http.request(req) + end + + JSON.parse(res.body)['status'].to_s.to_sym + end + end +end + +case ARGV[0] +when 'omnibus' + Trigger::Omnibus.new.invoke!.wait! +when 'cng' + Trigger::CNG.new.invoke!.wait! +else + puts "Please provide a valid option: + omnibus - Triggers a pipeline that builds the omnibus-gitlab package + cng - Triggers a pipeline that builds images used by the GitLab helm chart" +end diff --git a/scripts/trigger-build-cloud-native b/scripts/trigger-build-cloud-native deleted file mode 100755 index b6ca75a588d..00000000000 --- a/scripts/trigger-build-cloud-native +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env ruby - -require 'gitlab' - -# -# Configure credentials to be used with gitlab gem -# -Gitlab.configure do |config| - config.endpoint = 'https://gitlab.com/api/v4' -end - -# -# The remote project -# -GITLAB_CNG_REPO = 'gitlab-org/build/CNG'.freeze - -def ee? - ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md') -end - -def read_file_version(filename) - raw_version = File.read(filename).strip - - # if the version matches semver format, treat it as a tag and prepend `v` - if raw_version =~ Regexp.compile(/^\d+\.\d+\.\d+(-rc\d+)?(-ee)?$/) - "v#{raw_version}" - else - raw_version - end -end - -def params - params = { - 'GITLAB_SHELL_VERSION' => read_file_version('GITLAB_SHELL_VERSION'), - 'GITALY_VERSION' => read_file_version('GITALY_SERVER_VERSION'), - 'TRIGGERED_USER' => ENV['GITLAB_USER_NAME'], - 'TRIGGER_SOURCE' => "https://gitlab.com/gitlab-org/#{ENV['CI_PROJECT_NAME']}/-/jobs/#{ENV['CI_JOB_ID']}" - } - - if ee? - params['EE_PIPELINE'] = 'true' - params['GITLAB_EE_VERSION'] = ENV['CI_COMMIT_REF_NAME'] - else - params['CE_PIPELINE'] = 'true' - params['GITLAB_CE_VERSION'] = ENV['CI_COMMIT_REF_NAME'] - end - - params -end - -# -# Trigger a pipeline -# -def trigger_pipeline - # Create the cross project pipeline using CI_JOB_TOKEN - pipeline = Gitlab.run_trigger(GITLAB_CNG_REPO, ENV['CI_JOB_TOKEN'], 'master', params) - - puts "Triggered https://gitlab.com/#{GITLAB_CNG_REPO}/pipelines/#{pipeline.id}" -end - -trigger_pipeline diff --git a/scripts/trigger-build-omnibus b/scripts/trigger-build-omnibus deleted file mode 100755 index 95f35b44f5a..00000000000 --- a/scripts/trigger-build-omnibus +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env ruby - -require 'net/http' -require 'json' -require 'cgi' - -module Omnibus - PROJECT_PATH = 'gitlab-org/omnibus-gitlab'.freeze - - class Trigger - TOKEN = ENV['BUILD_TRIGGER_TOKEN'] - TRIGGERER = ENV['CI_PROJECT_NAME'] - - def initialize - @uri = URI("https://gitlab.com/api/v4/projects/#{CGI.escape(Omnibus::PROJECT_PATH)}/trigger/pipeline") - @params = env_params.merge(file_params).merge(token: TOKEN) - end - - def invoke! - res = Net::HTTP.post_form(@uri, @params) - id = JSON.parse(res.body)['id'] - - if id - puts "Triggered https://gitlab.com/#{Omnibus::PROJECT_PATH}/pipelines/#{id}" - puts "Waiting for downstream pipeline status" - else - raise "Trigger failed! The response from the trigger is: #{res.body}" - end - - Omnibus::Pipeline.new(id) - end - - private - - def ee? - TRIGGERER == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md') - end - - def env_params - { - "ref" => ENV["OMNIBUS_BRANCH"] || "master", - "variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"], - "variables[ALTERNATIVE_SOURCES]" => true, - "variables[ee]" => ee? ? 'true' : 'false', - "variables[TRIGGERED_USER]" => ENV["GITLAB_USER_NAME"], - "variables[TRIGGER_SOURCE]" => "https://gitlab.com/gitlab-org/#{ENV['CI_PROJECT_NAME']}/-/jobs/#{ENV['CI_JOB_ID']}" - } - end - - def file_params - Hash.new.tap do |params| - Dir.glob("*_VERSION").each do |version_file| - params["variables[#{version_file}]"] = File.read(version_file).strip - end - end - end - end - - class Pipeline - INTERVAL = 60 # seconds - MAX_DURATION = 3600 * 3 # 3 hours - - def initialize(id) - @start = Time.now.to_i - @uri = URI("https://gitlab.com/api/v4/projects/#{CGI.escape(Omnibus::PROJECT_PATH)}/pipelines/#{id}") - end - - def wait! - loop do - raise "Pipeline timed out after waiting for #{duration} minutes!" if timeout? - - case status - when :created, :pending, :running - print "." - sleep INTERVAL - when :success - puts "Omnibus pipeline succeeded in #{duration} minutes!" - break - else - raise "Omnibus pipeline did not succeed!" - end - - STDOUT.flush - end - end - - def timeout? - Time.now.to_i > (@start + MAX_DURATION) - end - - def duration - (Time.now.to_i - @start) / 60 - end - - def status - req = Net::HTTP::Get.new(@uri) - req['PRIVATE-TOKEN'] = ENV['GITLAB_QA_ACCESS_TOKEN'] - - res = Net::HTTP.start(@uri.hostname, @uri.port, use_ssl: true) do |http| - http.request(req) - end - - JSON.parse(res.body)['status'].to_s.to_sym - end - end -end - -Omnibus::Trigger.new.invoke!.wait! diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index b4fc2aa326f..9d10d725ff3 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -73,7 +73,7 @@ describe Admin::ApplicationSettingsController do end it 'updates the restricted_visibility_levels when empty array is passed' do - put :update, application_setting: { restricted_visibility_levels: [] } + put :update, application_setting: { restricted_visibility_levels: [""] } expect(response).to redirect_to(admin_application_settings_path) expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index fbafb4a4de8..74f362fd7fc 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -512,7 +512,7 @@ describe ApplicationController do context '422 errors' do it 'logs a response with a string' do - response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json') + response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index @@ -521,7 +521,7 @@ describe ApplicationController do it 'logs a response with an array' do body = ['I want', 'my hat back'] - response = spy(ActionDispatch::Response, status: 422, body: body, content_type: 'application/json') + response = spy(ActionDispatch::Response, status: 422, body: body, content_type: 'application/json', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index @@ -529,7 +529,7 @@ describe ApplicationController do end it 'does not log a string with an empty body' do - response = spy(ActionDispatch::Response, status: 422, body: nil, content_type: 'application/json') + response = spy(ActionDispatch::Response, status: 422, body: nil, content_type: 'application/json', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index @@ -537,7 +537,7 @@ describe ApplicationController do end it 'does not log an HTML body' do - response = spy(ActionDispatch::Response, status: 422, body: 'This is a test', content_type: 'application/html') + response = spy(ActionDispatch::Response, status: 422, body: 'This is a test', content_type: 'application/html', cookies: {}) allow(controller).to receive(:response).and_return(response) get :index diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb index a0ee13b2352..7e23b56356e 100644 --- a/spec/controllers/concerns/internal_redirect_spec.rb +++ b/spec/controllers/concerns/internal_redirect_spec.rb @@ -54,6 +54,31 @@ describe InternalRedirect do end end + describe '#sanitize_redirect' do + let(:valid_path) { '/hello/world?hello=world' } + let(:valid_url) { "http://test.host#{valid_path}" } + + it 'returns `nil` for invalid paths' do + invalid_path = '//not/valid' + + expect(controller.sanitize_redirect(invalid_path)).to eq nil + end + + it 'returns `nil` for invalid urls' do + input = 'http://test.host:3000/invalid' + + expect(controller.sanitize_redirect(input)).to eq nil + end + + it 'returns input for valid paths' do + expect(controller.sanitize_redirect(valid_path)).to eq valid_path + end + + it 'returns path for valid urls' do + expect(controller.sanitize_redirect(valid_url)).to eq valid_path + end + end + describe '#host_allowed?' do it 'allows uris with the same host and port' do expect(controller.host_allowed?(URI('http://test.host/test'))).to be(true) diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 00a7df6ccc8..9e696e9cb29 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -55,6 +55,25 @@ describe Projects::BlobController do expect(json_response).to have_key 'raw_path' end end + + context "with viewer=none" do + let(:id) { 'master/README.md' } + + before do + get(:show, + namespace_id: project.namespace, + project_id: project, + id: id, + format: :json, + viewer: 'none') + end + + it do + expect(response).to be_ok + expect(json_response).not_to have_key 'html' + expect(json_response).to have_key 'raw_path' + end + end end context 'with tree path' do diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb index 011843baffc..812833cc86b 100644 --- a/spec/controllers/projects/imports_controller_spec.rb +++ b/spec/controllers/projects/imports_controller_spec.rb @@ -29,7 +29,7 @@ describe Projects::ImportsController do context 'when import is in progress' do before do - project.update_attribute(:import_status, :started) + project.update_attributes(import_status: :started) end it 'renders template' do @@ -47,7 +47,7 @@ describe Projects::ImportsController do context 'when import failed' do before do - project.update_attribute(:import_status, :failed) + project.update_attributes(import_status: :failed) end it 'redirects to new_namespace_project_import_path' do @@ -59,7 +59,7 @@ describe Projects::ImportsController do context 'when import finished' do before do - project.update_attribute(:import_status, :finished) + project.update_attributes(import_status: :finished) end context 'when project is a fork' do @@ -108,7 +108,7 @@ describe Projects::ImportsController do context 'when import never happened' do before do - project.update_attribute(:import_status, :none) + project.update_attributes(import_status: :none) end it 'redirects to namespace_project_path' do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 22858de0475..a412e74581d 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -234,7 +234,7 @@ describe Projects::MergeRequestsController do body = JSON.parse(response.body) expect(body['assignee'].keys) - .to match_array(%w(name username avatar_url)) + .to match_array(%w(name username avatar_url id state web_url)) end end diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb index 3506305f755..4cdaa54e0bc 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -310,9 +310,19 @@ describe Projects::PipelineSchedulesController do end def go - put :update, namespace_id: project.namespace.to_param, - project_id: project, id: pipeline_schedule, - schedule: schedule + if Gitlab.rails5? + put :update, params: { namespace_id: project.namespace.to_param, + project_id: project, + id: pipeline_schedule, + schedule: schedule }, + as: :html + + else + put :update, namespace_id: project.namespace.to_param, + project_id: project, + id: pipeline_schedule, + schedule: schedule + end end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 5bd22ea803c..705b30f0130 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -296,16 +296,22 @@ describe ProjectsController do shared_examples_for 'updating a project' do context 'when only renaming a project path' do it "sets the repository to the right path after a rename" do - original_repository_path = project.repository.path + original_repository_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.repository.path + end expect { update_project path: 'renamed_path' } .to change { project.reload.path } expect(project.path).to include 'renamed_path' + assign_repository_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + assigns(:repository).path + end + if project.hashed_storage?(:repository) - expect(assigns(:repository).path).to eq(original_repository_path) + expect(assign_repository_path).to eq(original_repository_path) else - expect(assigns(:repository).path).to include(project.path) + expect(assign_repository_path).to include(project.path) end expect(response).to have_gitlab_http_status(302) diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 555b186fe31..2b61e0d4a85 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -257,15 +257,15 @@ describe SessionsController do end end - describe '#new' do + describe "#new" do before do set_devise_mapping(context: @request) end - it 'redirects correctly for referer on same host with params' do - search_path = '/search?search=seed_project' - allow(controller.request).to receive(:referer) - .and_return('http://%{host}%{path}' % { host: 'test.host', path: search_path }) + it "redirects correctly for referer on same host with params" do + host = "test.host" + search_path = "/search?search=seed_project" + request.headers[:HTTP_REFERER] = "http://#{host}#{search_path}" get(:new, redirect_to_referer: :yes) diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 1df2c954893..eb94d395a9e 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -580,23 +580,6 @@ describe UploadsController do expect(response).to have_gitlab_http_status(404) end end - - context 'has a valid filename on the version file' do - it 'successfully returns the file' do - get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'favicon_main_dk.png' - - expect(response).to have_gitlab_http_status(200) - expect(response.header['Content-Disposition']).to end_with 'filename="favicon_main_dk.png"' - end - end - - context 'has an invalid filename on the version file' do - it 'returns a 404' do - get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'favicon_bogusversion_dk.png' - - expect(response).to have_gitlab_http_status(404) - end - end end end end diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb index 5d6cd44ad1c..90d02f7e40f 100644 --- a/spec/features/ics/dashboard_issues_spec.rb +++ b/spec/features/ics/dashboard_issues_spec.rb @@ -11,13 +11,25 @@ describe 'Dashboard Issues Calendar Feed' do end context 'when authenticated' do - it 'renders calendar feed' do - sign_in user - visit issues_dashboard_path(:ics) + context 'with no referer' do + it 'renders calendar feed' do + sign_in user + visit issues_dashboard_path(:ics) - expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') - expect(body).to have_text('BEGIN:VCALENDAR') + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'with GitLab as the referer' do + it 'renders calendar feed as text/plain' do + sign_in user + page.driver.header('Referer', issues_dashboard_url(host: Settings.gitlab.base_url)) + visit issues_dashboard_path(:ics) + + expect(response_headers['Content-Type']).to have_content('text/plain') + expect(body).to have_text('BEGIN:VCALENDAR') + end end end @@ -28,7 +40,6 @@ describe 'Dashboard Issues Calendar Feed' do visit issues_dashboard_path(:ics, private_token: personal_access_token.token) expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') expect(body).to have_text('BEGIN:VCALENDAR') end end @@ -38,7 +49,6 @@ describe 'Dashboard Issues Calendar Feed' do visit issues_dashboard_path(:ics, feed_token: user.feed_token) expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') expect(body).to have_text('BEGIN:VCALENDAR') end end diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb index 0a049be2ffe..24de5b4b7c6 100644 --- a/spec/features/ics/group_issues_spec.rb +++ b/spec/features/ics/group_issues_spec.rb @@ -13,13 +13,25 @@ describe 'Group Issues Calendar Feed' do end context 'when authenticated' do - it 'renders calendar feed' do - sign_in user - visit issues_group_path(group, :ics) + context 'with no referer' do + it 'renders calendar feed' do + sign_in user + visit issues_group_path(group, :ics) - expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') - expect(body).to have_text('BEGIN:VCALENDAR') + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'with GitLab as the referer' do + it 'renders calendar feed as text/plain' do + sign_in user + page.driver.header('Referer', issues_group_url(group, host: Settings.gitlab.base_url)) + visit issues_group_path(group, :ics) + + expect(response_headers['Content-Type']).to have_content('text/plain') + expect(body).to have_text('BEGIN:VCALENDAR') + end end end @@ -30,7 +42,6 @@ describe 'Group Issues Calendar Feed' do visit issues_group_path(group, :ics, private_token: personal_access_token.token) expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') expect(body).to have_text('BEGIN:VCALENDAR') end end @@ -40,7 +51,6 @@ describe 'Group Issues Calendar Feed' do visit issues_group_path(group, :ics, feed_token: user.feed_token) expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') expect(body).to have_text('BEGIN:VCALENDAR') end end diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb index b99e9607f1d..2ca3d52a5be 100644 --- a/spec/features/ics/project_issues_spec.rb +++ b/spec/features/ics/project_issues_spec.rb @@ -12,13 +12,25 @@ describe 'Project Issues Calendar Feed' do end context 'when authenticated' do - it 'renders calendar feed' do - sign_in user - visit project_issues_path(project, :ics) + context 'with no referer' do + it 'renders calendar feed' do + sign_in user + visit project_issues_path(project, :ics) - expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') - expect(body).to have_text('BEGIN:VCALENDAR') + expect(response_headers['Content-Type']).to have_content('text/calendar') + expect(body).to have_text('BEGIN:VCALENDAR') + end + end + + context 'with GitLab as the referer' do + it 'renders calendar feed as text/plain' do + sign_in user + page.driver.header('Referer', project_issues_url(project, host: Settings.gitlab.base_url)) + visit project_issues_path(project, :ics) + + expect(response_headers['Content-Type']).to have_content('text/plain') + expect(body).to have_text('BEGIN:VCALENDAR') + end end end @@ -29,7 +41,6 @@ describe 'Project Issues Calendar Feed' do visit project_issues_path(project, :ics, private_token: personal_access_token.token) expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') expect(body).to have_text('BEGIN:VCALENDAR') end end @@ -39,7 +50,6 @@ describe 'Project Issues Calendar Feed' do visit project_issues_path(project, :ics, feed_token: user.feed_token) expect(response_headers['Content-Type']).to have_content('text/calendar') - expect(response_headers['Content-Disposition']).to have_content('inline') expect(body).to have_text('BEGIN:VCALENDAR') end end diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index cbd0949c192..c8115db9212 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -31,7 +31,7 @@ describe 'Dropdown assignee', :js do describe 'behavior' do it 'opens when the search bar has assignee:' do - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) expect(page).to have_css(js_dropdown_assignee, visible: true) end @@ -44,6 +44,7 @@ describe 'Dropdown assignee', :js do it 'should show loading indicator when opened' do slow_requests do + # We aren't using `input_filtered_search` because we want to see the loading indicator filtered_search.set('assignee:') expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true) @@ -51,19 +52,19 @@ describe 'Dropdown assignee', :js do end it 'should hide loading indicator when loaded' do - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading') end it 'should load all the assignees when opened' do - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) expect(dropdown_assignee_size).to eq(4) end it 'shows current user at top of dropdown' do - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name) end @@ -71,7 +72,7 @@ describe 'Dropdown assignee', :js do describe 'filtering' do before do - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) @@ -79,23 +80,21 @@ describe 'Dropdown assignee', :js do end it 'filters by name' do - filtered_search.send_keys('j') + input_filtered_search('jac', submit: false, extra_space: false) - expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name) end it 'filters by case insensitive name' do - filtered_search.send_keys('J') + input_filtered_search('JAC', submit: false, extra_space: false) - expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name) end it 'filters by username with symbol' do - filtered_search.send_keys('@ot') + input_filtered_search('@ott', submit: false, extra_space: false) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name) @@ -103,7 +102,7 @@ describe 'Dropdown assignee', :js do end it 'filters by case insensitive username with symbol' do - filtered_search.send_keys('@OT') + input_filtered_search('@OTT', submit: false, extra_space: false) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name) @@ -111,7 +110,9 @@ describe 'Dropdown assignee', :js do end it 'filters by username without symbol' do - filtered_search.send_keys('ot') + input_filtered_search('ott', submit: false, extra_space: false) + + wait_for_requests expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name) @@ -119,7 +120,9 @@ describe 'Dropdown assignee', :js do end it 'filters by case insensitive username without symbol' do - filtered_search.send_keys('OT') + input_filtered_search('OTT', submit: false, extra_space: false) + + wait_for_requests expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name) expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name) @@ -129,7 +132,7 @@ describe 'Dropdown assignee', :js do describe 'selecting from dropdown' do before do - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) end it 'fills in the assignee username when the assignee has not been filtered' do @@ -143,7 +146,7 @@ describe 'Dropdown assignee', :js do end it 'fills in the assignee username when the assignee has been filtered' do - filtered_search.send_keys('roo') + input_filtered_search('roo', submit: false, extra_space: false) click_assignee(user.name) wait_for_requests @@ -165,7 +168,7 @@ describe 'Dropdown assignee', :js do describe 'selecting from dropdown without Ajax call' do before do Gitlab::Testing::RequestBlockerMiddleware.block_requests! - filtered_search.set('assignee:') + input_filtered_search('assignee:', submit: false, extra_space: false) end after do @@ -183,31 +186,31 @@ describe 'Dropdown assignee', :js do describe 'input has existing content' do it 'opens assignee dropdown with existing search term' do - filtered_search.set('searchTerm assignee:') + input_filtered_search('searchTerm assignee:', submit: false, extra_space: false) expect(page).to have_css(js_dropdown_assignee, visible: true) end it 'opens assignee dropdown with existing author' do - filtered_search.set('author:@user assignee:') + input_filtered_search('author:@user assignee:', submit: false, extra_space: false) expect(page).to have_css(js_dropdown_assignee, visible: true) end it 'opens assignee dropdown with existing label' do - filtered_search.set('label:~bug assignee:') + input_filtered_search('label:~bug assignee:', submit: false, extra_space: false) expect(page).to have_css(js_dropdown_assignee, visible: true) end it 'opens assignee dropdown with existing milestone' do - filtered_search.set('milestone:%v1.0 assignee:') + input_filtered_search('milestone:%v1.0 assignee:', submit: false, extra_space: false) expect(page).to have_css(js_dropdown_assignee, visible: true) end it 'opens assignee dropdown with existing my-reaction' do - filtered_search.set('my-reaction:star assignee:') + input_filtered_search('my-reaction:star assignee:', submit: false, extra_space: false) expect(page).to have_css(js_dropdown_assignee, visible: true) end @@ -215,8 +218,7 @@ describe 'Dropdown assignee', :js do describe 'caching requests' do it 'caches requests after the first load' do - filtered_search.set('assignee') - filtered_search.send_keys(':') + input_filtered_search('assignee:', submit: false, extra_space: false) initial_size = dropdown_assignee_size expect(initial_size).to be > 0 @@ -224,8 +226,7 @@ describe 'Dropdown assignee', :js do new_user = create(:user) project.add_master(new_user) find('.filtered-search-box .clear-search').click - filtered_search.set('assignee') - filtered_search.send_keys(':') + input_filtered_search('assignee:', submit: false, extra_space: false) expect(dropdown_assignee_size).to eq(initial_size) end diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index dacca494755..17818beb947 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -226,7 +226,9 @@ feature 'Issues > User uses quick actions', :js do it 'does not move the issue' do add_note("/move #{project_unauthorized.full_path}") - expect(page).not_to have_content 'Commands applied' + wait_for_requests + + expect(page).to have_content 'Commands applied' expect(issue.reload).to be_open end end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index 4700ada1aae..5573148f8bc 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -34,7 +34,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - expect(page).to have_selector('span.badge', text: label.title) + expect(page).to have_selector('.badge', text: label.title) end end @@ -45,7 +45,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - expect(page).not_to have_selector('span.badge', text: child_group_label.title) + expect(page).not_to have_selector('.badge', text: child_group_label.title) end end diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index 4d897f09b57..05228e27963 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -502,6 +502,13 @@ describe 'Copy as GFM', :js do 1. Numbered lists GFM + # list item followed by an HR + <<-GFM.strip_heredoc, + - list item + + ----- + GFM + '# Heading', '## Heading', '### Heading', @@ -515,8 +522,6 @@ describe 'Copy as GFM', :js do '~~Strikethrough~~', - '2^2', - '-----', # table diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index c86ba8c50a5..cac8a5068ec 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -44,7 +44,7 @@ describe 'GitLab Markdown', :aggregate_failures do # Shared behavior that all pipelines should exhibit shared_examples 'all pipelines' do - it 'includes Redcarpet extensions' do + it 'includes extensions' do aggregate_failures 'does not parse emphasis inside of words' do expect(doc.to_html).not_to match('foo<em>bar</em>baz') end @@ -72,10 +72,6 @@ describe 'GitLab Markdown', :aggregate_failures do aggregate_failures 'parses strikethroughs' do expect(doc).to have_selector(%{del:contains("and this text doesn't")}) end - - aggregate_failures 'parses superscript' do - expect(doc).to have_selector('sup', count: 2) - end end it 'includes SanitizationFilter' do @@ -123,16 +119,24 @@ describe 'GitLab Markdown', :aggregate_failures do expect(doc).to have_selector('details summary:contains("collapsible")') end - aggregate_failures 'permits style attribute in th elements' do - expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center' - expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right' - expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left' + aggregate_failures 'permits align attribute in th elements' do + expect(doc.at_css('th:contains("Header")')['align']).to eq 'center' + expect(doc.at_css('th:contains("Row")')['align']).to eq 'right' + expect(doc.at_css('th:contains("Example")')['align']).to eq 'left' end - aggregate_failures 'permits style attribute in td elements' do - expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center' - expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right' - expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left' + aggregate_failures 'permits align attribute in td elements' do + expect(doc.at_css('td:contains("Foo")')['align']).to eq 'center' + expect(doc.at_css('td:contains("Bar")')['align']).to eq 'right' + expect(doc.at_css('td:contains("Baz")')['align']).to eq 'left' + end + + aggregate_failures 'permits superscript elements' do + expect(doc).to have_selector('sup', count: 2) + end + + aggregate_failures 'permits subscript elements' do + expect(doc).to have_selector('sub', count: 3) end aggregate_failures 'removes `rel` attribute from links' do @@ -320,6 +324,31 @@ describe 'GitLab Markdown', :aggregate_failures do end end + context 'Redcarpet documents' do + before do + allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet') + @html = markdown(@feat.raw_markdown) + end + + it 'processes certain elements differently' do + aggregate_failures 'parses superscript' do + expect(doc).to have_selector('sup', count: 3) + end + + aggregate_failures 'permits style attribute in th elements' do + expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center' + expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right' + expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left' + end + + aggregate_failures 'permits style attribute in td elements' do + expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center' + expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right' + expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left' + end + end + end + # Fake a `current_user` helper def current_user @feat.user diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb index 43a23c42f83..1552a3512dd 100644 --- a/spec/features/projects/deploy_keys_spec.rb +++ b/spec/features/projects/deploy_keys_spec.rb @@ -22,7 +22,8 @@ describe 'Project deploy keys', :js do accept_confirm { find('.ic-remove').click() } - expect(page).not_to have_selector('.fa-spinner', count: 0) + wait_for_requests + expect(page).to have_selector('.deploy-key', count: 0) end end diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb index c1307ab640f..9bfcb1e816a 100644 --- a/spec/features/projects/diffs/diff_show_spec.rb +++ b/spec/features/projects/diffs/diff_show_spec.rb @@ -166,8 +166,7 @@ feature 'Diff file viewer', :js do context 'expanding the diff' do before do - # We can't use `click_link` because the "link" doesn't have an `href`. - find('a.click-to-expand').click + click_button 'Click to expand it.' wait_for_requests end diff --git a/spec/features/projects/issues/user_comments_on_issue_spec.rb b/spec/features/projects/issues/user_comments_on_issue_spec.rb index c45fdc7642f..353f487485d 100644 --- a/spec/features/projects/issues/user_comments_on_issue_spec.rb +++ b/spec/features/projects/issues/user_comments_on_issue_spec.rb @@ -31,11 +31,14 @@ describe "User comments on issue", :js do end it "adds comment with code block" do - comment = "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```" + code_block_content = "Command [1]: /usr/local/bin/git , see [text](doc/text)" + comment = "```\n#{code_block_content}\n```" add_note(comment) - expect(page).to have_content(comment) + wait_for_requests + + expect(page.find('pre code').text).to eq code_block_content end end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 2dc3c5e3927..f37d8998045 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -36,7 +36,7 @@ feature 'Task Lists' do MARKDOWN end - let(:nested_tasks_markdown) do + let(:nested_tasks_markdown_redcarpet) do <<-EOT.strip_heredoc - [ ] Task a - [x] Task a.1 @@ -49,6 +49,19 @@ feature 'Task Lists' do EOT end + let(:nested_tasks_markdown) do + <<-EOT.strip_heredoc + - [ ] Task a + - [x] Task a.1 + - [ ] Task a.2 + - [ ] Task b + + 1. [ ] Task 1 + 1. [ ] Task 1.1 + 1. [x] Task 1.2 + EOT + end + before do Warden.test_mode! @@ -141,13 +154,11 @@ feature 'Task Lists' do end end - describe 'nested tasks', :js do - let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) } - + shared_examples 'shared nested tasks' do before do + allow(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet') visit_issue(project, issue) end - it 'renders' do expect(page).to have_selector('ul.task-list', count: 2) expect(page).to have_selector('li.task-list-item', count: 7) @@ -171,6 +182,30 @@ feature 'Task Lists' do expect(page).to have_content('marked the task Task 1.1 as complete') end end + + describe 'nested tasks', :js do + context 'with Redcarpet' do + let(:issue) { create(:issue, description: nested_tasks_markdown_redcarpet, author: user, project: project) } + + before do + allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet') + visit_issue(project, issue) + end + + it_behaves_like 'shared nested tasks' + end + + context 'with CommonMark' do + let(:issue) { create(:issue, description: nested_tasks_markdown, author: user, project: project) } + + before do + allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('CommonMark') + visit_issue(project, issue) + end + + it_behaves_like 'shared nested tasks' + end + end end describe 'for Notes' do diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json index f7bc137c90c..cf257ac00de 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_basic.json +++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json @@ -14,7 +14,21 @@ "subscribed": { "type": ["boolean", "null"] }, "participants": { "type": "array" }, "allow_collaboration": { "type": "boolean"}, - "allow_maintainer_to_push": { "type": "boolean"} + "allow_maintainer_to_push": { "type": "boolean"}, + "assignee": { + "oneOf": [ + { "type": "null" }, + { "$ref": "user.json" } + ] + }, + "milestone": { + "type": [ "object", "null" ] + }, + "labels": { + "type": [ "array", "null" ] + }, + "task_status": { "type": "string" }, + "task_status_short": { "type": "string" } }, "additionalProperties": false } diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index da32a46675f..e5d01c3bd03 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -43,8 +43,14 @@ This text says this, ~~and this text doesn't~~. ### Superscript -This is my 1^(st) time using superscript in Markdown. Now this is my -2^(nd). +This is my 1<sup>(st)</sup> time using superscript in Markdown. Now this is my +2<sup>(nd)</sup>. + +Redcarpet supports this superscript syntax ( x^2 ). + +### Subscript + +This (C<sub>6</sub>H<sub>12</sub>O<sub>6</sub>) is an example of subscripts in Markdown. ### Next step diff --git a/spec/graphql/resolvers/merge_request_resolver_spec.rb b/spec/graphql/resolvers/merge_request_resolver_spec.rb index af015533209..73993b3a039 100644 --- a/spec/graphql/resolvers/merge_request_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_request_resolver_spec.rb @@ -10,49 +10,36 @@ describe Resolvers::MergeRequestResolver do set(:other_project) { create(:project, :repository) } set(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) } - let(:full_path) { project.full_path } let(:iid_1) { merge_request_1.iid } let(:iid_2) { merge_request_2.iid } - let(:other_full_path) { other_project.full_path } let(:other_iid) { other_merge_request.iid } describe '#resolve' do it 'batch-resolves merge requests by target project full path and IID' do - path = full_path # avoid database query - result = batch(max_queries: 2) do - [resolve_mr(path, iid_1), resolve_mr(path, iid_2)] + [resolve_mr(project, iid_1), resolve_mr(project, iid_2)] end expect(result).to contain_exactly(merge_request_1, merge_request_2) end it 'can batch-resolve merge requests from different projects' do - path = project.full_path # avoid database queries - other_path = other_full_path - result = batch(max_queries: 3) do - [resolve_mr(path, iid_1), resolve_mr(path, iid_2), resolve_mr(other_path, other_iid)] + [resolve_mr(project, iid_1), resolve_mr(project, iid_2), resolve_mr(other_project, other_iid)] end expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request) end it 'resolves an unknown iid to nil' do - result = batch { resolve_mr(full_path, -1) } - - expect(result).to be_nil - end - - it 'resolves a known iid for an unknown full_path to nil' do - result = batch { resolve_mr('unknown/project', iid_1) } + result = batch { resolve_mr(project, -1) } expect(result).to be_nil end end - def resolve_mr(full_path, iid) - resolve(described_class, args: { full_path: full_path, iid: iid }) + def resolve_mr(project, iid) + resolve(described_class, obj: project, args: { iid: iid }) end end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index e0f89105b86..b4eeca2e3f1 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -2,4 +2,13 @@ require 'spec_helper' describe GitlabSchema.types['Project'] do it { expect(described_class.graphql_name).to eq('Project') } + + describe 'nested merge request' do + it { expect(described_class).to have_graphql_field(:merge_request) } + + it 'authorizes the merge request' do + expect(described_class.fields['mergeRequest']) + .to require_graphql_authorizations(:read_merge_request) + end + end end diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 8488252fd59..e1df6f9811d 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -5,7 +5,7 @@ describe GitlabSchema.types['Query'] do expect(described_class.graphql_name).to eq('Query') end - it { is_expected.to have_graphql_fields(:project, :merge_request, :echo) } + it { is_expected.to have_graphql_fields(:project, :echo) } describe 'project field' do subject { described_class.fields['project'] } @@ -20,18 +20,4 @@ describe GitlabSchema.types['Query'] do is_expected.to require_graphql_authorizations(:read_project) end end - - describe 'merge_request field' do - subject { described_class.fields['mergeRequest'] } - - it 'finds MRs by project and IID' do - is_expected.to have_graphql_arguments(:full_path, :iid) - is_expected.to have_graphql_type(Types::MergeRequestType) - is_expected.to have_graphql_resolver(Resolvers::MergeRequestResolver) - end - - it 'authorizes with read_merge_request' do - is_expected.to require_graphql_authorizations(:read_merge_request) - end - end end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index c0dc9293397..1a720aae55c 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -298,7 +298,7 @@ describe MarkupHelper do it 'preserves code color scheme' do object = create_object("```ruby\ndef test\n 'hello world'\nend\n```") - expected = "\n<pre class=\"code highlight js-syntax-highlight ruby\">" \ + expected = "<pre class=\"code highlight js-syntax-highlight ruby\">" \ "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \ "</code></pre>" diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index d372e58f63d..5cf9e9e8f12 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -90,6 +90,10 @@ describe ProjectsHelper do expect(helper.project_list_cache_key(project)).to include(project.cache_key) end + it "includes the last activity date" do + expect(helper.project_list_cache_key(project)).to include(project.last_activity_date) + end + it "includes the controller name" do expect(helper.controller).to receive(:controller_name).and_return("testcontroller") @@ -276,7 +280,11 @@ describe ProjectsHelper do describe '#sanitizerepo_repo_path' do let(:project) { create(:project, :repository) } - let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path } + let(:storage_path) do + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + Gitlab.config.repositories.storages.default.legacy_disk_path + end + end before do allow(Settings.shared).to receive(:[]).with('path').and_return('/base/repo/export/path') diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index f920c4ca945..8b79624d9f4 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -32,7 +32,7 @@ describe('Blob viewer', () => { afterEach(() => { mock.restore(); - location.hash = ''; + window.location.hash = ''; }); it('loads source file after switching views', (done) => { @@ -49,7 +49,7 @@ describe('Blob viewer', () => { }); it('loads source file when line number is in hash', (done) => { - location.hash = '#L1'; + window.location.hash = '#L1'; new BlobViewer(); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js index 93dc60d59fe..6f679369289 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js @@ -36,7 +36,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('on click', () => { it('should change the url according to the clicked tab', () => { - const historySpy = spyOn(history, 'replaceState').and.callFake(() => {}); + const historySpy = spyOn(window.history, 'replaceState').and.callFake(() => {}); const linkedTabs = new LinkedTabs({ action: 'show', diff --git a/spec/javascripts/commits_spec.js b/spec/javascripts/commits_spec.js index 60d100e8544..28b89157bd3 100644 --- a/spec/javascripts/commits_spec.js +++ b/spec/javascripts/commits_spec.js @@ -56,7 +56,7 @@ describe('Commits List', () => { beforeEach(() => { commitsList.searchField.val(''); - spyOn(history, 'replaceState').and.stub(); + spyOn(window.history, 'replaceState').and.stub(); mock = new MockAdapter(axios); mock.onGet('/h5bp/html5-boilerplate/commits/master').reply(200, { diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index 615168645b4..6968fbc7ce7 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -220,7 +220,7 @@ describe('Environment', () => { ); component = mountComponent(EnvironmentsComponent, mockData); - spyOn(history, 'pushState').and.stub(); + spyOn(window.history, 'pushState').and.stub(); }); describe('updateContent', () => { diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js index f5ce4df0bfe..51d4213c38f 100644 --- a/spec/javascripts/environments/folder/environments_folder_view_spec.js +++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js @@ -177,7 +177,7 @@ describe('Environments Folder View', () => { }); component = mountComponent(Component, mockData); - spyOn(history, 'pushState').and.stub(); + spyOn(window.history, 'pushState').and.stub(); }); describe('updateContent', () => { diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js index 4e93fd91751..108e0064c47 100644 --- a/spec/javascripts/gl_field_errors_spec.js +++ b/spec/javascripts/gl_field_errors_spec.js @@ -8,7 +8,9 @@ describe('GL Style Field Errors', function() { beforeEach(function() { loadFixtures('static/gl_field_errors.html.raw'); - const $form = this.$form = $('form.gl-show-field-errors'); + const $form = $('form.gl-show-field-errors'); + + this.$form = $form; this.fieldErrors = new GlFieldErrors($form); }); diff --git a/spec/javascripts/helpers/user_mock_data_helper.js b/spec/javascripts/helpers/user_mock_data_helper.js index 323fee3767e..f6c3ce5aecc 100644 --- a/spec/javascripts/helpers/user_mock_data_helper.js +++ b/spec/javascripts/helpers/user_mock_data_helper.js @@ -1,7 +1,7 @@ export default { createNumberRandomUsers(numberUsers) { const users = []; - for (let i = 0; i < numberUsers; i = i += 1) { + for (let i = 0; i < numberUsers; i += 1) { users.push( { avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js index 8f7cf24c22f..bf96170f703 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js @@ -93,14 +93,14 @@ describe('Multi-file editor commit sidebar list item', () => { describe('is active', () => { it('does not add active class when dont keys match', () => { - expect(vm.$el.classList).not.toContain('is-active'); + expect(vm.$el.querySelector('.is-active')).toBe(null); }); it('adds active class when keys match', done => { vm.keyPrefix = 'staged'; vm.$nextTick(() => { - expect(vm.$el.classList).toContain('is-active'); + expect(vm.$el.querySelector('.is-active')).not.toBe(null); done(); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/list_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_spec.js index 6fb52378386..b786be55019 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_spec.js @@ -16,6 +16,7 @@ describe('Multi-file editor commit sidebar list', () => { iconName: 'staged', action: 'stageAllChanges', actionBtnText: 'stage all', + actionBtnIcon: 'history', itemActionComponent: 'stage-button', activeFileKey: 'staged-testing', keyPrefix: 'staged', @@ -42,7 +43,7 @@ describe('Multi-file editor commit sidebar list', () => { }); it('renders list', () => { - expect(vm.$el.querySelectorAll('li').length).toBe(1); + expect(vm.$el.querySelectorAll('.multi-file-commit-list > li').length).toBe(1); }); }); diff --git a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js index 6bf8710bda7..a5b906da8a1 100644 --- a/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/stage_button_spec.js @@ -39,7 +39,7 @@ describe('IDE stage file button', () => { }); it('calls store with discard button', () => { - vm.$el.querySelectorAll('.btn')[1].click(); + vm.$el.querySelector('.dropdown-menu button').click(); expect(vm.discardFileChanges).toHaveBeenCalledWith(f.path); }); diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js index 531bcd6e540..6bf309fb4bf 100644 --- a/spec/javascripts/ide/components/repo_commit_section_spec.js +++ b/spec/javascripts/ide/components/repo_commit_section_spec.js @@ -111,7 +111,7 @@ describe('RepoCommitSection', () => { }); it('renders a commit section', () => { - const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')]; + const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list > li')]; const allFiles = vm.$store.state.changedFiles.concat(vm.$store.state.stagedFiles); expect(changedFileElements.length).toEqual(4); @@ -140,22 +140,26 @@ describe('RepoCommitSection', () => { vm.$el.querySelector('.multi-file-discard-btn .btn').click(); Vue.nextTick(() => { - expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe( - 1, - ); + expect( + vm.$el + .querySelector('.ide-commit-list-container') + .querySelectorAll('.multi-file-commit-list > li').length, + ).toBe(1); done(); }); }); it('discards a single file', done => { - vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click(); + vm.$el.querySelector('.multi-file-discard-btn .dropdown-menu button').click(); Vue.nextTick(() => { expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1'); - expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe( - 1, - ); + expect( + vm.$el + .querySelector('.ide-commit-list-container') + .querySelectorAll('.multi-file-commit-list > li').length, + ).toBe(1); done(); }); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index d318521d0a0..2256deb7dac 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -315,6 +315,17 @@ describe('RepoEditor', () => { done(); }); }); + + it('calls updateDimensions when rightPane is updated', done => { + vm.$store.state.rightPane = 'testing'; + + vm.$nextTick(() => { + expect(vm.editor.updateDimensions).toHaveBeenCalled(); + expect(vm.editor.updateDiffView).toHaveBeenCalled(); + + done(); + }); + }); }); describe('show tabs', () => { diff --git a/spec/javascripts/ide/lib/editor_spec.js b/spec/javascripts/ide/lib/editor_spec.js index c1932284d53..c2cb964ea87 100644 --- a/spec/javascripts/ide/lib/editor_spec.js +++ b/spec/javascripts/ide/lib/editor_spec.js @@ -263,4 +263,23 @@ describe('Multi-file editor library', () => { expect(instance.isDiffEditorType).toBe(false); }); }); + + it('sets quickSuggestions to false when language is markdown', () => { + instance.createInstance(holder); + + spyOn(instance.instance, 'updateOptions').and.callThrough(); + + const model = instance.createModel({ + ...file(), + key: 'index.md', + path: 'index.md', + }); + + instance.attachModel(model); + + expect(instance.instance.updateOptions).toHaveBeenCalledWith({ + readOnly: false, + quickSuggestions: false, + }); + }); }); diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index 7bebc2288e3..5746683917e 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -166,12 +166,12 @@ describe('IDE store file actions', () => { }); it('resets location.hash for line highlighting', done => { - location.hash = 'test'; + window.location.hash = 'test'; store .dispatch('setFileActive', localFile.path) .then(() => { - expect(location.hash).not.toBe('test'); + expect(window.location.hash).not.toBe('test'); done(); }) diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index a2869ff378b..133ad627f34 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -108,77 +108,6 @@ describe('IDE commit module actions', () => { }); }); - describe('checkCommitStatus', () => { - beforeEach(() => { - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - branches: { - master: { - workingReference: '1', - }, - }, - }; - }); - - it('calls service', done => { - spyOn(service, 'getBranchData').and.returnValue( - Promise.resolve({ - data: { - commit: { id: '123' }, - }, - }), - ); - - store - .dispatch('commit/checkCommitStatus') - .then(() => { - expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master'); - - done(); - }) - .catch(done.fail); - }); - - it('returns true if current ref does not equal returned ID', done => { - spyOn(service, 'getBranchData').and.returnValue( - Promise.resolve({ - data: { - commit: { id: '123' }, - }, - }), - ); - - store - .dispatch('commit/checkCommitStatus') - .then(val => { - expect(val).toBeTruthy(); - - done(); - }) - .catch(done.fail); - }); - - it('returns false if current ref equals returned ID', done => { - spyOn(service, 'getBranchData').and.returnValue( - Promise.resolve({ - data: { - commit: { id: '1' }, - }, - }), - ); - - store - .dispatch('commit/checkCommitStatus') - .then(val => { - expect(val).toBeFalsy(); - - done(); - }) - .catch(done.fail); - }); - }); - describe('updateFilesAfterCommit', () => { const data = { id: '123', @@ -314,6 +243,7 @@ describe('IDE commit module actions', () => { ...file('changed'), type: 'blob', active: true, + lastCommitSha: '123456789', }; store.state.stagedFiles.push(f); store.state.changedFiles = [ @@ -366,6 +296,7 @@ describe('IDE commit module actions', () => { file_path: jasmine.anything(), content: jasmine.anything(), encoding: jasmine.anything(), + last_commit_id: undefined, }, ], start_branch: 'master', @@ -376,6 +307,32 @@ describe('IDE commit module actions', () => { .catch(done.fail); }); + it('sends lastCommit ID when not creating new branch', done => { + store.state.commit.commitAction = '1'; + + store + .dispatch('commit/commitChanges') + .then(() => { + expect(service.commit).toHaveBeenCalledWith('abcproject', { + branch: jasmine.anything(), + commit_message: 'testing 123', + actions: [ + { + action: 'update', + file_path: jasmine.anything(), + content: jasmine.anything(), + encoding: jasmine.anything(), + last_commit_id: '123456789', + }, + ], + start_branch: undefined, + }); + + done(); + }) + .catch(done.fail); + }); + it('sets last Commit Msg', done => { store .dispatch('commit/commitChanges') diff --git a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js index 03ec08d05c3..fa4c18931e5 100644 --- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js @@ -208,18 +208,19 @@ describe('IDE merge requests actions', () => { expect(commit.calls.argsFor(1)).toEqual(['SET_CURRENT_MERGE_REQUEST', '1', { root: true }]); expect(commit.calls.argsFor(2)).toEqual(['RESET_OPEN_FILES', null, { root: true }]); - expect(dispatch.calls.argsFor(0)).toEqual([ - 'pipelines/resetLatestPipeline', + expect(dispatch.calls.argsFor(0)).toEqual(['setCurrentBranchId', '', { root: true }]); + expect(dispatch.calls.argsFor(1)).toEqual([ + 'pipelines/stopPipelinePolling', null, { root: true }, ]); - expect(dispatch.calls.argsFor(1)).toEqual(['setCurrentBranchId', '', { root: true }]); - expect(dispatch.calls.argsFor(2)).toEqual([ - 'pipelines/stopPipelinePolling', + expect(dispatch.calls.argsFor(2)).toEqual(['setRightPane', null, { root: true }]); + expect(dispatch.calls.argsFor(3)).toEqual([ + 'pipelines/resetLatestPipeline', null, { root: true }, ]); - expect(dispatch.calls.argsFor(3)).toEqual([ + expect(dispatch.calls.argsFor(4)).toEqual([ 'pipelines/clearEtagPoll', null, { root: true }, diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js index f2f8e780cd1..f47e69d6e5b 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -18,6 +18,7 @@ import actions, { receiveJobTraceError, receiveJobTraceSuccess, fetchJobTrace, + resetLatestPipeline, } from '~/ide/stores/modules/pipelines/actions'; import state from '~/ide/stores/modules/pipelines/state'; import * as types from '~/ide/stores/modules/pipelines/mutation_types'; @@ -416,4 +417,20 @@ describe('IDE pipelines actions', () => { }); }); }); + + describe('resetLatestPipeline', () => { + it('commits reset mutations', done => { + testAction( + resetLatestPipeline, + null, + mockedState, + [ + { type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: null }, + { type: types.SET_DETAIL_JOB, payload: null }, + ], + [], + done, + ); + }); + }); }); diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js index f38ac6dd82f..a7bd443af51 100644 --- a/spec/javascripts/ide/stores/utils_spec.js +++ b/spec/javascripts/ide/stores/utils_spec.js @@ -1,4 +1,5 @@ import * as utils from '~/ide/stores/utils'; +import { file } from '../helpers'; describe('Multi-file store utils', () => { describe('setPageTitle', () => { @@ -63,4 +64,59 @@ describe('Multi-file store utils', () => { expect(foundEntry).toBeUndefined(); }); }); + + describe('createCommitPayload', () => { + it('returns API payload', () => { + const state = { + commitMessage: 'commit message', + }; + const rootState = { + stagedFiles: [ + { + ...file('staged'), + path: 'staged', + content: 'updated file content', + lastCommitSha: '123456789', + }, + { + ...file('newFile'), + path: 'added', + tempFile: true, + content: 'new file content', + base64: true, + lastCommitSha: '123456789', + }, + ], + currentBranchId: 'master', + }; + const payload = utils.createCommitPayload({ + branch: 'master', + newBranch: false, + state, + rootState, + }); + + expect(payload).toEqual({ + branch: 'master', + commit_message: 'commit message', + actions: [ + { + action: 'update', + file_path: 'staged', + content: 'updated file content', + encoding: 'text', + last_commit_id: '123456789', + }, + { + action: 'create', + file_path: 'added', + content: 'new file content', + encoding: 'base64', + last_commit_id: '123456789', + }, + ], + start_branch: undefined, + }); + }); + }); }); diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index bf1f0c822fe..eb5e0bddb74 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -145,7 +145,7 @@ describe('Issuable output', () => { resolve({ data: { confidential: false, - web_url: location.pathname, + web_url: window.location.pathname, }, }); })); @@ -177,7 +177,7 @@ describe('Issuable output', () => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { resolve({ data: { - web_url: location.pathname, + web_url: window.location.pathname, confidential: vm.isConfidential, }, }); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 2d7cc3443cf..a9ec7f42a9d 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -40,13 +40,13 @@ describe('common_utils', () => { }); it('should decode params', () => { - history.pushState('', '', '?label_name%5B%5D=test'); + window.history.pushState('', '', '?label_name%5B%5D=test'); expect( commonUtils.getUrlParamsArray()[0], ).toBe('label_name[]=test'); - history.pushState('', '', '?'); + window.history.pushState('', '', '?'); }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 648fb3e9bd3..acbf23e2007 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -974,7 +974,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ).toBeFalsy(); expect( $tempNoteHeader - .find('.d-none.d-sm-block') + .find('.d-none.d-sm-inline-block') .text() .trim(), ).toEqual(currentUserFullname); @@ -1020,7 +1020,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; const $tempNoteHeader = $tempNote.find('.note-header'); expect( $tempNoteHeader - .find('.d-none.d-sm-block') + .find('.d-none.d-sm-inline-block') .text() .trim(), ).toEqual('Foo <script>alert("XSS")</script>'); diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index ff17602da2b..50141bd99b4 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -427,7 +427,7 @@ describe('Pipelines', () => { describe('methods', () => { beforeEach(() => { - spyOn(history, 'pushState').and.stub(); + spyOn(window.history, 'pushState').and.stub(); }); describe('updateContent', () => { diff --git a/spec/javascripts/profile/account/components/update_username_spec.js b/spec/javascripts/profile/account/components/update_username_spec.js index bac306edf5a..5311499fb73 100644 --- a/spec/javascripts/profile/account/components/update_username_spec.js +++ b/spec/javascripts/profile/account/components/update_username_spec.js @@ -90,7 +90,8 @@ describe('UpdateUsername component', () => { it('confirmation modal should escape usernames properly', done => { const { modalBody } = findElements(); - vm.username = vm.newUsername = '<i>Italic</i>'; + vm.username = '<i>Italic</i>'; + vm.newUsername = vm.username; Vue.nextTick() .then(() => { diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js index 4fba36bd4de..c1a69bd7018 100644 --- a/spec/javascripts/settings_panels_spec.js +++ b/spec/javascripts/settings_panels_spec.js @@ -9,11 +9,11 @@ describe('Settings Panels', () => { describe('initSettingsPane', () => { afterEach(() => { - location.hash = ''; + window.location.hash = ''; }); it('should expand linked hash fragment panel', () => { - location.hash = '#autodevops-settings'; + window.location.hash = '#autodevops-settings'; const pipelineSettingsPanel = document.querySelector('#autodevops-settings'); // Our test environment automatically expands everything so we need to clear that out first diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index d73608ed0ed..b10d8be6781 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -66,7 +66,7 @@ describe('ShortcutsIssuable', function () { }); describe('with a multi-line selection', () => { it('quotes the selected lines as a group', () => { - stubSelection('<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>'); + stubSelection('<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>'); this.shortcut.replyWithSelectedText(true); expect($(this.selector).val()).toBe('> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n'); }); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 2411d33a496..994011b262a 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -39,7 +39,8 @@ jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH; beforeAll(() => jasmine.addMatchers(customMatchers)); // globalize common libraries -window.$ = window.jQuery = $; +window.$ = $; +window.jQuery = window.$; // stub expected globals window.gl = window.gl || {}; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js index 3e2fd71b5b8..efa5c878678 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -39,7 +39,8 @@ describe('MRWidgetMerged', () => { readableClosedAt: '', }, updatedAt: 'mergedUpdatedAt', - shortMergeCommitSha: 'asdf1234', + shortMergeCommitSha: '958c0475', + mergeCommitSha: '958c047516e182dfc52317f721f696e8a1ee85ed', mergeCommitPath: 'http://localhost:3000/root/nautilus/commit/f7ce827c314c9340b075657fd61c789fb01cf74d', sourceBranch: 'bar', targetBranch, @@ -153,7 +154,7 @@ describe('MRWidgetMerged', () => { it('shows button to copy commit SHA to clipboard', () => { expect(selectors.copyMergeShaButton).toExist(); - expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe(vm.mr.shortMergeCommitSha); + expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe(vm.mr.mergeCommitSha); }); it('shows merge commit SHA link', () => { diff --git a/spec/javascripts/vue_shared/components/gl_modal_spec.js b/spec/javascripts/vue_shared/components/gl_modal_spec.js index 23be8d93b81..e4737714312 100644 --- a/spec/javascripts/vue_shared/components/gl_modal_spec.js +++ b/spec/javascripts/vue_shared/components/gl_modal_spec.js @@ -208,6 +208,14 @@ describe('GlModal', () => { expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-lg')).toEqual(true); }); + it('should render modal-xl', () => { + vm = mountComponent(modalComponent, { + modalSize: 'xl', + }); + + expect(vm.$el.querySelector('.modal-dialog').classList.contains('modal-xl')).toEqual(true); + }); + it('should not add modal size classes when md size is passed', () => { vm = mountComponent(modalComponent, { modalSize: 'md', diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb index 8224dc5a6b9..b645e49bd43 100644 --- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb +++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb @@ -11,4 +11,8 @@ describe Banzai::Filter::BlockquoteFenceFilter do expect(output).to eq(expected) end + + it 'allows trailing whitespace on blockquote fence lines' do + expect(filter(">>> \ntest\n>>> ")).to eq("> test") + end end diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb index c19de7b784a..41f957c4e00 100644 --- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb +++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Banzai::Filter::ImageLazyLoadFilter, lib: true do +describe Banzai::Filter::ImageLazyLoadFilter do include FilterSpecHelper def image(path) diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb index 00c407d1b69..ab14d77d552 100644 --- a/spec/lib/banzai/filter/markdown_filter_spec.rb +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -7,13 +7,13 @@ describe Banzai::Filter::MarkdownFilter do it 'adds language to lang attribute when specified' do result = filter("```html\nsome code\n```") - expect(result).to start_with("\n<pre><code lang=\"html\">") + expect(result).to start_with("<pre><code lang=\"html\">") end it 'does not add language to lang attribute when not specified' do result = filter("```\nsome code\n```") - expect(result).to start_with("\n<pre><code>") + expect(result).to start_with("<pre><code>") end end end diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb index 136646bd4ee..454ad1589b9 100644 --- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb @@ -99,7 +99,7 @@ describe Gitlab::Auth::UserAuthFinders do context 'when the request format is empty' do it 'the method call does not modify the original value' do - env['action_dispatch.request.formats'] = nil + env.delete('action_dispatch.request.formats') find_user_from_feed_token diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb index 0d2074eed22..0dee683350f 100644 --- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb +++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb @@ -114,7 +114,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq, :migra it 'does not drop the temporary tracking table after processing the batch, if there are still untracked rows' do subject.perform(1, untracked_files_for_uploads.last.id - 1) - expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_truthy + expect(ActiveRecord::Base.connection.data_source_exists?(:untracked_files_for_uploads)).to be_truthy end it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index a65012d2314..0e0788ce974 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -1,21 +1,17 @@ require 'spec_helper' describe Gitlab::Checks::ForcePush do - let(:project) { create(:project, :repository) } - let(:repository) { project.repository.raw } + set(:project) { create(:project, :repository) } - context "exit code checking", :skip_gitaly_mock do - it "does not raise a runtime error if the `popen` call to git returns a zero exit code" do - allow(repository).to receive(:popen).and_return(['normal output', 0]) + describe '.force_push?' do + it 'returns false if the repo is empty' do + allow(project).to receive(:empty_repo?).and_return(true) - expect { described_class.force_push?(project, 'oldrev', 'newrev') }.not_to raise_error + expect(described_class.force_push?(project, 'HEAD', 'HEAD~')).to be(false) end - it "raises a GitError error if the `popen` call to git returns a non-zero exit code" do - allow(repository).to receive(:popen).and_return(['error', 1]) - - expect { described_class.force_push?(project, 'oldrev', 'newrev') } - .to raise_error(Gitlab::Git::Repository::GitError) + it 'checks if old rev is an anchestor' do + expect(described_class.force_push?(project, 'HEAD', 'HEAD~')).to be(true) end end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index bc5a5e43103..2e204da307d 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -49,7 +49,7 @@ describe Gitlab::Ci::Config do describe '.new' do it 'raises error' do expect { config }.to raise_error( - Gitlab::Ci::Config::Loader::FormatError, + ::Gitlab::Ci::Config::Loader::FormatError, /Invalid configuration format/ ) end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index c5a4d9b4778..284aed91e29 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Chain::Populate do - set(:project) { create(:project) } + set(:project) { create(:project, :repository) } set(:user) { create(:user) } let(:pipeline) do @@ -174,7 +174,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do end let(:pipeline) do - build(:ci_pipeline, ref: 'master', config: config) + build(:ci_pipeline, ref: 'master', project: project, config: config) end it_behaves_like 'a correct pipeline' diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb index c53294d091c..a8dc5356413 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Chain::Validate::Config do - set(:project) { create(:project) } + set(:project) { create(:project, :repository) } set(:user) { create(:user) } let(:command) do diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index ecb16daec96..fa5327c26f0 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab module Ci - describe YamlProcessor, :lib do + describe YamlProcessor do subject { described_class.new(config) } describe 'our current .gitlab-ci.yml' do diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 8ac36ae8bab..8bb246aa4bd 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -314,8 +314,13 @@ describe Gitlab::Database do describe '.cached_table_exists?' do it 'only retrieves data once per table' do - expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original - expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original + if Gitlab.rails5? + expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original + expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original + else + expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original + expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original + end 2.times do expect(described_class.cached_table_exists?(:projects)).to be_truthy diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb index 08c4a474217..f36111a4946 100644 --- a/spec/lib/gitlab/favicon_spec.rb +++ b/spec/lib/gitlab/favicon_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Gitlab::Favicon, :request_store do it 'uses the custom favicon if a favicon appearance is present' do create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png') - expect(described_class.main).to match %r{/uploads/-/system/appearance/favicon/\d+/favicon_main_dk.png} + expect(described_class.main).to match %r{/uploads/-/system/appearance/favicon/\d+/dk.png} end end diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index 793228701cf..ba790b717ae 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::Git::Blame, seed_helper: true do Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md") end - shared_examples 'blaming a file' do + describe 'blaming a file' do context "each count" do it do data = [] @@ -68,12 +68,4 @@ describe Gitlab::Git::Blame, seed_helper: true do end end end - - context 'when Gitaly blame feature is enabled' do - it_behaves_like 'blaming a file' - end - - context 'when Gitaly blame feature is disabled', :skip_gitaly_mock do - it_behaves_like 'blaming a file' - end end diff --git a/spec/lib/gitlab/git/committer_with_hooks_spec.rb b/spec/lib/gitlab/git/committer_with_hooks_spec.rb index 267056b96e6..2100690f873 100644 --- a/spec/lib/gitlab/git/committer_with_hooks_spec.rb +++ b/spec/lib/gitlab/git/committer_with_hooks_spec.rb @@ -1,154 +1,156 @@ require 'spec_helper' describe Gitlab::Git::CommitterWithHooks, seed_helper: true do - shared_examples 'calling wiki hooks' do - let(:project) { create(:project) } - let(:user) { project.owner } - let(:project_wiki) { ProjectWiki.new(project, user) } - let(:wiki) { project_wiki.wiki } - let(:options) do - { - id: user.id, - username: user.username, - name: user.name, - email: user.email, - message: 'commit message' - } - end - - subject { described_class.new(wiki, options) } + # TODO https://gitlab.com/gitlab-org/gitaly/issues/1234 + skip 'needs to be moved to gitaly-ruby test suite' do + shared_examples 'calling wiki hooks' do + let(:project) { create(:project) } + let(:user) { project.owner } + let(:project_wiki) { ProjectWiki.new(project, user) } + let(:wiki) { project_wiki.wiki } + let(:options) do + { + id: user.id, + username: user.username, + name: user.name, + email: user.email, + message: 'commit message' + } + end - before do - project_wiki.create_page('home', 'test content') - end + subject { described_class.new(wiki, options) } - shared_examples 'failing pre-receive hook' do before do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, '']) - expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update') - expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive') + project_wiki.create_page('home', 'test content') end - it 'raises exception' do - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - end + shared_examples 'failing pre-receive hook' do + before do + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([false, '']) + expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('update') + expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive') + end - it 'does not create a new commit inside the repository' do - current_rev = find_current_rev + it 'raises exception' do + expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) + end - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) + it 'does not create a new commit inside the repository' do + current_rev = find_current_rev - expect(current_rev).to eq find_current_rev - end - end + expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - shared_examples 'failing update hook' do - before do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, '']) - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, '']) - expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive') + expect(current_rev).to eq find_current_rev + end end - it 'raises exception' do - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - end + shared_examples 'failing update hook' do + before do + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, '']) + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([false, '']) + expect_any_instance_of(Gitlab::Git::HooksService).not_to receive(:run_hook).with('post-receive') + end - it 'does not create a new commit inside the repository' do - current_rev = find_current_rev + it 'raises exception' do + expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) + end - expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) + it 'does not create a new commit inside the repository' do + current_rev = find_current_rev - expect(current_rev).to eq find_current_rev - end - end + expect { subject.commit }.to raise_error(Gitlab::Git::Wiki::OperationError) - shared_examples 'failing post-receive hook' do - before do - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, '']) - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, '']) - expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, '']) + expect(current_rev).to eq find_current_rev + end end - it 'does not raise exception' do - expect { subject.commit }.not_to raise_error - end + shared_examples 'failing post-receive hook' do + before do + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('pre-receive').and_return([true, '']) + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('update').and_return([true, '']) + expect_any_instance_of(Gitlab::Git::HooksService).to receive(:run_hook).with('post-receive').and_return([false, '']) + end + + it 'does not raise exception' do + expect { subject.commit }.not_to raise_error + end - it 'creates the commit' do - current_rev = find_current_rev + it 'creates the commit' do + current_rev = find_current_rev - subject.commit + subject.commit - expect(current_rev).not_to eq find_current_rev + expect(current_rev).not_to eq find_current_rev + end end - end - shared_examples 'when hooks call succceeds' do - let(:hook) { double(:hook) } + shared_examples 'when hooks call succceeds' do + let(:hook) { double(:hook) } - it 'calls the three hooks' do - expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) - expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil]) + it 'calls the three hooks' do + expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) + expect(hook).to receive(:trigger).exactly(3).times.and_return([true, nil]) - subject.commit - end + subject.commit + end - it 'creates the commit' do - current_rev = find_current_rev + it 'creates the commit' do + current_rev = find_current_rev - subject.commit + subject.commit - expect(current_rev).not_to eq find_current_rev + expect(current_rev).not_to eq find_current_rev + end end - end - context 'when creating a page' do - before do - project_wiki.create_page('index', 'test content') + context 'when creating a page' do + before do + project_wiki.create_page('index', 'test content') + end + + it_behaves_like 'failing pre-receive hook' + it_behaves_like 'failing update hook' + it_behaves_like 'failing post-receive hook' + it_behaves_like 'when hooks call succceeds' end - it_behaves_like 'failing pre-receive hook' - it_behaves_like 'failing update hook' - it_behaves_like 'failing post-receive hook' - it_behaves_like 'when hooks call succceeds' - end + context 'when updating a page' do + before do + project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown) + end - context 'when updating a page' do - before do - project_wiki.update_page(find_page('home'), content: 'some other content', format: :markdown) + it_behaves_like 'failing pre-receive hook' + it_behaves_like 'failing update hook' + it_behaves_like 'failing post-receive hook' + it_behaves_like 'when hooks call succceeds' end - it_behaves_like 'failing pre-receive hook' - it_behaves_like 'failing update hook' - it_behaves_like 'failing post-receive hook' - it_behaves_like 'when hooks call succceeds' - end + context 'when deleting a page' do + before do + project_wiki.delete_page(find_page('home')) + end - context 'when deleting a page' do - before do - project_wiki.delete_page(find_page('home')) + it_behaves_like 'failing pre-receive hook' + it_behaves_like 'failing update hook' + it_behaves_like 'failing post-receive hook' + it_behaves_like 'when hooks call succceeds' end - it_behaves_like 'failing pre-receive hook' - it_behaves_like 'failing update hook' - it_behaves_like 'failing post-receive hook' - it_behaves_like 'when hooks call succceeds' - end + def find_current_rev + wiki.gollum_wiki.repo.commits.first&.sha + end - def find_current_rev - wiki.gollum_wiki.repo.commits.first&.sha + def find_page(name) + wiki.page(title: name) + end end - def find_page(name) - wiki.page(title: name) + context 'when Gitaly is enabled' do + it_behaves_like 'calling wiki hooks' end - end - - # TODO: Uncomment once Gitaly updates the ruby vendor code - # context 'when Gitaly is enabled' do - # it_behaves_like 'calling wiki hooks' - # end - context 'when Gitaly is disabled', :skip_gitaly_mock do - it_behaves_like 'calling wiki hooks' + context 'when Gitaly is disabled', :disable_gitaly do + it_behaves_like 'calling wiki hooks' + end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 1744db1b17e..595482f76d5 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -77,17 +77,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#root_ref' do - context 'with gitaly disabled' do - before do - allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) - end - - it 'calls #discover_default_branch' do - expect(repository).to receive(:discover_default_branch) - repository.root_ref - end - end - it 'returns UTF-8' do expect(repository.root_ref).to be_utf8 end @@ -153,46 +142,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "#discover_default_branch" do - let(:master) { 'master' } - let(:feature) { 'feature' } - let(:feature2) { 'feature2' } - - around do |example| - # discover_default_branch will be moved to gitaly-ruby - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - it "returns 'master' when master exists" do - expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, master]) - expect(repository.discover_default_branch).to eq('master') - end - - it "returns non-master when master exists but default branch is set to something else" do - File.write(File.join(repository_path, 'HEAD'), 'ref: refs/heads/feature') - expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, master]) - expect(repository.discover_default_branch).to eq('feature') - File.write(File.join(repository_path, 'HEAD'), 'ref: refs/heads/master') - end - - it "returns a non-master branch when only one exists" do - expect(repository).to receive(:branch_names).at_least(:once).and_return([feature]) - expect(repository.discover_default_branch).to eq('feature') - end - - it "returns a non-master branch when more than one exists and master does not" do - expect(repository).to receive(:branch_names).at_least(:once).and_return([feature, feature2]) - expect(repository.discover_default_branch).to eq('feature') - end - - it "returns nil when no branch exists" do - expect(repository).to receive(:branch_names).at_least(:once).and_return([]) - expect(repository.discover_default_branch).to be_nil - end - end - describe '#branch_names' do subject { repository.branch_names } @@ -476,7 +425,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#has_local_branches?' do - shared_examples 'check for local branches' do + context 'check for local branches' do it { expect(repository.has_local_branches?).to eq(true) } context 'mutable' do @@ -510,14 +459,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end end - - context 'with gitaly' do - it_behaves_like 'check for local branches' - end - - context 'without gitaly', :skip_gitaly_mock do - it_behaves_like 'check for local branches' - end end describe "#delete_branch" do @@ -1173,7 +1114,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#count_commits' do - shared_examples 'extended commit counting' do + describe 'extended commit counting' do context 'with after timestamp' do it 'returns the number of commits after timestamp' do options = { ref: 'master', after: Time.iso8601('2013-03-03T20:15:01+00:00') } @@ -1258,14 +1199,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end end - - context 'when Gitaly count_commits feature is enabled' do - it_behaves_like 'extended commit counting' - end - - context 'when Gitaly count_commits feature is disabled', :disable_gitaly do - it_behaves_like 'extended commit counting' - end end describe '#autocrlf' do @@ -1395,24 +1328,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - # With Gitaly enabled, Gitaly just doesn't return deleted branches. - context 'with deleted branch with Gitaly disabled' do - before do - allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) - end - - it 'returns no results' do - ref = double() - allow(ref).to receive(:name) { 'bad-branch' } - allow(ref).to receive(:target) { raise Rugged::ReferenceError } - branches = double() - allow(branches).to receive(:each) { [ref].each } - allow(repository_rugged).to receive(:branches) { branches } - - expect(subject).to be_empty - end - end - it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branches end @@ -1765,70 +1680,52 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#languages' do - shared_examples 'languages' do - it 'returns exactly the expected results' do - languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6') - expected_languages = [ - { value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" }, - { value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, - { value: 7.9, label: "HTML", color: "#e34c26", highlight: "#e34c26" }, - { value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" } - ] + it 'returns exactly the expected results' do + languages = repository.languages('4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6') + expected_languages = [ + { value: 66.63, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 22.96, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, + { value: 7.9, label: "HTML", color: "#e34c26", highlight: "#e34c26" }, + { value: 2.51, label: "CoffeeScript", color: "#244776", highlight: "#244776" } + ] - expect(languages.size).to eq(expected_languages.size) + expect(languages.size).to eq(expected_languages.size) - expected_languages.size.times do |i| - a = expected_languages[i] - b = languages[i] + expected_languages.size.times do |i| + a = expected_languages[i] + b = languages[i] - expect(a.keys.sort).to eq(b.keys.sort) - expect(a[:value]).to be_within(0.1).of(b[:value]) + expect(a.keys.sort).to eq(b.keys.sort) + expect(a[:value]).to be_within(0.1).of(b[:value]) - non_float_keys = a.keys - [:value] - expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys)) - end - end - - it "uses the repository's HEAD when no ref is passed" do - lang = repository.languages.first - - expect(lang[:label]).to eq('Ruby') + non_float_keys = a.keys - [:value] + expect(a.values_at(*non_float_keys)).to eq(b.values_at(*non_float_keys)) end end - it_behaves_like 'languages' + it "uses the repository's HEAD when no ref is passed" do + lang = repository.languages.first - context 'with rugged', :skip_gitaly_mock do - it_behaves_like 'languages' + expect(lang[:label]).to eq('Ruby') end end describe '#license_short_name' do - shared_examples 'acquiring the Licensee license key' do - subject { repository.license_short_name } - - context 'when no license file can be found' do - let(:project) { create(:project, :repository) } - let(:repository) { project.repository.raw_repository } - - before do - project.repository.delete_file(project.owner, 'LICENSE', message: 'remove license', branch_name: 'master') - end + subject { repository.license_short_name } - it { is_expected.to be_nil } - end + context 'when no license file can be found' do + let(:project) { create(:project, :repository) } + let(:repository) { project.repository.raw_repository } - context 'when an mit license is found' do - it { is_expected.to eq('mit') } + before do + project.repository.delete_file(project.owner, 'LICENSE', message: 'remove license', branch_name: 'master') end - end - context 'when gitaly is enabled' do - it_behaves_like 'acquiring the Licensee license key' + it { is_expected.to be_nil } end - context 'when gitaly is disabled', :disable_gitaly do - it_behaves_like 'acquiring the Licensee license key' + context 'when an mit license is found' do + it { is_expected.to eq('mit') } end end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 95dc47e2a00..b752c3e8341 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -93,14 +93,4 @@ describe Gitlab::Git::RevList do expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2]) end end - - context "#missed_ref" do - let(:rev_list) { described_class.new(repository, oldrev: 'oldrev', newrev: 'newrev') } - - it 'calls out to `popen`' do - stub_popen_rev_list('--max-count=1', 'oldrev', '^newrev', with_lazy_block: false, output: "sha1\nsha2") - - expect(rev_list.missed_ref).to eq(%w[sha1 sha2]) - end - end end diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb index 35b06b14620..b63658e1b3b 100644 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ b/spec/lib/gitlab/git/wiki_spec.rb @@ -6,9 +6,7 @@ describe Gitlab::Git::Wiki do let(:project_wiki) { ProjectWiki.new(project, user) } subject { project_wiki.wiki } - # Remove skip_gitaly_mock flag when gitaly_find_page when - # https://gitlab.com/gitlab-org/gitlab-ce/issues/42039 is solved - describe '#page', :skip_gitaly_mock do + describe '#page' do before do create_page('page1', 'content') create_page('foo/page1', 'content foo/page1') @@ -25,7 +23,7 @@ describe Gitlab::Git::Wiki do end end - describe '#delete_page', :skip_gitaly_mock do + describe '#delete_page' do after do destroy_page('page1') end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 0d5f6a0b576..ff32025253a 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -934,6 +934,22 @@ describe Gitlab::GitAccess do expect(project.repository).to receive(:clean_stale_repository_files).and_call_original expect { push_access_check }.not_to raise_error end + + it 'avoids N+1 queries', :request_store do + # Run this once to establish a baseline. Cached queries should get + # cached, so that when we introduce another change we shouldn't see + # additional queries. + access.check('git-receive-pack', changes) + + control_count = ActiveRecord::QueryRecorder.new do + access.check('git-receive-pack', changes) + end + + changes = ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] + + # There is still an N+1 query with protected branches + expect { access.check('git-receive-pack', changes) }.not_to exceed_query_limit(control_count).with_threshold(1) + end end end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 730ede99fc9..9c6c9fe13bf 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -52,7 +52,9 @@ describe Gitlab::GitAccessWiki do context 'when the wiki repository does not exist' do it 'returns not found' do wiki_repo = project.wiki.repository - FileUtils.rm_rf(wiki_repo.path) + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + FileUtils.rm_rf(wiki_repo.path) + end # Sanity check for rm_rf expect(wiki_repo.exists?).to eq(false) diff --git a/spec/lib/gitlab/i18n/metadata_entry_spec.rb b/spec/lib/gitlab/i18n/metadata_entry_spec.rb index ab71d6454a9..a399517cc04 100644 --- a/spec/lib/gitlab/i18n/metadata_entry_spec.rb +++ b/spec/lib/gitlab/i18n/metadata_entry_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::I18n::MetadataEntry do - describe '#expected_plurals' do + describe '#expected_forms' do it 'returns the number of plurals' do data = { msgid: "", @@ -22,7 +22,7 @@ describe Gitlab::I18n::MetadataEntry do } entry = described_class.new(data) - expect(entry.expected_plurals).to eq(2) + expect(entry.expected_forms).to eq(2) end it 'returns 0 for the POT-metadata' do @@ -45,7 +45,7 @@ describe Gitlab::I18n::MetadataEntry do } entry = described_class.new(data) - expect(entry.expected_plurals).to eq(0) + expect(entry.expected_forms).to eq(0) end end end diff --git a/spec/lib/gitlab/i18n/po_linter_spec.rb b/spec/lib/gitlab/i18n/po_linter_spec.rb index 3a962ba7f22..3dbc23d2aaf 100644 --- a/spec/lib/gitlab/i18n/po_linter_spec.rb +++ b/spec/lib/gitlab/i18n/po_linter_spec.rb @@ -1,10 +1,31 @@ require 'spec_helper' require 'simple_po_parser' +# Disabling this cop to allow for multi-language examples in comments +# rubocop:disable Style/AsciiComments describe Gitlab::I18n::PoLinter do let(:linter) { described_class.new(po_path) } let(:po_path) { 'spec/fixtures/valid.po' } + def fake_translation(msgid:, translation:, plural_id: nil, plurals: []) + data = { msgid: msgid, msgid_plural: plural_id } + + if plural_id + [translation, *plurals].each_with_index do |plural, index| + allow(FastGettext::Translation).to receive(:n_).with(msgid, plural_id, index).and_return(plural) + data.merge!("msgstr[#{index}]" => plural) + end + else + allow(FastGettext::Translation).to receive(:_).with(msgid).and_return(translation) + data[:msgstr] = translation + end + + Gitlab::I18n::TranslationEntry.new( + data, + plurals.size + 1 + ) + end + describe '#errors' do it 'only calls validation once' do expect(linter).to receive(:validate_po).once.and_call_original @@ -155,9 +176,8 @@ describe Gitlab::I18n::PoLinter do describe '#validate_entries' do it 'keeps track of errors for entries' do - fake_invalid_entry = Gitlab::I18n::TranslationEntry.new( - { msgid: "Hello %{world}", msgstr: "Bonjour %{monde}" }, 2 - ) + fake_invalid_entry = fake_translation(msgid: "Hello %{world}", + translation: "Bonjour %{monde}") allow(linter).to receive(:translation_entries) { [fake_invalid_entry] } expect(linter).to receive(:validate_entry) @@ -177,6 +197,7 @@ describe Gitlab::I18n::PoLinter do expect(linter).to receive(:validate_newlines).with([], fake_entry) expect(linter).to receive(:validate_number_of_plurals).with([], fake_entry) expect(linter).to receive(:validate_unescaped_chars).with([], fake_entry) + expect(linter).to receive(:validate_translation).with([], fake_entry) linter.validate_entry(fake_entry) end @@ -185,7 +206,7 @@ describe Gitlab::I18n::PoLinter do describe '#validate_number_of_plurals' do it 'validates when there are an incorrect number of translations' do fake_metadata = double - allow(fake_metadata).to receive(:expected_plurals).and_return(2) + allow(fake_metadata).to receive(:expected_forms).and_return(2) allow(linter).to receive(:metadata_entry).and_return(fake_metadata) fake_entry = Gitlab::I18n::TranslationEntry.new( @@ -201,13 +222,16 @@ describe Gitlab::I18n::PoLinter do end describe '#validate_variables' do - it 'validates both signular and plural in a pluralized string when the entry has a singular' do - pluralized_entry = Gitlab::I18n::TranslationEntry.new( - { msgid: 'Hello %{world}', - msgid_plural: 'Hello all %{world}', - 'msgstr[0]' => 'Bonjour %{world}', - 'msgstr[1]' => 'Bonjour tous %{world}' }, - 2 + before do + allow(linter).to receive(:validate_variables_in_message).and_call_original + end + + it 'validates both singular and plural in a pluralized string when the entry has a singular' do + pluralized_entry = fake_translation( + msgid: 'Hello %{world}', + translation: 'Bonjour %{world}', + plural_id: 'Hello all %{world}', + plurals: ['Bonjour tous %{world}'] ) expect(linter).to receive(:validate_variables_in_message) @@ -221,11 +245,10 @@ describe Gitlab::I18n::PoLinter do end it 'only validates plural when there is no separate singular' do - pluralized_entry = Gitlab::I18n::TranslationEntry.new( - { msgid: 'Hello %{world}', - msgid_plural: 'Hello all %{world}', - 'msgstr[0]' => 'Bonjour %{world}' }, - 1 + pluralized_entry = fake_translation( + msgid: 'Hello %{world}', + translation: 'Bonjour %{world}', + plural_id: 'Hello all %{world}' ) expect(linter).to receive(:validate_variables_in_message) @@ -235,37 +258,65 @@ describe Gitlab::I18n::PoLinter do end it 'validates the message variables' do - entry = Gitlab::I18n::TranslationEntry.new( - { msgid: 'Hello', msgstr: 'Bonjour' }, - 2 - ) + entry = fake_translation(msgid: 'Hello', translation: 'Bonjour') expect(linter).to receive(:validate_variables_in_message) .with([], 'Hello', 'Bonjour') linter.validate_variables([], entry) end + + it 'validates variable usage in message ids' do + entry = fake_translation( + msgid: 'Hello %{world}', + translation: 'Bonjour %{world}', + plural_id: 'Hello all %{world}', + plurals: ['Bonjour tous %{world}'] + ) + + expect(linter).to receive(:validate_variables_in_message) + .with([], 'Hello %{world}', 'Hello %{world}') + .and_call_original + expect(linter).to receive(:validate_variables_in_message) + .with([], 'Hello all %{world}', 'Hello all %{world}') + .and_call_original + + linter.validate_variables([], entry) + end end describe '#validate_variables_in_message' do it 'detects when a variables are used incorrectly' do errors = [] - expected_errors = ['<hello %{world} %d> is missing: [%{hello}]', - '<hello %{world} %d> is using unknown variables: [%{world}]', - 'is combining multiple unnamed variables'] + expected_errors = ['<%d hello %{world} %s> is missing: [%{hello}]', + '<%d hello %{world} %s> is using unknown variables: [%{world}]', + 'is combining multiple unnamed variables', + 'is combining named variables with unnamed variables'] - linter.validate_variables_in_message(errors, '%{hello} world %d', 'hello %{world} %d') + linter.validate_variables_in_message(errors, '%d %{hello} world %s', '%d hello %{world} %s') expect(errors).to include(*expected_errors) end + + it 'does not allow combining 1 `%d` unnamed variable with named variables' do + errors = [] + + linter.validate_variables_in_message(errors, + '%{type} detected %d vulnerability', + '%{type} detecteerde %d kwetsbaarheid') + + expect(errors).not_to be_empty + end end describe '#validate_translation' do + let(:entry) { fake_translation(msgid: 'Hello %{world}', translation: 'Bonjour %{world}') } + it 'succeeds with valid variables' do errors = [] - linter.validate_translation(errors, 'Hello %{world}', ['%{world}']) + linter.validate_translation(errors, entry) expect(errors).to be_empty end @@ -275,43 +326,80 @@ describe Gitlab::I18n::PoLinter do expect(FastGettext::Translation).to receive(:_) { raise 'broken' } - linter.validate_translation(errors, 'Hello', []) + linter.validate_translation(errors, entry) - expect(errors).to include('Failure translating to en with []: broken') + expect(errors).to include('Failure translating to en: broken') end it 'adds an error message when translating fails when translating with context' do + entry = fake_translation(msgid: 'Tests|Hello', translation: 'broken') errors = [] expect(FastGettext::Translation).to receive(:s_) { raise 'broken' } - linter.validate_translation(errors, 'Tests|Hello', []) + linter.validate_translation(errors, entry) - expect(errors).to include('Failure translating to en with []: broken') + expect(errors).to include('Failure translating to en: broken') end it "adds an error when trying to translate with incorrect variables when using unnamed variables" do + entry = fake_translation(msgid: 'Hello %s', translation: 'Hello %d') errors = [] - linter.validate_translation(errors, 'Hello %d', ['%s']) + linter.validate_translation(errors, entry) - expect(errors.first).to start_with("Failure translating to en with") + expect(errors.first).to start_with("Failure translating to en") end it "adds an error when trying to translate with named variables when unnamed variables are expected" do + entry = fake_translation(msgid: 'Hello %s', translation: 'Hello %{thing}') errors = [] - linter.validate_translation(errors, 'Hello %d', ['%{world}']) + linter.validate_translation(errors, entry) - expect(errors.first).to start_with("Failure translating to en with") + expect(errors.first).to start_with("Failure translating to en") end - it 'adds an error when translated with incorrect variables using named variables' do - errors = [] + it 'tests translation for all given forms' do + # Fake a language that has 3 forms to translate + fake_metadata = double + allow(fake_metadata).to receive(:forms_to_test).and_return(3) + allow(linter).to receive(:metadata_entry).and_return(fake_metadata) + entry = fake_translation( + msgid: '%d exception', + translation: '%d uitzondering', + plural_id: '%d exceptions', + plurals: ['%d uitzonderingen', '%d uitzonderingetjes'] + ) + + # Make each count use a different index + allow(linter).to receive(:index_for_pluralization).with(0).and_return(0) + allow(linter).to receive(:index_for_pluralization).with(1).and_return(1) + allow(linter).to receive(:index_for_pluralization).with(2).and_return(2) + + expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 0).and_call_original + expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 1).and_call_original + expect(FastGettext::Translation).to receive(:n_).with('%d exception', '%d exceptions', 2).and_call_original + + linter.validate_translation([], entry) + end + end + + describe '#numbers_covering_all_plurals' do + it 'can correctly find all required numbers to translate to Polish' do + # Polish used as an example with 3 different forms: + # 0, all plurals except the ones ending in 2,3,4: Kotów + # 1: Kot + # 2-3-4: Koty + # So translating with [0, 1, 2] will give us all different posibilities + fake_metadata = double + allow(fake_metadata).to receive(:forms_to_test).and_return(4) + allow(linter).to receive(:metadata_entry).and_return(fake_metadata) + allow(linter).to receive(:locale).and_return('pl_PL') - linter.validate_translation(errors, 'Hello %{thing}', ['%d']) + numbers = linter.numbers_covering_all_plurals - expect(errors.first).to start_with("Failure translating to en with") + expect(numbers).to contain_exactly(0, 1, 2) end end @@ -336,3 +424,4 @@ describe Gitlab::I18n::PoLinter do end end end +# rubocop:enable Style/AsciiComments diff --git a/spec/lib/gitlab/i18n/translation_entry_spec.rb b/spec/lib/gitlab/i18n/translation_entry_spec.rb index f68bc8feff9..b301e6ea443 100644 --- a/spec/lib/gitlab/i18n/translation_entry_spec.rb +++ b/spec/lib/gitlab/i18n/translation_entry_spec.rb @@ -109,7 +109,7 @@ describe Gitlab::I18n::TranslationEntry do data = { msgid: %w(hello world) } entry = described_class.new(data, 2) - expect(entry.msgid_contains_newlines?).to be_truthy + expect(entry.msgid_has_multiple_lines?).to be_truthy end end @@ -118,7 +118,7 @@ describe Gitlab::I18n::TranslationEntry do data = { msgid_plural: %w(hello world) } entry = described_class.new(data, 2) - expect(entry.plural_id_contains_newlines?).to be_truthy + expect(entry.plural_id_has_multiple_lines?).to be_truthy end end @@ -127,7 +127,7 @@ describe Gitlab::I18n::TranslationEntry do data = { msgstr: %w(hello world) } entry = described_class.new(data, 2) - expect(entry.translations_contain_newlines?).to be_truthy + expect(entry.translations_have_multiple_lines?).to be_truthy end end diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 548eb28fe4d..4059188fba1 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -135,6 +135,51 @@ describe Gitlab::Profiler do end end + describe '.clean_backtrace' do + it 'uses the Rails backtrace cleaner' do + backtrace = [] + + expect(Rails.backtrace_cleaner).to receive(:clean).with(backtrace) + + described_class.clean_backtrace(backtrace) + end + + it 'removes lines from IGNORE_BACKTRACES' do + backtrace = [ + "lib/gitlab/gitaly_client.rb:294:in `block (2 levels) in migrate'", + "lib/gitlab/gitaly_client.rb:331:in `allow_n_plus_1_calls'", + "lib/gitlab/gitaly_client.rb:280:in `block in migrate'", + "lib/gitlab/metrics/influx_db.rb:103:in `measure'", + "lib/gitlab/gitaly_client.rb:278:in `migrate'", + "lib/gitlab/git/repository.rb:1451:in `gitaly_migrate'", + "lib/gitlab/git/commit.rb:66:in `find'", + "app/models/repository.rb:1047:in `find_commit'", + "lib/gitlab/metrics/instrumentation.rb:159:in `block in find_commit'", + "lib/gitlab/metrics/method_call.rb:36:in `measure'", + "lib/gitlab/metrics/instrumentation.rb:159:in `find_commit'", + "app/models/repository.rb:113:in `commit'", + "lib/gitlab/i18n.rb:50:in `with_locale'", + "lib/gitlab/middleware/multipart.rb:95:in `call'", + "lib/gitlab/request_profiler/middleware.rb:14:in `call'", + "ee/lib/gitlab/database/load_balancing/rack_middleware.rb:37:in `call'", + "ee/lib/gitlab/jira/middleware.rb:15:in `call'" + ] + + expect(described_class.clean_backtrace(backtrace)) + .to eq([ + "lib/gitlab/gitaly_client.rb:294:in `block (2 levels) in migrate'", + "lib/gitlab/gitaly_client.rb:331:in `allow_n_plus_1_calls'", + "lib/gitlab/gitaly_client.rb:280:in `block in migrate'", + "lib/gitlab/gitaly_client.rb:278:in `migrate'", + "lib/gitlab/git/repository.rb:1451:in `gitaly_migrate'", + "lib/gitlab/git/commit.rb:66:in `find'", + "app/models/repository.rb:1047:in `find_commit'", + "app/models/repository.rb:113:in `commit'", + "ee/lib/gitlab/jira/middleware.rb:15:in `call'" + ]) + end + end + describe '.with_custom_logger' do context 'when the logger is set' do it 'uses the replacement logger for the duration of the block' do diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 155e1663298..c435f988cdd 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -498,34 +498,18 @@ describe Gitlab::Shell do ) end - context 'with gitaly' do - it 'returns true when the command succeeds' do - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository) - .with(repository.raw_repository) { :gitaly_response_object } - - is_expected.to be_truthy - end - - it 'return false when the command fails' do - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository) - .with(repository.raw_repository) { raise GRPC::BadStatus, 'bla' } + it 'returns true when the command succeeds' do + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository) + .with(repository.raw_repository) { :gitaly_response_object } - is_expected.to be_falsy - end + is_expected.to be_truthy end - context 'without gitaly', :disable_gitaly do - it 'returns true when the command succeeds' do - expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { true } - - is_expected.to be_truthy - end - - it 'return false when the command fails' do - expect(gitlab_projects).to receive(:fork_repository).with('nfs-file05', 'fork/path.git') { false } + it 'return false when the command fails' do + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:fork_repository) + .with(repository.raw_repository) { raise GRPC::BadStatus, 'bla' } - is_expected.to be_falsy - end + is_expected.to be_falsy end end @@ -665,7 +649,7 @@ describe Gitlab::Shell do subject do gitlab_shell.fetch_remote(repository.raw_repository, remote_name, - forced: true, no_tags: true, ssh_auth: ssh_auth) + forced: true, no_tags: true, ssh_auth: ssh_auth) end it 'passes the correct params to the gitaly service' do diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index 4ee1d255fbd..ac34efa4f9d 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -6,7 +6,11 @@ require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_ describe MigrateProcessCommitWorkerJobs do let(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:commit) { project.commit.raw.rugged_commit } + let(:commit) do + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.commit.raw.rugged_commit + end + end describe 'Project' do describe 'find_including_path' do diff --git a/spec/migrations/remove_soft_removed_objects_spec.rb b/spec/migrations/remove_soft_removed_objects_spec.rb index fb70c284f5e..d0bde98b80e 100644 --- a/spec/migrations/remove_soft_removed_objects_spec.rb +++ b/spec/migrations/remove_soft_removed_objects_spec.rb @@ -3,6 +3,18 @@ require Rails.root.join('db', 'post_migrate', '20171207150343_remove_soft_remove describe RemoveSoftRemovedObjects, :migration do describe '#up' do + let!(:groups) do + table(:namespaces).tap do |t| + t.inheritance_column = nil + end + end + + let!(:routes) do + table(:routes).tap do |t| + t.inheritance_column = nil + end + end + it 'removes various soft removed objects' do 5.times do create_with_deleted_at(:issue) @@ -28,19 +40,20 @@ describe RemoveSoftRemovedObjects, :migration do it 'removes routes of soft removed personal namespaces' do namespace = create_with_deleted_at(:namespace) - group = create(:group) # rubocop:disable RSpec/FactoriesInMigrationSpecs + group = groups.create!(name: 'group', path: 'group_path', type: 'Group') + routes.create!(source_id: group.id, source_type: 'Group', name: 'group', path: 'group_path') - expect(Route.where(source: namespace).exists?).to eq(true) - expect(Route.where(source: group).exists?).to eq(true) + expect(routes.where(source_id: namespace.id).exists?).to eq(true) + expect(routes.where(source_id: group.id).exists?).to eq(true) run_migration - expect(Route.where(source: namespace).exists?).to eq(false) - expect(Route.where(source: group).exists?).to eq(true) + expect(routes.where(source_id: namespace.id).exists?).to eq(false) + expect(routes.where(source_id: group.id).exists?).to eq(true) end it 'schedules the removal of soft removed groups' do - group = create_with_deleted_at(:group) + group = create_deleted_group admin = create(:user, admin: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs expect_any_instance_of(GroupDestroyWorker) @@ -51,7 +64,7 @@ describe RemoveSoftRemovedObjects, :migration do end it 'does not remove soft removed groups when no admin user could be found' do - create_with_deleted_at(:group) + create_deleted_group expect_any_instance_of(GroupDestroyWorker) .not_to receive(:perform) @@ -74,4 +87,13 @@ describe RemoveSoftRemovedObjects, :migration do row end + + def create_deleted_group + group = groups.create!(name: 'group', path: 'group_path', type: 'Group') + routes.create!(source_id: group.id, source_type: 'Group', name: 'group', path: 'group_path') + + groups.where(id: group.id).update_all(deleted_at: 1.year.ago) + + group + end end diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb index 560409f08de..5f5ba426d69 100644 --- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb +++ b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb @@ -49,10 +49,14 @@ describe TurnNestedGroupsIntoRegularGroupsForMysql do end it 'renames the repository of any projects' do - expect(updated_project.repository.path) + repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + updated_project.repository.path + end + + expect(repo_path) .to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git") - expect(File.directory?(updated_project.repository.path)).to eq(true) + expect(File.directory?(repo_path)).to eq(true) end it 'creates a redirect route for renamed projects' do diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index cbcf1e55979..b5a6d959ccb 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -54,14 +54,6 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do it { is_expected.to eq('Sample data in db') } end - - context 'when data_store is others' do - before do - build_trace_chunk.send(:write_attribute, :data_store, -1) - end - - it { expect { subject }.to raise_error('Unsupported data store') } - end end describe '#set_data' do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index b3797c1fb46..2d75422ee68 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -156,7 +156,7 @@ describe CacheMarkdownField do end it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) } + it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) } end describe '#cached_html_up_to_date?' do @@ -234,7 +234,7 @@ describe CacheMarkdownField do it 'returns default version when version is nil' do thing.cached_markdown_version = nil - is_expected.to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) + is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) end end @@ -261,7 +261,7 @@ describe CacheMarkdownField do thing.cached_markdown_version = nil thing.refresh_markdown_cache - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) + expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) end end @@ -346,7 +346,7 @@ describe CacheMarkdownField do expect(thing.foo_html).to eq(updated_html) expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) + expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) end end @@ -366,7 +366,7 @@ describe CacheMarkdownField do expect(thing.foo_html).to eq(updated_html) expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) + expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) end end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index b9f1c7dd5df..6c637533c6b 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -478,7 +478,7 @@ describe JiraService do create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png') props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title') - expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/favicon_main_dk.png$} + expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/dk.png$} end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 585cf7aab44..a2f8fac2f38 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2339,6 +2339,22 @@ describe Project do end end + describe '#any_lfs_file_locks?', :request_store do + set(:project) { create(:project) } + + it 'returns false when there are no LFS file locks' do + expect(project.any_lfs_file_locks?).to be_falsey + end + + it 'returns a cached true when there are LFS file locks' do + create(:lfs_file_lock, project: project) + + expect(project.lfs_file_locks).to receive(:any?).once.and_call_original + + 2.times { expect(project.any_lfs_file_locks?).to be_truthy } + end + end + describe '#protected_for?' do let(:project) { create(:project) } @@ -2943,7 +2959,7 @@ describe Project do project.rename_repo - expect(project.repository.rugged.config['gitlab.fullpath']).to eq(project.full_path) + expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) end end @@ -3104,7 +3120,7 @@ describe Project do it 'updates project full path in .git/config' do project.rename_repo - expect(project.repository.rugged.config['gitlab.fullpath']).to eq(project.full_path) + expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) end end @@ -3525,13 +3541,13 @@ describe Project do it 'writes full path in .git/config when key is missing' do project.write_repository_config - expect(project.repository.rugged.config['gitlab.fullpath']).to eq project.full_path + expect(rugged_config['gitlab.fullpath']).to eq project.full_path end it 'updates full path in .git/config when key is present' do project.write_repository_config(gl_full_path: 'old/path') - expect { project.write_repository_config }.to change { project.repository.rugged.config['gitlab.fullpath'] }.from('old/path').to(project.full_path) + expect { project.write_repository_config }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path) end it 'does not raise an error with an empty repository' do @@ -3817,4 +3833,10 @@ describe Project do let(:uploader_class) { AttachmentUploader } end end + + def rugged_config + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.repository.rugged.config + end + end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index f1142832f1a..a3c20b3b3c1 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -188,7 +188,11 @@ describe ProjectWiki do before do subject.wiki # Make sure the wiki repo exists - BareRepoOperations.new(subject.repository.path_to_repo).commit_file(image, 'image.png') + repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + subject.repository.path_to_repo + end + + BareRepoOperations.new(repo_path).commit_file(image, 'image.png') end it 'returns the latest version of the file if it exists' do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index 4c086eeadfc..3597b080021 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -74,7 +74,9 @@ describe RemoteMirror do mirror.update_attribute(:url, 'http://foo:baz@test.com') - config = repo.raw_repository.rugged.config + config = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repo.raw_repository.rugged.config + end expect(config["remote.#{mirror.remote_name}.url"]).to eq('http://foo:baz@test.com') end diff --git a/spec/requests/api/graphql/merge_request_query_spec.rb b/spec/requests/api/graphql/merge_request_query_spec.rb deleted file mode 100644 index 12b1d5d18a2..00000000000 --- a/spec/requests/api/graphql/merge_request_query_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'spec_helper' - -describe 'getting merge request information' do - include GraphqlHelpers - - let(:project) { create(:project, :repository) } - let(:merge_request) { create(:merge_request, source_project: project) } - let(:current_user) { create(:user) } - - let(:query) do - attributes = { - 'fullPath' => merge_request.project.full_path, - 'iid' => merge_request.iid - } - graphql_query_for('mergeRequest', attributes) - end - - context 'when the user has access to the merge request' do - before do - project.add_developer(current_user) - post_graphql(query, current_user: current_user) - end - - it 'returns the merge request' do - expect(graphql_data['mergeRequest']).not_to be_nil - end - - # This is a field coming from the `MergeRequestPresenter` - it 'includes a web_url' do - expect(graphql_data['mergeRequest']['webUrl']).to be_present - end - - it_behaves_like 'a working graphql query' - end - - context 'when the user does not have access to the merge request' do - before do - post_graphql(query, current_user: current_user) - end - - it 'returns an empty field' do - post_graphql(query, current_user: current_user) - - expect(graphql_data['mergeRequest']).to be_nil - end - - it_behaves_like 'a working graphql query' - end -end diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb index 8196bcfa87c..796ffc9d569 100644 --- a/spec/requests/api/graphql/project_query_spec.rb +++ b/spec/requests/api/graphql/project_query_spec.rb @@ -13,27 +13,76 @@ describe 'getting project information' do context 'when the user has access to the project' do before do project.add_developer(current_user) - post_graphql(query, current_user: current_user) end it 'includes the project' do + post_graphql(query, current_user: current_user) + expect(graphql_data['project']).not_to be_nil end - it_behaves_like 'a working graphql query' - end + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end - context 'when the user does not have access to the project' do - before do - post_graphql(query, current_user: current_user) + context 'when requesting a nested merge request' do + let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request_graphql_data) { graphql_data['project']['mergeRequest'] } + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('mergeRequest', iid: merge_request.iid) + ) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + it 'contains merge request information' do + post_graphql(query, current_user: current_user) + + expect(merge_request_graphql_data).not_to be_nil + end + + # This is a field coming from the `MergeRequestPresenter` + it 'includes a web_url' do + post_graphql(query, current_user: current_user) + + expect(merge_request_graphql_data['webUrl']).to be_present + end + + context 'when the user does not have access to the merge request' do + let(:project) { create(:project, :public, :repository) } + + it 'returns nil' do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) + + post_graphql(query) + + expect(merge_request_graphql_data).to be_nil + end + end end + end + context 'when the user does not have access to the project' do it 'returns an empty field' do post_graphql(query, current_user: current_user) expect(graphql_data['project']).to be_nil end - it_behaves_like 'a working graphql query' + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 7d923932309..da23fdd7dca 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -138,10 +138,15 @@ describe API::Groups do context "when using sorting" do let(:group3) { create(:group, name: "a#{group1.name}", path: "z#{group1.path}") } + let(:group4) { create(:group, name: "same-name", path: "y#{group1.path}") } + let(:group5) { create(:group, name: "same-name") } let(:response_groups) { json_response.map { |group| group['name'] } } + let(:response_groups_ids) { json_response.map { |group| group['id'] } } before do group3.add_owner(user1) + group4.add_owner(user1) + group5.add_owner(user1) end it "sorts by name ascending by default" do @@ -150,7 +155,7 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq([group3.name, group1.name]) + expect(response_groups).to eq(Group.visible_to_user(user1).order(:name).pluck(:name)) end it "sorts in descending order when passed" do @@ -159,16 +164,52 @@ describe API::Groups do expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq([group1.name, group3.name]) + expect(response_groups).to eq(Group.visible_to_user(user1).order(name: :desc).pluck(:name)) end - it "sorts by the order_by param" do + it "sorts by path in order_by param" do get api("/groups", user1), order_by: "path" expect(response).to have_gitlab_http_status(200) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(response_groups).to eq([group1.name, group3.name]) + expect(response_groups).to eq(Group.visible_to_user(user1).order(:path).pluck(:name)) + end + + it "sorts by id in the order_by param" do + get api("/groups", user1), order_by: "id" + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(response_groups).to eq(Group.visible_to_user(user1).order(:id).pluck(:name)) + end + + it "sorts also by descending id with pagination fix" do + get api("/groups", user1), order_by: "id", sort: "desc" + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(response_groups).to eq(Group.visible_to_user(user1).order(id: :desc).pluck(:name)) + end + + it "sorts identical keys by id for good pagination" do + get api("/groups", user1), search: "same-name", order_by: "name" + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(response_groups_ids).to eq(Group.select { |group| group['name'] == 'same-name' }.map { |group| group['id'] }.sort) + end + + it "sorts descending identical keys by id for good pagination" do + get api("/groups", user1), search: "same-name", order_by: "name", sort: "desc" + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(response_groups_ids).to eq(Group.select { |group| group['name'] == 'same-name' }.map { |group| group['id'] }.sort) end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index bc32372d3a9..a56b913198c 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -522,7 +522,6 @@ describe API::Internal do context 'the project path was changed' do let(:project) { create(:project, :repository, :legacy_storage) } - let!(:old_path_to_repo) { project.repository.path_to_repo } let!(:repository) { project.repository } before do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 16e6f19773f..e7639599874 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -351,11 +351,13 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when valid token is provided' do context 'when Runner is not active' do let(:runner) { create(:ci_runner, :inactive) } + let(:update_value) { runner.ensure_runner_queue_value } it 'returns 204 error' do request_job - expect(response).to have_gitlab_http_status 204 + expect(response).to have_gitlab_http_status(204) + expect(response.header['X-GitLab-Last-Update']).to eq(update_value) end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index a8f003b1073..e8cbf84e3be 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -272,8 +272,11 @@ describe Projects::CreateService, '#execute' do it 'writes project full path to .git/config' do project = create_project(user, opts) + rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.repository.rugged + end - expect(project.repository.rugged.config['gitlab.fullpath']).to eq project.full_path + expect(rugged.config['gitlab.fullpath']).to eq project.full_path end def create_project(user, opts) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8417b340de5..dac609e2545 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -87,6 +87,7 @@ RSpec.configure do |config| config.include LiveDebugger, :js config.include MigrationsHelpers, :migration config.include RedisHelpers + config.include Rails.application.routes.url_helpers, type: :routing if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb index 5a1dd44bc9d..614aaa73693 100644 --- a/spec/support/gitaly.rb +++ b/spec/support/gitaly.rb @@ -9,7 +9,7 @@ RSpec.configure do |config| # Use 'and_wrap_original' to make sure the arguments are valid allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_wrap_original do |m, *args| m.call(*args) - !Gitlab::GitalyClient::EXPLICIT_OPT_IN_REQUIRED.include?(args.first) + !Gitlab::GitalyClient.explicit_opt_in_required.include?(args.first) end end end diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb index 1a1d5853a7a..2b9f8b30c60 100644 --- a/spec/support/helpers/features/notes_helpers.rb +++ b/spec/support/helpers/features/notes_helpers.rb @@ -13,7 +13,7 @@ module Spec module Features module NotesHelpers def add_note(text) - Sidekiq::Testing.fake! do + perform_enqueued_jobs do page.within(".js-main-target-form") do fill_in("note[note]", with: text) find(".js-comment-submit-button").click diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 30ff9a1196a..0930b9da368 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -34,14 +34,20 @@ module GraphqlHelpers end def graphql_query_for(name, attributes = {}, fields = nil) + <<~QUERY + { + #{query_graphql_field(name, attributes, fields)} + } + QUERY + end + + def query_graphql_field(name, attributes = {}, fields = nil) fields ||= all_graphql_fields_for(name.classify) attributes = attributes_to_graphql(attributes) <<~QUERY - { #{name}(#{attributes}) { #{fields} } - } QUERY end @@ -50,12 +56,15 @@ module GraphqlHelpers return "" unless type type.fields.map do |name, field| + # We can't guess arguments, so skip fields that require them + next if field.arguments.any? + if scalar?(field) name else "#{name} { #{all_graphql_fields_for(field_type(field))} }" end - end.join("\n") + end.compact.join("\n") end def attributes_to_graphql(attributes) diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 84abec75c26..0bc235701eb 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -10,10 +10,6 @@ module MigrationsHelpers ActiveRecord::Migrator.migrations_paths end - def table_exists?(name) - ActiveRecord::Base.connection.table_exists?(name) - end - def migrations ActiveRecord::Migrator.migrations(migrations_paths) end diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb index ba7a1c8cde0..d23cbaf4beb 100644 --- a/spec/support/matchers/graphql_matchers.rb +++ b/spec/support/matchers/graphql_matchers.rb @@ -13,6 +13,12 @@ RSpec::Matchers.define :have_graphql_fields do |*expected| end end +RSpec::Matchers.define :have_graphql_field do |field_name| + match do |kls| + expect(kls.fields.keys).to include(GraphqlHelpers.fieldnamerize(field_name)) + end +end + RSpec::Matchers.define :have_graphql_arguments do |*expected| include GraphqlHelpers diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 2228e872926..7c34c7b4977 100644 --- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb @@ -245,6 +245,70 @@ RSpec.shared_examples 'slack or mattermost notifications' do end end + describe 'Push events' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository, creator: user) } + + before do + allow(chat_service).to receive_messages( + project: project, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + end + + context 'only notify for the default branch' do + context 'when enabled' do + before do + chat_service.notify_only_default_branch = true + end + + it 'does not notify push events if they are not for the default branch' do + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" + push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + + chat_service.execute(push_sample_data) + + expect(WebMock).not_to have_requested(:post, webhook_url) + end + + it 'notifies about push events for the default branch' do + push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) + + chat_service.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it 'still notifies about pushed tags' do + ref = "#{Gitlab::Git::TAG_REF_PREFIX}test" + push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + + chat_service.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'when disabled' do + before do + chat_service.notify_only_default_branch = false + end + + it 'notifies about all push events' do + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" + push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) + + chat_service.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + end + end + describe "Note events" do let(:user) { create(:user) } let(:project) { create(:project, :repository, creator: user) } @@ -394,23 +458,6 @@ RSpec.shared_examples 'slack or mattermost notifications' do expect(result).to be_falsy end - - it 'does not notify push events if they are not for the default branch' do - ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test" - push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, []) - - chat_service.execute(push_sample_data) - - expect(WebMock).not_to have_requested(:post, webhook_url) - end - - it 'notifies about push events for the default branch' do - push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) - - chat_service.execute(push_sample_data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end end context 'when disabled' do diff --git a/spec/support/shoulda/matchers/rails_shim.rb b/spec/support/shoulda/matchers/rails_shim.rb new file mode 100644 index 00000000000..8d70598beb5 --- /dev/null +++ b/spec/support/shoulda/matchers/rails_shim.rb @@ -0,0 +1,27 @@ +# monkey patch which fixes serialization matcher in Rails 5 +# https://github.com/thoughtbot/shoulda-matchers/issues/913 +# This can be removed when a new version of shoulda-matchers +# is released +module Shoulda + module Matchers + class RailsShim + def self.serialized_attributes_for(model) + if defined?(::ActiveRecord::Type::Serialized) + # Rails 5+ + serialized_columns = model.columns.select do |column| + model.type_for_attribute(column.name).is_a?( + ::ActiveRecord::Type::Serialized + ) + end + + serialized_columns.inject({}) do |hash, column| # rubocop:disable Style/EachWithObject + hash[column.name.to_s] = model.type_for_attribute(column.name).coder + hash + end + else + model.serialized_attributes + end + end + end + end +end diff --git a/spec/uploaders/favicon_uploader_spec.rb b/spec/uploaders/favicon_uploader_spec.rb deleted file mode 100644 index 37deea8ab90..00000000000 --- a/spec/uploaders/favicon_uploader_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' - -RSpec.describe FaviconUploader do - include CarrierWave::Test::Matchers - - let(:uploader) { described_class.new(build_stubbed(:user)) } - - after do - uploader.remove! - end - - def upload_fixture(filename) - fixture_file_upload("spec/fixtures/#{filename}") - end - - context 'versions' do - before do - uploader.store!(upload_fixture('dk.png')) - end - - it 'has the correct format' do - expect(uploader.favicon_main).to be_format('png') - end - - it 'has the correct dimensions' do - expect(uploader.favicon_main).to have_dimensions(32, 32) - end - end -end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 9e3b99b3502..2106959e23c 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -13,7 +13,7 @@ describe 'Every Sidekiq worker' do file_worker_queues = Gitlab::SidekiqConfig.worker_queues.to_set worker_queues = Gitlab::SidekiqConfig.workers.map(&:queue).to_set - worker_queues << ActionMailer::DeliveryJob.queue_name + worker_queues << ActionMailer::DeliveryJob.new.queue_name worker_queues << 'default' missing_from_file = worker_queues - file_worker_queues diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 807d1b8c084..e39dec556fc 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -11,36 +11,63 @@ describe GitGarbageCollectWorker do subject { described_class.new } describe "#perform" do - shared_examples 'flushing ref caches' do |gitaly| - context 'with active lease_uuid' do + context 'with active lease_uuid' do + before do + allow(subject).to receive(:get_lease_uuid).and_return(lease_uuid) + end + + it "flushes ref caches when the task if 'gc'" do + expect(subject).to receive(:renew_lease).with(lease_key, lease_uuid).and_call_original + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect) + .and_return(nil) + expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).to receive(:branch_names).and_call_original + expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original + expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original + + subject.perform(project.id, :gc, lease_key, lease_uuid) + end + end + + context 'with different lease than the active one' do + before do + allow(subject).to receive(:get_lease_uuid).and_return(SecureRandom.uuid) + end + + it 'returns silently' do + expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original + expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original + expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original + + subject.perform(project.id, :gc, lease_key, lease_uuid) + end + end + + context 'with no active lease' do + before do + allow(subject).to receive(:get_lease_uuid).and_return(false) + end + + context 'when is able to get the lease' do before do - allow(subject).to receive(:get_lease_uuid).and_return(lease_uuid) + allow(subject).to receive(:try_obtain_lease).and_return(SecureRandom.uuid) end it "flushes ref caches when the task if 'gc'" do - expect(subject).to receive(:renew_lease).with(lease_key, lease_uuid).and_call_original - expect(subject).to receive(:command).with(:gc).and_return([:the, :command]) - - if gitaly - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect) - .and_return(nil) - else - expect(Gitlab::Popen).to receive(:popen) - .with([:the, :command], project.repository.path_to_repo).and_return(["", 0]) - end - + expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect) + .and_return(nil) expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original expect_any_instance_of(Repository).to receive(:branch_names).and_call_original expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original - subject.perform(project.id, :gc, lease_key, lease_uuid) + subject.perform(project.id) end end - context 'with different lease than the active one' do + context 'when no lease can be obtained' do before do - allow(subject).to receive(:get_lease_uuid).and_return(SecureRandom.uuid) + expect(subject).to receive(:try_obtain_lease).and_return(false) end it 'returns silently' do @@ -49,63 +76,9 @@ describe GitGarbageCollectWorker do expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original - subject.perform(project.id, :gc, lease_key, lease_uuid) + subject.perform(project.id) end end - - context 'with no active lease' do - before do - allow(subject).to receive(:get_lease_uuid).and_return(false) - end - - context 'when is able to get the lease' do - before do - allow(subject).to receive(:try_obtain_lease).and_return(SecureRandom.uuid) - end - - it "flushes ref caches when the task if 'gc'" do - expect(subject).to receive(:command).with(:gc).and_return([:the, :command]) - - if gitaly - expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:garbage_collect) - .and_return(nil) - else - expect(Gitlab::Popen).to receive(:popen) - .with([:the, :command], project.repository.path_to_repo).and_return(["", 0]) - end - - expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original - expect_any_instance_of(Repository).to receive(:branch_names).and_call_original - expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original - expect_any_instance_of(Gitlab::Git::Repository).to receive(:has_visible_content?).and_call_original - - subject.perform(project.id) - end - end - - context 'when no lease can be obtained' do - before do - expect(subject).to receive(:try_obtain_lease).and_return(false) - end - - it 'returns silently' do - expect(subject).not_to receive(:command) - expect_any_instance_of(Repository).not_to receive(:after_create_branch).and_call_original - expect_any_instance_of(Repository).not_to receive(:branch_names).and_call_original - expect_any_instance_of(Repository).not_to receive(:has_visible_content?).and_call_original - - subject.perform(project.id) - end - end - end - end - - context "with Gitaly turned on" do - it_should_behave_like 'flushing ref caches', true - end - - context "with Gitaly turned off", :disable_gitaly do - it_should_behave_like 'flushing ref caches', false end context "repack_full" do diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 5d83397e8df..ac8716ecfb1 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -92,16 +92,6 @@ describe RepositoryForkWorker do end it_behaves_like 'RepositoryForkWorker performing' - - it 'logs a message about forking with old-style arguments' do - allow(subject).to receive(:gitlab_shell).and_return(shell) - expect(shell).to receive(:fork_repository) { true } - - allow(Rails.logger).to receive(:info).with(anything) # To compensate for other logs - expect(Rails.logger).to receive(:info).with("Project #{fork_project.id} is being forked using old-style arguments.") - - perform! - end end end end diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb index f22d7c1d073..5968c5da3c9 100644 --- a/spec/workers/repository_remove_remote_worker_spec.rb +++ b/spec/workers/repository_remove_remote_worker_spec.rb @@ -44,7 +44,9 @@ describe RepositoryRemoveRemoteWorker do end def create_remote_branch(remote_name, branch_name, target) - rugged = project.repository.rugged + rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + project.repository.rugged + end rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target.id) end end diff --git a/yarn.lock b/yarn.lock index 65d78173a5b..cefd7c9a62e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -270,11 +270,7 @@ acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" -acorn@^5.0.0, acorn@^5.3.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" - -acorn@^5.5.0: +acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0: version "5.6.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.6.2.tgz#b1da1d7be2ac1b4a327fb9eab851702c5045b4e7" @@ -4002,14 +3998,10 @@ icss-utils@^2.1.0: dependencies: postcss "^6.0.1" -ieee754@^1.1.11: +ieee754@^1.1.11, ieee754@^1.1.4: version "1.1.11" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" -ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" - iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" @@ -6247,11 +6239,7 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75" - -prettier@^1.11.1: +prettier@1.12.1, prettier@^1.11.1: version "1.12.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" |