diff options
349 files changed, 5311 insertions, 3890 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..f1c41c9bb76 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +Dangerfile gitlab-language=ruby diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 610a5ecba6d..e7b28e540a5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29" +image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29" .dedicated-runner: &dedicated-runner retry: 1 @@ -86,7 +86,9 @@ stages: .rails5: &rails5 allow_failure: true only: - - /rails5/ + variables: + - $CI_COMMIT_REF_NAME =~ /rails5/ + - $RAILS5_ENABLED variables: BUNDLE_GEMFILE: "Gemfile.rails5" RAILS5: "true" @@ -327,7 +329,7 @@ cloud-native-image: cache: {} script: - gem install gitlab --no-ri --no-rdoc - - ./scripts/trigger-build cng + - BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN scripts/trigger-build cng only: - tags@gitlab-org/gitlab-ce - tags@gitlab-org/gitlab-ee @@ -348,6 +350,25 @@ retrieve-tests-metadata: - wget -O $FLAKY_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$FLAKY_RSPEC_SUITE_REPORT_PATH || rm $FLAKY_RSPEC_SUITE_REPORT_PATH - '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}' +danger-review: + image: registry.gitlab.com/gitlab-org/gitaly/dangercontainer:latest + stage: prepare + allow_failure: true + before_script: + - source scripts/utils.sh + - retry gem install danger --no-ri --no-rdoc + cache: {} + only: + refs: + - branches@gitlab-org/gitlab-ce + - branches@gitlab-org/gitlab-ee + except: + variables: + - $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/ + script: + - git version + - danger --fail-on-errors=true + update-tests-metadata: <<: *tests-metadata-state <<: *only-canonical-masters diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd4e769ecee..4a1fa39b41d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,6 +50,7 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ - [Definition of done](#definition-of-done) - [Style guides](#style-guides) - [Code of conduct](#code-of-conduct) +- [Contribution Flow](#contribution-flow) <!-- END doctoc generated TOC please keep comment here to allow auto update --> @@ -225,24 +226,24 @@ 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 +### 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. +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. -| Label | Meaning | Estimate time to fix | Guidance | -|-------|-----------------|------------------------------------------------------------------|----------| -| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com | | -| ~P2 | High Priority | The next release | | -| ~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 | +| Label | Meaning | Estimate time to fix | +|-------|-----------------|------------------------------------------------------------------| +| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com | +| ~P2 | High Priority | The next release | +| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | +| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | -### Bug Severity labels +### Severity labels Severity labels help us clearly communicate the impact of a ~bug on users. -| Label | Meaning | Impact of the defect | Example | +| Label | Meaning | Impact on Functionality | Example | |-------|-------------------|-------------------------------------------------------|---------| | ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. | | ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. | @@ -251,12 +252,14 @@ Severity labels help us clearly communicate the impact of a ~bug on users. #### Severity impact guidance -| 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 | -| ~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 | +Severity levels can be applied further depending on the facet of the impact; e.g. Affected customers, GitLab.com availability, performance and etc. The below is a guideline. + +| Severity | Affected Customers/Users | GitLab.com Availability | Performance Degradation | +|----------|---------------------------------------------------------------------|----------------------------------------------------|------------------------------| +| ~S1 | >50% users affected (possible company extinction level event) | Significant impact on all of GitLab.com | | +| ~S2 | Many users or multiple paid customers affected (but not apocalyptic)| Significant impact on large portions of GitLab.com | Degradation is guaranteed to occur in the near future | +| ~S3 | A few users or a single paid customer affected | Limited impact on important portions of GitLab.com | Degradation is likely to occur in the near future | +| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on on GitLab.com | Degradation _may_ occur but it's not likely | ### Label for community contributors @@ -729,6 +732,24 @@ reported by emailing `contact@gitlab.com`. This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/). +## Contribution Flow + +When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty. + +When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged. + +When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance. + +Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future. + +GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches. + +GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach. + +When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it. + +When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process. + [core team]: https://about.gitlab.com/core-team/ [team]: https://about.gitlab.com/team/ [getting-help]: https://about.gitlab.com/getting-help/ diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 00000000000..84b72673c50 --- /dev/null +++ b/Dangerfile @@ -0,0 +1,6 @@ +danger.import_dangerfile(path: 'danger/metadata') +danger.import_dangerfile(path: 'danger/changes_size') +danger.import_dangerfile(path: 'danger/changelog') +danger.import_dangerfile(path: 'danger/specs') +danger.import_dangerfile(path: 'danger/gemfile') +danger.import_dangerfile(path: 'danger/database') diff --git a/Gemfile.lock b/Gemfile.lock index 1b8a777ceb4..7f9207d9dfe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -359,7 +359,7 @@ GEM grape-entity (0.7.1) activesupport (>= 4.0) multi_json (>= 1.3.2) - grape-path-helpers (1.0.5) + grape-path-helpers (1.0.6) activesupport (>= 4, < 5.1) grape (~> 1.0) rake (~> 12) diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index 1638e09132b..b0c85c2572e 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -2,13 +2,16 @@ import $ from 'jquery'; import { rstrip } from './lib/utils/common_utils'; function openConfirmDangerModal($form, text) { + const $input = $('.js-confirm-danger-input'); + $input.val(''); + $('.js-confirm-text').text(text || ''); - $('.js-confirm-danger-input').val(''); $('#modal-confirm-danger').modal('show'); const confirmTextMatch = $('.js-confirm-danger-match').text(); const $submit = $('.js-confirm-danger-submit'); $submit.disable(); + $input.focus(); $('.js-confirm-danger-input').off('input').on('input', function handleInput() { const confirmText = rstrip($(this).val()); diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 0327fceb38d..1d1415fe6ca 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -41,11 +41,6 @@ export default { required: true, }, }, - data() { - return { - activeFile: '', - }; - }, computed: { ...mapState({ isLoading: state => state.diffs.isLoading, @@ -126,14 +121,6 @@ export default { eventHub.$emit('fetchNotesData'); } }, - setActive(filePath) { - this.activeFile = filePath; - }, - unsetActive(filePath) { - if (this.activeFile === filePath) { - this.activeFile = ''; - } - }, adjustView() { if (this.shouldShow && this.isParallelView) { window.mrTabs.expandViewContainer(); @@ -195,7 +182,6 @@ export default { <changed-files :diff-files="diffFiles" - :active-file="activeFile" /> <div @@ -207,8 +193,6 @@ export default { :key="file.newPath" :file="file" :current-user="currentUser" - @setActive="setActive(file.filePath)" - @unsetActive="unsetActive(file.filePath)" /> </div> <no-changes v-else /> diff --git a/app/assets/javascripts/diffs/components/changed_files.vue b/app/assets/javascripts/diffs/components/changed_files.vue index 9d29357d800..97751db1254 100644 --- a/app/assets/javascripts/diffs/components/changed_files.vue +++ b/app/assets/javascripts/diffs/components/changed_files.vue @@ -16,13 +16,6 @@ export default { ClipboardButton, }, mixins: [changedFilesMixin], - props: { - activeFile: { - type: String, - required: false, - default: '', - }, - }, data() { return { isStuck: false, @@ -70,7 +63,7 @@ export default { pluralize, handleScroll() { if (!this.updating) { - requestAnimationFrame(this.updateIsStuck); + this.$nextTick(this.updateIsStuck); this.updating = true; } }, @@ -148,25 +141,8 @@ export default { /> <span - v-show="activeFile" - class="prepend-left-5" - > - <strong class="prepend-right-5"> - {{ truncatedDiffPath(activeFile) }} - </strong> - <clipboard-button - :text="activeFile" - :title="s__('Copy file name to clipboard')" - tooltip-placement="bottom" - tooltip-container="body" - class="btn btn-default btn-transparent btn-clipboard" - /> - </span> - - <span - v-show="!isStuck" - id="diff-stats" - class="diff-stats-additions-deletions-expanded" + class="js-diff-stats-additions-deletions-expanded + diff-stats-additions-deletions-expanded" > with <strong class="cgreen"> @@ -177,6 +153,17 @@ export default { {{ pluralize(`${sumRemovedLines} deletion`, sumRemovedLines) }} </strong> </span> + <div + class="js-diff-stats-additions-deletions-collapsed + diff-stats-additions-deletions-collapsed float-right d-sm-none" + > + <strong class="cgreen"> + +{{ sumAddedLines }} + </strong> + <strong class="cred"> + -{{ sumRemovedLines }} + </strong> + </div> </div> </div> </div> diff --git a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue index b38d217fbe3..045688a32bf 100644 --- a/app/assets/javascripts/diffs/components/changed_files_dropdown.vue +++ b/app/assets/javascripts/diffs/components/changed_files_dropdown.vue @@ -40,7 +40,7 @@ export default { {{ n__('%d changed file', '%d changed files', diffFiles.length) }} </span> <icon - :size="8" + class="caret-icon" name="chevron-down" /> </button> diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index a61e368249a..944084f05c9 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -25,7 +25,6 @@ export default { }, data() { return { - isActive: false, isLoadingCollapsedDiff: false, forkMessageVisible: false, }; @@ -48,12 +47,6 @@ export default { return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge; }, }, - mounted() { - document.addEventListener('scroll', this.handleScroll); - }, - beforeDestroy() { - document.removeEventListener('scroll', this.handleScroll); - }, methods: { ...mapActions('diffs', ['loadCollapsedDiff']), handleToggle() { @@ -65,36 +58,6 @@ export default { this.file.collapsed = !this.file.collapsed; } }, - handleScroll() { - if (!this.updating) { - requestAnimationFrame(this.scrollUpdate.bind(this)); - this.updating = true; - } - }, - scrollUpdate() { - const header = document.querySelector('.js-diff-files-changed'); - if (!header) { - this.updating = false; - return; - } - - const { top, bottom } = this.$el.getBoundingClientRect(); - const { top: topOfFixedHeader, bottom: bottomOfFixedHeader } = header.getBoundingClientRect(); - - const headerOverlapsContent = top < topOfFixedHeader && bottom > bottomOfFixedHeader; - const fullyAboveHeader = bottom < bottomOfFixedHeader; - const fullyBelowHeader = top > topOfFixedHeader; - - if (headerOverlapsContent && !this.isActive) { - this.$emit('setActive'); - this.isActive = true; - } else if (this.isActive && (fullyAboveHeader || fullyBelowHeader)) { - this.$emit('unsetActive'); - this.isActive = false; - } - - this.updating = false; - }, handleLoadCollapsedDiff() { this.isLoadingCollapsedDiff = true; diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 9f016e0338f..257a7432c20 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -1,6 +1,7 @@ <script> import Mousetrap from 'mousetrap'; import { mapActions, mapState, mapGetters } from 'vuex'; +import NewModal from './new_dropdown/modal.vue'; import IdeSidebar from './ide_side_bar.vue'; import RepoTabs from './repo_tabs.vue'; import IdeStatusBar from './ide_status_bar.vue'; @@ -13,6 +14,7 @@ const originalStopCallback = Mousetrap.stopCallback; export default { components: { + NewModal, IdeSidebar, RepoTabs, IdeStatusBar, @@ -137,5 +139,6 @@ export default { /> </div> <ide-status-bar :file="activeFile"/> + <new-modal /> </article> </template> diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 0582ad32e92..715dc1bfb42 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -5,6 +5,7 @@ import tooltip from '~/vue_shared/directives/tooltip'; import timeAgoMixin from '~/vue_shared/mixins/timeago'; import CiIcon from '../../vue_shared/components/ci_icon.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import { rightSidebarViews } from '../constants'; export default { components: { @@ -49,6 +50,7 @@ export default { this.stopPipelinePolling(); }, methods: { + ...mapActions(['setRightPane']), ...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']), startTimer() { this.intervalId = setInterval(() => { @@ -69,24 +71,31 @@ export default { return `${this.currentProject.web_url}/commit/${shortSha}`; }, }, + rightSidebarViews, }; </script> <template> <footer class="ide-status-bar"> <div - v-if="lastCommit && lastCommitFormatedAge" + v-if="lastCommit" class="ide-status-branch" > <span v-if="latestPipeline && latestPipeline.details" class="ide-status-pipeline" > - <ci-icon - v-tooltip - :status="latestPipeline.details.status" - :title="latestPipeline.details.status.text" - /> + <button + type="button" + class="p-0 border-0 h-50" + @click="setRightPane($options.rightSidebarViews.pipelines)" + > + <ci-icon + v-tooltip + :status="latestPipeline.details.status" + :title="latestPipeline.details.status.text" + /> + </button> Pipeline <a :href="latestPipeline.details.status.details_path" diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue index 8fc4ebe6ca6..0a95c0bb30d 100644 --- a/app/assets/javascripts/ide/components/ide_tree.vue +++ b/app/assets/javascripts/ide/components/ide_tree.vue @@ -1,12 +1,16 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; -import NewDropdown from './new_dropdown/index.vue'; +import Icon from '~/vue_shared/components/icon.vue'; import IdeTreeList from './ide_tree_list.vue'; +import Upload from './new_dropdown/upload.vue'; +import NewEntryButton from './new_dropdown/button.vue'; export default { components: { - NewDropdown, + Icon, + Upload, IdeTreeList, + NewEntryButton, }, computed: { ...mapState(['currentBranchId']), @@ -20,23 +24,42 @@ export default { } }, methods: { - ...mapActions(['updateViewer']), + ...mapActions(['updateViewer', 'openNewEntryModal', 'createTempEntry']), }, }; </script> <template> <ide-tree-list + header-class="d-flex w-100" viewer-type="editor" > <template slot="header" > {{ __('Edit') }} - <new-dropdown - :project-id="currentProject.name_with_namespace" - :branch="currentBranchId" - /> + <div class="ml-auto d-flex"> + <new-entry-button + :label="__('New file')" + :show-label="false" + class="d-flex border-0 p-0 mr-3" + icon="doc-new" + @click="openNewEntryModal({ type: 'blob' })" + /> + <upload + :show-label="false" + class="d-flex mr-3" + button-css-classes="border-0 p-0" + @create="createTempEntry" + /> + <new-entry-button + :label="__('New directory')" + :show-label="false" + class="d-flex border-0 p-0" + icon="folder-new" + @click="openNewEntryModal({ type: 'tree' })" + /> + </div> </template> </ide-tree-list> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/button.vue b/app/assets/javascripts/ide/components/new_dropdown/button.vue new file mode 100644 index 00000000000..7682b34ce4d --- /dev/null +++ b/app/assets/javascripts/ide/components/new_dropdown/button.vue @@ -0,0 +1,51 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { + Icon, + }, + props: { + label: { + type: String, + required: false, + default: null, + }, + icon: { + type: String, + required: true, + }, + iconClasses: { + type: String, + required: false, + default: null, + }, + showLabel: { + type: Boolean, + required: false, + default: true, + }, + }, + methods: { + clicked() { + this.$emit('click'); + }, + }, +}; +</script> + +<template> + <button + :aria-label="label" + type="button" + @click.stop.prevent="clicked" + > + <icon + :name="icon" + :css-classes="iconClasses" + /> + <template v-if="showLabel"> + {{ label }} + </template> + </button> +</template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index 1e398d7e1aa..c29e49ba766 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -3,12 +3,14 @@ import { mapActions } from 'vuex'; import icon from '~/vue_shared/components/icon.vue'; import newModal from './modal.vue'; import upload from './upload.vue'; +import ItemButton from './button.vue'; export default { components: { icon, newModal, upload, + ItemButton, }, props: { branch: { @@ -20,11 +22,13 @@ export default { required: false, default: '', }, + mouseOver: { + type: Boolean, + required: true, + }, }, data() { return { - openModal: false, - modalType: '', dropdownOpen: false, }; }, @@ -34,17 +38,18 @@ export default { this.$refs.dropdownMenu.scrollIntoView(); }); }, + mouseOver() { + if (!this.mouseOver) { + this.dropdownOpen = false; + } + }, }, methods: { - ...mapActions(['createTempEntry']), + ...mapActions(['createTempEntry', 'openNewEntryModal']), createNewItem(type) { - this.modalType = type; - this.openModal = true; + this.openNewEntryModal({ type, path: this.path }); this.dropdownOpen = false; }, - hideModal() { - this.openModal = false; - }, openDropdown() { this.dropdownOpen = !this.dropdownOpen; }, @@ -58,23 +63,19 @@ export default { :class="{ show: dropdownOpen, }" - class="dropdown" + class="dropdown d-flex" > <button + :aria-label="__('Create new file or directory')" type="button" - class="btn btn-sm btn-default dropdown-toggle add-to-tree" - aria-label="Create new file or directory" + class="rounded border-0 d-flex ide-entry-dropdown-toggle" @click.stop="openDropdown()" > <icon - :size="12" - name="plus" - css-classes="float-left" + name="hamburger" /> <icon - :size="12" name="arrow-down" - css-classes="float-left" /> </button> <ul @@ -82,39 +83,30 @@ export default { class="dropdown-menu dropdown-menu-right" > <li> - <a - href="#" - role="button" - @click.stop.prevent="createNewItem('blob')" - > - {{ __('New file') }} - </a> + <item-button + :label="__('New file')" + class="d-flex" + icon="doc-new" + icon-classes="mr-2" + @click="createNewItem('blob')" + /> </li> <li> <upload - :branch-id="branch" :path="path" @create="createTempEntry" /> </li> <li> - <a - href="#" - role="button" - @click.stop.prevent="createNewItem('tree')" - > - {{ __('New directory') }} - </a> + <item-button + :label="__('New directory')" + class="d-flex" + icon="folder-new" + icon-classes="mr-2" + @click="createNewItem('tree')" + /> </li> </ul> </div> - <new-modal - v-if="openModal" - :type="modalType" - :branch-id="branch" - :path="path" - @hide="hideModal" - @create="createTempEntry" - /> </div> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 1e9668d5154..1867b7980d2 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -1,78 +1,70 @@ <script> import { __ } from '~/locale'; -import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; +import { mapActions, mapState } from 'vuex'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; export default { components: { - DeprecatedModal, - }, - props: { - branchId: { - type: String, - required: true, - }, - type: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, + GlModal, }, data() { return { - entryName: this.path !== '' ? `${this.path}/` : '', + name: '', }; }, computed: { + ...mapState(['newEntryModal']), + entryName: { + get() { + return this.name || (this.newEntryModal.path !== '' ? `${this.newEntryModal.path}/` : ''); + }, + set(val) { + this.name = val; + }, + }, modalTitle() { - if (this.type === 'tree') { + if (this.newEntryModal.type === 'tree') { return __('Create new directory'); } return __('Create new file'); }, buttonLabel() { - if (this.type === 'tree') { + if (this.newEntryModal.type === 'tree') { return __('Create directory'); } return __('Create file'); }, }, - mounted() { - this.$refs.fieldName.focus(); - }, methods: { + ...mapActions(['createTempEntry']), createEntryInStore() { - this.$emit('create', { - branchId: this.branchId, - name: this.entryName, - type: this.type, + this.createTempEntry({ + name: this.name, + type: this.newEntryModal.type, }); - - this.hideModal(); }, - hideModal() { - this.$emit('hide'); + focusInput() { + setTimeout(() => { + this.$refs.fieldName.focus(); + }); }, }, }; </script> <template> - <deprecated-modal - :title="modalTitle" - :primary-button-label="buttonLabel" - kind="success" - @cancel="hideModal" + <gl-modal + id="ide-new-entry" + :header-title-text="modalTitle" + :footer-primary-button-text="buttonLabel" + footer-primary-button-variant="success" @submit="createEntryInStore" + @open="focusInput" > - <form - slot="body" + <div class="form-group row" - @submit.prevent="createEntryInStore" > <label class="label-light col-form-label col-sm-3"> {{ __('Name') }} @@ -85,6 +77,6 @@ export default { class="form-control" /> </div> - </form> - </deprecated-modal> + </div> + </gl-modal> </template> diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue index 677b282bd61..5b1743bb30e 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue @@ -1,71 +1,85 @@ <script> - export default { - props: { - branchId: { - type: String, - required: true, - }, - path: { - type: String, - required: false, - default: '', - }, +import Icon from '~/vue_shared/components/icon.vue'; +import ItemButton from './button.vue'; + +export default { + components: { + Icon, + ItemButton, + }, + props: { + path: { + type: String, + required: false, + default: '', }, - mounted() { - this.$refs.fileUpload.addEventListener('change', this.openFile); + showLabel: { + type: Boolean, + required: false, + default: true, }, - beforeDestroy() { - this.$refs.fileUpload.removeEventListener('change', this.openFile); + buttonCssClasses: { + type: String, + required: false, + default: null, }, - methods: { - createFile(target, file, isText) { - const { name } = file; - let { result } = target; + }, + mounted() { + this.$refs.fileUpload.addEventListener('change', this.openFile); + }, + beforeDestroy() { + this.$refs.fileUpload.removeEventListener('change', this.openFile); + }, + methods: { + createFile(target, file, isText) { + const { name } = file; + let { result } = target; - if (!isText) { - // eslint-disable-next-line prefer-destructuring - result = result.split('base64,')[1]; - } + if (!isText) { + // eslint-disable-next-line prefer-destructuring + result = result.split('base64,')[1]; + } - this.$emit('create', { - name: `${(this.path ? `${this.path}/` : '')}${name}`, - branchId: this.branchId, - type: 'blob', - content: result, - base64: !isText, - }); - }, - readFile(file) { - const reader = new FileReader(); - const isText = file.type.match(/text.*/) !== null; + this.$emit('create', { + name: `${this.path ? `${this.path}/` : ''}${name}`, + type: 'blob', + content: result, + base64: !isText, + }); + }, + readFile(file) { + const reader = new FileReader(); + const isText = file.type.match(/text.*/) !== null; - reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); + reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); - if (isText) { - reader.readAsText(file); - } else { - reader.readAsDataURL(file); - } - }, - openFile() { - Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); - }, - startFileUpload() { - this.$refs.fileUpload.click(); - }, + if (isText) { + reader.readAsText(file); + } else { + reader.readAsDataURL(file); + } + }, + openFile() { + Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); }, - }; + startFileUpload() { + this.$refs.fileUpload.click(); + }, + }, +}; </script> <template> <div> - <a - href="#" - role="button" - @click.stop.prevent="startFileUpload" - > - {{ __('Upload file') }} - </a> + <item-button + :class="buttonCssClasses" + :show-label="showLabel" + :icon-classes="showLabel ? 'mr-2' : ''" + :label="__('Upload file')" + class="d-flex" + icon="upload" + @click="startFileUpload" + /> <input id="file-upload" ref="fileUpload" diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index f490a3a2a39..3b4dd5ae9aa 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -40,6 +40,11 @@ export default { default: false, }, }, + data() { + return { + mouseOver: false, + }; + }, computed: { ...mapGetters([ 'getChangesInFolder', @@ -142,6 +147,9 @@ export default { hasUrlAtCurrentRoute() { return this.$router.currentRoute.path === `/project${this.file.url}`; }, + toggleHover(over) { + this.mouseOver = over; + }, }, }; </script> @@ -153,6 +161,8 @@ export default { class="file" role="button" @click="clickFile" + @mouseover="toggleHover(true)" + @mouseout="toggleHover(false)" > <div class="file-name" @@ -206,6 +216,7 @@ export default { :project-id="file.projectId" :branch="file.branchId" :path="file.path" + :mouse-over="mouseOver" class="float-right prepend-left-8" /> </div> diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index cc8dbb942d8..44c35e9a5a5 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -101,6 +101,7 @@ router.beforeEach((to, from, next) => { store .dispatch('getMergeRequestData', { projectId: fullProjectId, + targetProjectId: to.query.target_project, mergeRequestId: to.params.mrid, }) .then(mr => { @@ -119,12 +120,14 @@ router.beforeEach((to, from, next) => { .then(() => store.dispatch('getMergeRequestVersions', { projectId: fullProjectId, + targetProjectId: to.query.target_project, mergeRequestId: to.params.mrid, }), ) .then(() => store.dispatch('getMergeRequestChanges', { projectId: fullProjectId, + targetProjectId: to.query.target_project, mergeRequestId: to.params.mrid, }), ) diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index 5e91fa915ff..b5bd6f5a6bb 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -52,7 +52,7 @@ export const setResizingStatus = ({ commit }, resizing) => { export const createTempEntry = ( { state, commit, dispatch }, - { branchId, name, type, content = '', base64 = false }, + { name, type, content = '', base64 = false }, ) => new Promise(resolve => { const worker = new FilesDecoratorWorker(); @@ -81,7 +81,7 @@ export const createTempEntry = ( commit(types.CREATE_TMP_ENTRY, { data, projectId: state.currentProjectId, - branchId, + branchId: state.currentBranchId, }); if (type === 'blob') { @@ -100,7 +100,7 @@ export const createTempEntry = ( worker.postMessage({ data: [fullName], projectId: state.currentProjectId, - branchId, + branchId: state.currentBranchId, type, tempFile: true, base64, @@ -178,6 +178,13 @@ export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links); export const setErrorMessage = ({ commit }, errorMessage) => commit(types.SET_ERROR_MESSAGE, errorMessage); +export const openNewEntryModal = ({ commit }, { type, path = '' }) => { + commit(types.OPEN_NEW_ENTRY_MODAL, { type, path }); + + // open the modal manually so we don't mess around with dropdown/rows + $('#ide-new-entry').modal('show'); +}; + export * from './actions/tree'; export * from './actions/file'; export * from './actions/project'; diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js index 6bdf9dc3028..1887b77b00b 100644 --- a/app/assets/javascripts/ide/stores/actions/merge_request.js +++ b/app/assets/javascripts/ide/stores/actions/merge_request.js @@ -4,12 +4,14 @@ import * as types from '../mutation_types'; export const getMergeRequestData = ( { commit, dispatch, state }, - { projectId, mergeRequestId, force = false } = {}, + { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, ) => new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId] || force) { service - .getProjectMergeRequestData(projectId, mergeRequestId, { render_html: true }) + .getProjectMergeRequestData(targetProjectId || projectId, mergeRequestId, { + render_html: true, + }) .then(({ data }) => { commit(types.SET_MERGE_REQUEST, { projectPath: projectId, @@ -38,12 +40,12 @@ export const getMergeRequestData = ( export const getMergeRequestChanges = ( { commit, dispatch, state }, - { projectId, mergeRequestId, force = false } = {}, + { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, ) => new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId].changes.length || force) { service - .getProjectMergeRequestChanges(projectId, mergeRequestId) + .getProjectMergeRequestChanges(targetProjectId || projectId, mergeRequestId) .then(({ data }) => { commit(types.SET_MERGE_REQUEST_CHANGES, { projectPath: projectId, @@ -71,12 +73,12 @@ export const getMergeRequestChanges = ( export const getMergeRequestVersions = ( { commit, dispatch, state }, - { projectId, mergeRequestId, force = false } = {}, + { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, ) => new Promise((resolve, reject) => { if (!state.projects[projectId].mergeRequests[mergeRequestId].versions.length || force) { service - .getProjectMergeRequestVersions(projectId, mergeRequestId) + .getProjectMergeRequestVersions(targetProjectId || projectId, mergeRequestId) .then(res => res.data) .then(data => { commit(types.SET_MERGE_REQUEST_VERSIONS, { 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 cdd8076952f..6ef938b0ae2 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js @@ -31,7 +31,7 @@ export const fetchMergeRequests = ({ dispatch, state: { state } }, { type, searc dispatch('requestMergeRequests', type); dispatch('resetMergeRequests', type); - Api.mergeRequests({ scope, state, search }) + return Api.mergeRequests({ scope, state, search }) .then(({ data }) => dispatch('receiveMergeRequestsSuccess', { type, data })) .catch(() => dispatch('receiveMergeRequestsError', { type, search })); }; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 8cb01f25223..3e67b222e66 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -102,7 +102,7 @@ export const receiveJobsSuccess = ({ commit }, { id, data }) => export const fetchJobs = ({ dispatch }, stage) => { dispatch('requestJobs', stage.id); - axios + return axios .get(stage.dropdownPath) .then(({ data }) => dispatch('receiveJobsSuccess', { id: stage.id, data })) .catch(() => dispatch('receiveJobsError', stage)); diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index 555802e1811..8d6f9ccaf34 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -74,3 +74,5 @@ export const CLEAR_PROJECTS = 'CLEAR_PROJECTS'; export const RESET_OPEN_FILES = 'RESET_OPEN_FILES'; export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE'; + +export const OPEN_NEW_ENTRY_MODAL = 'OPEN_NEW_ENTRY_MODAL'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 702be2140e2..f8091f5b5e0 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -166,6 +166,11 @@ export default { [types.SET_ERROR_MESSAGE](state, errorMessage) { Object.assign(state, { errorMessage }); }, + [types.OPEN_NEW_ENTRY_MODAL](state, { type, path }) { + Object.assign(state, { + newEntryModal: { type, path }, + }); + }, ...projectMutations, ...mergeRequestMutation, ...fileMutations, diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index be229b2c723..0f32a267469 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -26,4 +26,8 @@ export default () => ({ rightPane: null, links: {}, errorMessage: null, + newEntryModal: { + type: '', + path: '', + }, }); diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 9aa83ce6269..ff19b9a9c30 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -39,7 +39,6 @@ export default class Todos { } initFilters() { - this.initFilterDropdown($('.js-group-search'), 'group_id', ['text']); this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); this.initFilterDropdown($('.js-type-search'), 'type'); this.initFilterDropdown($('.js-action-search'), 'action_id'); @@ -54,16 +53,7 @@ export default class Todos { filterable: searchFields ? true : false, search: { fields: searchFields }, data: $dropdown.data('data'), - clicked: () => { - const $formEl = $dropdown.closest('form.filter-form'); - const mutexDropdowns = { - group_id: 'project_id', - project_id: 'group_id', - }; - - $formEl.find(`input[name="${mutexDropdowns[fieldName]}"]`).remove(); - $formEl.submit(); - }, + clicked: () => $dropdown.closest('form.filter-form').submit(), }); } diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index b76965f280b..0fdb0a080cf 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -1,13 +1,10 @@ <script> import $ from 'jquery'; -import PerformanceBarService from '../services/performance_bar_service'; import detailedMetric from './detailed_metric.vue'; import requestSelector from './request_selector.vue'; import simpleMetric from './simple_metric.vue'; -import Flash from '../../flash'; - export default { components: { detailedMetric, @@ -69,37 +66,13 @@ export default { }, }, mounted() { - this.interceptor = PerformanceBarService.registerInterceptor( - this.peekUrl, - this.loadRequestDetails, - ); - - this.loadRequestDetails(this.requestId, window.location.href); this.currentRequest = this.requestId; if (this.lineProfileModal.length) { this.lineProfileModal.modal('toggle'); } }, - beforeDestroy() { - PerformanceBarService.removeInterceptor(this.interceptor); - }, methods: { - loadRequestDetails(requestId, requestUrl) { - if (!this.store.canTrackRequest(requestUrl)) { - return; - } - - this.store.addRequest(requestId, requestUrl); - - PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId) - .then(res => { - this.store.addRequestDetails(requestId, res.data.data); - }) - .catch(() => - Flash(`Error getting performance bar results for ${requestId}`), - ); - }, changeCurrentRequest(newRequestId) { this.currentRequest = newRequestId; }, diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js index 4a98aed7679..41880d27516 100644 --- a/app/assets/javascripts/performance_bar/index.js +++ b/app/assets/javascripts/performance_bar/index.js @@ -1,12 +1,13 @@ import Vue from 'vue'; -import performanceBarApp from './components/performance_bar_app.vue'; +import Flash from '../flash'; +import PerformanceBarService from './services/performance_bar_service'; import PerformanceBarStore from './stores/performance_bar_store'; export default ({ container }) => new Vue({ el: container, components: { - performanceBarApp, + performanceBarApp: () => import('./components/performance_bar_app.vue'), }, data() { const performanceBarData = document.querySelector(this.$options.el) @@ -21,6 +22,34 @@ export default ({ container }) => profileUrl: performanceBarData.profileUrl, }; }, + mounted() { + this.interceptor = PerformanceBarService.registerInterceptor( + this.peekUrl, + this.loadRequestDetails, + ); + + this.loadRequestDetails(this.requestId, window.location.href); + }, + beforeDestroy() { + PerformanceBarService.removeInterceptor(this.interceptor); + }, + methods: { + loadRequestDetails(requestId, requestUrl) { + if (!this.store.canTrackRequest(requestUrl)) { + return; + } + + this.store.addRequest(requestId, requestUrl); + + PerformanceBarService.fetchRequestDetails(this.peekUrl, requestId) + .then(res => { + this.store.addRequestDetails(requestId, res.data.data); + }) + .catch(() => + Flash(`Error getting performance bar results for ${requestId}`), + ); + }, + }, render(createElement) { return createElement('performance-bar-app', { props: { diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue deleted file mode 100644 index ffaed9c7193..00000000000 --- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue +++ /dev/null @@ -1,98 +0,0 @@ -<script> -import { __ } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; - -import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; - -const MARK_TEXT = __('Mark todo as done'); -const TODO_TEXT = __('Add todo'); - -export default { - directives: { - tooltip, - }, - components: { - Icon, - LoadingIcon, - }, - props: { - issuableId: { - type: Number, - required: true, - }, - issuableType: { - type: String, - required: true, - }, - isTodo: { - type: Boolean, - required: false, - default: true, - }, - isActionActive: { - type: Boolean, - required: false, - default: false, - }, - collapsed: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - buttonClasses() { - return this.collapsed ? - 'btn-blank btn-todo sidebar-collapsed-icon dont-change-state' : - 'btn btn-default btn-todo issuable-header-btn float-right'; - }, - buttonLabel() { - return this.isTodo ? MARK_TEXT : TODO_TEXT; - }, - collapsedButtonIconClasses() { - return this.isTodo ? 'todo-undone' : ''; - }, - collapsedButtonIcon() { - return this.isTodo ? 'todo-done' : 'todo-add'; - }, - }, - methods: { - handleButtonClick() { - this.$emit('toggleTodo'); - }, - }, -}; -</script> - -<template> - <button - v-tooltip - :class="buttonClasses" - :title="buttonLabel" - :aria-label="buttonLabel" - :data-issuable-id="issuableId" - :data-issuable-type="issuableType" - type="button" - data-container="body" - data-placement="left" - data-boundary="viewport" - @click="handleButtonClick" - > - <icon - v-show="collapsed" - :css-classes="collapsedButtonIconClasses" - :name="collapsedButtonIcon" - /> - <span - v-show="!collapsed" - class="issuable-todo-inner" - > - {{ buttonLabel }} - </span> - <loading-icon - v-show="isActionActive" - :inline="true" - /> - </button> -</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index c18b74743e4..a4c2289c590 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -1,7 +1,7 @@ <script> import tooltip from '~/vue_shared/directives/tooltip'; import { n__ } from '~/locale'; -import { webIDEUrl } from '~/lib/utils/url_utility'; +import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; import Icon from '~/vue_shared/components/icon.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; @@ -43,7 +43,10 @@ export default { return this.isBranchTitleLong(this.mr.targetBranch); }, webIdePath() { - return webIDEUrl(this.mr.statusPath.replace('.json', '')); + return mergeUrlParams({ + target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ? + this.mr.targetProjectFullPath : '', + }, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`)); }, }, methods: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 55b87f3a8ec..9aff95dcfec 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -32,7 +32,7 @@ }; </script> <template> - <div class="space-children d-flex append-right-10"> + <div class="space-children d-flex append-right-10 widget-status-icon"> <div v-if="isLoading" class="mr-widget-icon" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index fe777a07189..a5ca7b719a1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -233,7 +233,7 @@ export default { <status-icon :status="iconClass" /> <div class="media-body"> <div class="mr-widget-body-controls media space-children"> - <span class="btn-group append-bottom-5"> + <span class="btn-group"> <button :disabled="isMergeButtonDisabled" :class="mergeButtonClass" 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 c881cd496d1..e84c436905d 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 @@ -16,10 +16,11 @@ export default class MergeRequestStore { const pipelineStatus = data.pipeline ? data.pipeline.details.status : null; this.squash = data.squash; - this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath || - data.squash_before_merge_help_path; + this.squashBeforeMergeHelpPath = + this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path; this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true; + this.iid = data.iid; this.title = data.title; this.targetBranch = data.target_branch; this.sourceBranch = data.source_branch; @@ -85,6 +86,8 @@ export default class MergeRequestStore { this.isMergeAllowed = data.mergeable || false; this.mergeOngoing = data.merge_ongoing; this.allowCollaboration = data.allow_collaboration; + this.targetProjectFullPath = data.target_project_full_path; + this.sourceProjectFullPath = data.source_project_full_path; // Cherry-pick and Revert actions related this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false; @@ -97,7 +100,8 @@ export default class MergeRequestStore { this.hasCI = data.has_ci; this.ciStatus = data.ci_status; this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; - this.isPipelinePassing = this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings'; + this.isPipelinePassing = + this.ciStatus === 'success' || this.ciStatus === 'success_with_warnings'; this.isPipelineSkipped = this.ciStatus === 'skipped'; this.pipelineDetailedStatus = pipelineStatus; this.isPipelineActive = data.pipeline ? data.pipeline.active : false; diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue index b298b989203..416eda796a7 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -45,6 +45,11 @@ export default { emitSubmit(event) { this.$emit('submit', event); }, + opened({ propertyName }) { + if (propertyName === 'opacity') { + this.$emit('open'); + } + }, }, }; </script> @@ -55,6 +60,7 @@ export default { class="modal fade" tabindex="-1" role="dialog" + @transitionend="opened" > <div :class="modalSizeClass" diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.vue b/app/assets/javascripts/vue_shared/components/memory_graph.vue index 522091ea889..552a92541be 100644 --- a/app/assets/javascripts/vue_shared/components/memory_graph.vue +++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue @@ -126,7 +126,7 @@ export default { :cx="dotX" :cy="dotY" r="1.5" - tranform="translate(0 -1)" + transform="translate(0 -1)" /> </svg> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue index 80dc7d3557c..ac2e99abe77 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue @@ -12,11 +12,6 @@ export default { type: Boolean, required: true, }, - cssClasses: { - type: String, - required: false, - default: '', - }, }, computed: { tooltipLabel() { @@ -35,12 +30,10 @@ export default { <button v-tooltip :title="tooltipLabel" - :class="cssClasses" type="button" class="btn btn-blank gutter-toggle btn-sidebar-action" data-container="body" data-placement="left" - data-boundary="viewport" @click="toggle" > <i diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 9dbb04e5443..8bab8cf36b1 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -321,11 +321,18 @@ } &.activities { + display: flex; border-bottom: 1px solid $border-color; + overflow: hidden; .nav-links { border-bottom: 0; } + + @include media-breakpoint-down(xs) { + display: block; + overflow: visible; + } } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 7e89f8998fb..5e39bbb9890 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -518,6 +518,12 @@ outline: none; color: $gl-link-hover-color; } + + .caret-icon { + position: relative; + top: 2px; + left: -1px; + } } // Mobile diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index f6617380cc0..f9fd9f1ab8b 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -449,7 +449,6 @@ .todo-undone { color: $gl-link-color; - fill: $gl-link-color; } .author { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c32049e1b33..5835b8b8c9b 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -116,10 +116,8 @@ .modify-merge-commit-link { padding: 0; - background-color: transparent; border: 0; - color: $gl-text-color; &:hover, @@ -216,6 +214,10 @@ } } + .widget-status-icon { + align-self: flex-start; + } + .mr-widget-body { line-height: 28px; @@ -501,10 +503,6 @@ } } -.merge-request-details .content-block { - border-bottom: 0; -} - .mr-source-target { display: flex; flex-wrap: wrap; diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 6e2b285285a..8b1227b9131 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -44,6 +44,7 @@ padding-bottom: $grid-size; .file { + height: 32px; cursor: pointer; &.file-active { @@ -716,32 +717,6 @@ justify-content: center; } -.ide-new-btn { - .btn { - padding-top: 3px; - padding-bottom: 3px; - } - - .dropdown { - display: flex; - } - - .dropdown-toggle svg { - top: 0; - } - - .dropdown-menu { - left: auto; - right: 0; - - label { - font-weight: $gl-font-weight-normal; - padding: 5px 8px; - margin-bottom: 0; - } - } -} - .ide { overflow: hidden; @@ -1340,3 +1315,24 @@ overflow: auto; } } + +.ide-entry-dropdown-toggle { + padding: $gl-padding-4; + background-color: $theme-gray-100; + + &:hover { + background-color: $theme-gray-200; + } + + &:active, + &:focus { + color: $white-normal; + background-color: $blue-500; + outline: 0; + } +} + +.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle { + color: $white-normal; + background-color: $blue-500; +} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 010a2c05a1c..e5d7dd13915 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -174,18 +174,6 @@ } } -@include media-breakpoint-down(lg) { - .todos-filters { - .filter-categories { - width: 75%; - - .filter-item { - margin-bottom: 10px; - } - } - } -} - @include media-breakpoint-down(xs) { .todo { .avatar { @@ -211,10 +199,6 @@ } .todos-filters { - .filter-categories { - width: auto; - } - .dropdown-menu-toggle { width: 100%; } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 21cc6dfdd16..f45fcd4d900 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -30,7 +30,13 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception, prepend: true helper_method :can? - helper_method :import_sources_enabled?, :github_import_enabled?, :gitea_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled? + helper_method :import_sources_enabled?, :github_import_enabled?, + :gitea_import_enabled?, :github_import_configured?, + :gitlab_import_enabled?, :gitlab_import_configured?, + :bitbucket_import_enabled?, :bitbucket_import_configured?, + :google_code_import_enabled?, :fogbugz_import_enabled?, + :git_import_enabled?, :gitlab_project_import_enabled?, + :manifest_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -351,6 +357,10 @@ class ApplicationController < ActionController::Base Gitlab::CurrentSettings.import_sources.include?('gitlab_project') end + def manifest_import_enabled? + Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest') + end + # U2F (universal 2nd factor) devices need a unique identifier for the application # to perform authentication. # https://developers.yubico.com/U2F/App_ID.html diff --git a/app/controllers/concerns/todos_actions.rb b/app/controllers/concerns/todos_actions.rb deleted file mode 100644 index c0acdb3498d..00000000000 --- a/app/controllers/concerns/todos_actions.rb +++ /dev/null @@ -1,12 +0,0 @@ -module TodosActions - extend ActiveSupport::Concern - - def create - todo = TodoService.new.mark_todo(issuable, current_user) - - render json: { - count: TodosFinder.new(current_user, state: :pending).execute.count, - delete_path: dashboard_todo_path(todo) - } - end -end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index bd7111e28bc..f9e8fe624e8 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -70,7 +70,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController end def todo_params - params.permit(:action_id, :author_id, :project_id, :type, :sort, :state, :group_id) + params.permit(:action_id, :author_id, :project_id, :type, :sort, :state) end def redirect_out_of_range(todos) diff --git a/app/controllers/import/manifest_controller.rb b/app/controllers/import/manifest_controller.rb new file mode 100644 index 00000000000..e5a719fa0df --- /dev/null +++ b/app/controllers/import/manifest_controller.rb @@ -0,0 +1,93 @@ +class Import::ManifestController < Import::BaseController + before_action :whitelist_query_limiting, only: [:create] + before_action :verify_import_enabled + before_action :ensure_import_vars, only: [:create, :status] + + def new + end + + def status + @already_added_projects = find_already_added_projects + already_added_import_urls = @already_added_projects.pluck(:import_url) + + @pending_repositories = repositories.to_a.reject do |repository| + already_added_import_urls.include?(repository[:url]) + end + end + + def upload + group = Group.find(params[:group_id]) + + unless can?(current_user, :create_projects, group) + @errors = ["You don't have enough permissions to create projects in the selected group"] + + render :new && return + end + + manifest = Gitlab::ManifestImport::Manifest.new(params[:manifest].tempfile) + + if manifest.valid? + session[:manifest_import_repositories] = manifest.projects + session[:manifest_import_group_id] = group.id + + redirect_to status_import_manifest_path + else + @errors = manifest.errors + + render :new + end + end + + def jobs + render json: find_jobs + end + + def create + repository = repositories.find do |project| + project[:id] == params[:repo_id].to_i + end + + project = Gitlab::ManifestImport::ProjectCreator.new(repository, group, current_user).execute + + if project.persisted? + render json: ProjectSerializer.new.represent(project) + else + render json: { errors: project_save_error(project) }, status: :unprocessable_entity + end + end + + private + + def ensure_import_vars + unless group && repositories.present? + redirect_to(new_import_manifest_path) + end + end + + def group + @group ||= Group.find_by(id: session[:manifest_import_group_id]) + end + + def repositories + @repositories ||= session[:manifest_import_repositories] + end + + def find_jobs + find_already_added_projects.to_json(only: [:id], methods: [:import_status]) + end + + def find_already_added_projects + group.all_projects + .where(import_type: 'manifest') + .where(creator_id: current_user) + .includes(:import_state) + end + + def verify_import_enabled + render_404 unless manifest_import_enabled? + end + + def whitelist_query_limiting + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/48939') + end +end diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 93fb9da6510..a41fcb85c40 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -1,13 +1,19 @@ class Projects::TodosController < Projects::ApplicationController - include Gitlab::Utils::StrongMemoize - include TodosActions - before_action :authenticate_user!, only: [:create] + def create + todo = TodoService.new.mark_todo(issuable, current_user) + + render json: { + count: TodosFinder.new(current_user, state: :pending).execute.count, + delete_path: dashboard_todo_path(todo) + } + end + private def issuable - strong_memoize(:issuable) do + @issuable ||= begin case params[:issuable_type] when "issue" IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 2156413fb26..09e2c586f2a 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -15,7 +15,6 @@ class TodosFinder prepend FinderWithCrossProjectAccess include FinderMethods - include Gitlab::Utils::StrongMemoize requires_cross_project_access unless: -> { project? } @@ -35,11 +34,9 @@ class TodosFinder items = by_author(items) items = by_state(items) items = by_type(items) - items = by_group(items) # Filtering by project HAS TO be the last because we use # the project IDs yielded by the todos query thus far items = by_project(items) - items = visible_to_user(items) sort(items) end @@ -85,10 +82,6 @@ class TodosFinder params[:project_id].present? end - def group? - params[:group_id].present? - end - def project return @project if defined?(@project) @@ -107,14 +100,18 @@ class TodosFinder @project end - def group - strong_memoize(:group) do - Group.find(params[:group_id]) + def project_ids(items) + ids = items.except(:order).select(:project_id) + if Gitlab::Database.mysql? + # To make UPDATE work on MySQL, wrap it in a SELECT with an alias + ids = Todo.except(:order).select('*').from("(#{ids.to_sql}) AS t") end + + ids end def type? - type.present? && %w(Issue MergeRequest Epic).include?(type) + type.present? && %w(Issue MergeRequest).include?(type) end def type @@ -151,37 +148,12 @@ class TodosFinder def by_project(items) if project? - items = items.where(project: project) - end - - items - end + items.where(project: project) + else + projects = Project.public_or_visible_to_user(current_user) - def by_group(items) - if group? - groups = group.self_and_descendants - items = items.where( - 'project_id IN (?) OR group_id IN (?)', - Project.where(group: groups).select(:id), - groups.select(:id) - ) + items.joins(:project).merge(projects) end - - items - end - - def visible_to_user(items) - projects = Project.public_or_visible_to_user(current_user) - groups = Group.public_or_visible_to_user(current_user) - - items - .joins('LEFT JOIN namespaces ON namespaces.id = todos.group_id') - .joins('LEFT JOIN projects ON projects.id = todos.project_id') - .where( - 'project_id IN (?) OR group_id IN (?)', - projects.select(:id), - groups.select(:id) - ) end def by_state(items) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 7bbdc798ddd..8766bb43cac 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -131,19 +131,6 @@ module IssuablesHelper end end - def group_dropdown_label(group_id, default_label) - return default_label if group_id.nil? - return "Any group" if group_id == "0" - - group = ::Group.find_by(id: group_id) - - if group - group.full_name - else - default_label - end - end - def milestone_dropdown_label(milestone_title, default_label = "Milestone") title = case milestone_title diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 9be93fa69ae..9008db1b300 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -3,7 +3,7 @@ module NamespacesHelper params.dig(:project, :namespace_id) || params[:namespace_id] end - def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) + def namespaces_options(selected = :current_user, display_path: false, extra_group: nil, groups_only: false) groups = current_user.manageable_groups .joins(:route) .includes(:route) @@ -20,10 +20,13 @@ module NamespacesHelper options = [] options << options_for_group(groups, display_path: display_path, type: 'group') - options << options_for_group(users, display_path: display_path, type: 'user') - if selected == :current_user && current_user.namespace - selected = current_user.namespace.id + unless groups_only + options << options_for_group(users, display_path: display_path, type: 'user') + + if selected == :current_user && current_user.namespace + selected = current_user.namespace.id + end end grouped_options_for_select(options, selected) diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 9151543dfdc..ebfde993456 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -8,7 +8,7 @@ module SubmoduleHelper url = repository.submodule_url_for(ref, submodule_item.path) if url == '.' || url == './' - url = File.join(Gitlab.config.gitlab.url, @project.full_path) + url = File.join(Gitlab.config.gitlab.url, repository.project.full_path) end if url =~ %r{([^/:]+)/([^/]+(?:\.git)?)\Z} @@ -31,7 +31,7 @@ module SubmoduleHelper [namespace_project_path(namespace, project), namespace_project_tree_path(namespace, project, submodule_item.id)] elsif relative_self_url?(url) - relative_self_links(url, submodule_item.id) + relative_self_links(url, submodule_item.id, repository.project) elsif github_dot_com_url?(url) standard_links('github.com', namespace, project, submodule_item.id) elsif gitlab_dot_com_url?(url) @@ -73,7 +73,7 @@ module SubmoduleHelper [base, [base, '/tree/', commit].join('')] end - def relative_self_links(url, commit) + def relative_self_links(url, commit, project) url.rstrip! # Map relative links to a namespace and project # For example: @@ -85,7 +85,7 @@ module SubmoduleHelper namespace = components.pop.gsub(/^\.\.$/, '') if namespace.empty? - namespace = @project.namespace.full_path + namespace = project.namespace.full_path end begin diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 7cd74358168..f7620e0b6b8 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -43,7 +43,7 @@ module TodosHelper project_commit_path(todo.project, todo.target, anchor: anchor) else - path = [todo.parent, todo.target] + path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target] path.unshift(:pipelines) if todo.build_failed? @@ -167,12 +167,4 @@ module TodosHelper def show_todo_state?(todo) (todo.target.is_a?(MergeRequest) || todo.target.is_a?(Issue)) && %w(closed merged).include?(todo.target.state) end - - def todo_group_options - groups = current_user.authorized_groups.map do |group| - { id: group.id, text: group.full_name } - end - - groups.unshift({ id: '', text: 'Any Group' }).to_json - end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d8ddb4bc667..db86400128c 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -653,6 +653,7 @@ module Ci variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_COMMIT_SHA', value: sha) + variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha) variables.append(key: 'CI_COMMIT_REF_NAME', value: ref) variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug) variables.append(key: "CI_COMMIT_TAG", value: ref) if tag? diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 7a459078151..b93c1145f82 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -243,12 +243,6 @@ module Issuable opened? end - def overdue? - return false unless respond_to?(:due_date) - - due_date.try(:past?) || false - end - def user_notes_count if notes.loaded? # Use the in-memory association to select and count to avoid hitting the db diff --git a/app/models/deployment.rb b/app/models/deployment.rb index ac86e9e8de0..687246b47b2 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -92,10 +92,6 @@ class Deployment < ActiveRecord::Base @stop_action ||= manual_actions.find_by(name: on_stop) end - def stop_action? - stop_action.present? - end - def formatted_deployment_time created_at.to_time.in_time_zone.to_s(:medium) end diff --git a/app/models/environment.rb b/app/models/environment.rb index 8d523dae324..4856d313318 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -117,7 +117,7 @@ class Environment < ActiveRecord::Base external_url.gsub(%r{\A.*?://}, '') end - def stop_action? + def stop_action_available? available? && stop_action.present? end diff --git a/app/models/group.rb b/app/models/group.rb index 28677320e28..ddebaff50b0 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -39,8 +39,6 @@ class Group < Namespace has_many :boards has_many :badges, class_name: 'GroupBadge' - has_many :todos - accepts_nested_attributes_for :variables, allow_destroy: true validate :visibility_level_allowed_by_projects @@ -84,12 +82,6 @@ class Group < Namespace where(id: user.authorized_groups.select(:id).reorder(nil)) end - def public_or_visible_to_user(user) - where('id IN (?) OR namespaces.visibility_level IN (?)', - user.authorized_groups.select(:id), - Gitlab::VisibilityLevel.levels_for_user(user)) - end - def select_for_project_authorization if current_scope.joins_values.include?(:shared_projects) joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id') diff --git a/app/models/issue.rb b/app/models/issue.rb index 983684a5e05..4715d942c8d 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -275,6 +275,10 @@ class Issue < ActiveRecord::Base user ? readable_by?(user) : publicly_visible? end + def overdue? + due_date.try(:past?) || false + end + def check_for_spam? project.public? && (title_changed? || description_changed?) end diff --git a/app/models/note.rb b/app/models/note.rb index 3918bbee194..abc40d9016e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -229,10 +229,6 @@ class Note < ActiveRecord::Base !for_personal_snippet? end - def for_issuable? - for_issue? || for_merge_request? - end - def skip_project_check? !for_project_noteable? end diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 9195408551f..1933c46ee44 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -32,6 +32,7 @@ class NotificationSetting < ActiveRecord::Base :reopen_issue, :close_issue, :reassign_issue, + :issue_due, :new_merge_request, :push_to_merge_request, :reopen_merge_request, diff --git a/app/models/project.rb b/app/models/project.rb index 1894de6ceed..e29bca365a4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -368,8 +368,10 @@ class Project < ActiveRecord::Base chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 validates :build_timeout, allow_nil: true, - numericality: { greater_than_or_equal_to: 600, - message: 'needs to be at least 10 minutes' } + numericality: { greater_than_or_equal_to: 10.minutes, + less_than: 1.month, + only_integer: true, + message: 'needs to be beetween 10 minutes and 1 month' } # Returns a collection of projects that is either public or visible to the # logged in user. diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index fc868c3ebb7..9ae2fb0013a 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -20,7 +20,6 @@ class ProjectWiki @user = user end - delegate :empty?, to: :pages delegate :repository_storage, :hashed_storage?, to: :project def path @@ -74,6 +73,10 @@ class ProjectWiki !!find_page('home') end + def empty? + pages(limit: 1).empty? + end + # Returns an Array of Gitlab WikiPage instances or an # empty Array if this Wiki has no pages. def pages(limit: nil) diff --git a/app/models/repository.rb b/app/models/repository.rb index 5ed2a7b4068..a96c73e6ab7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -174,8 +174,8 @@ class Repository CommitCollection.new(project, commits, ref) end - def find_branch(name, fresh_repo: true) - raw_repository.find_branch(name, fresh_repo) + def find_branch(name) + raw_repository.find_branch(name) end def find_tag(name) diff --git a/app/models/todo.rb b/app/models/todo.rb index 942cbb754e3..a2ab405fdbe 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -22,18 +22,15 @@ class Todo < ActiveRecord::Base belongs_to :author, class_name: "User" belongs_to :note belongs_to :project - belongs_to :group belongs_to :target, polymorphic: true, touch: true # rubocop:disable Cop/PolymorphicAssociations belongs_to :user delegate :name, :email, to: :author, prefix: true, allow_nil: true - validates :action, :target_type, :user, presence: true + validates :action, :project, :target_type, :user, presence: true validates :author, presence: true validates :target_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - validates :project, presence: true, unless: :group_id - validates :group, presence: true, unless: :project_id scope :pending, -> { with_state(:pending) } scope :done, -> { with_state(:done) } @@ -47,7 +44,7 @@ class Todo < ActiveRecord::Base state :done end - after_save :keep_around_commit, if: :commit_id + after_save :keep_around_commit class << self # Priority sorting isn't displayed in the dropdown, because we don't show @@ -82,10 +79,6 @@ class Todo < ActiveRecord::Base end end - def parent - project - end - def unmergeable? action == UNMERGEABLE end diff --git a/app/policies/environment_policy.rb b/app/policies/environment_policy.rb index 978dc3a7c81..2d07311db72 100644 --- a/app/policies/environment_policy.rb +++ b/app/policies/environment_policy.rb @@ -2,11 +2,12 @@ class EnvironmentPolicy < BasePolicy delegate { @subject.project } condition(:stop_with_deployment_allowed) do - @subject.stop_action? && can?(:create_deployment) && can?(:update_build, @subject.stop_action) + @subject.stop_action_available? && + can?(:create_deployment) && can?(:update_build, @subject.stop_action) end condition(:stop_with_update_allowed) do - !@subject.stop_action? && can?(:update_environment, @subject) + !@subject.stop_action_available? && can?(:update_environment, @subject) end rule { stop_with_deployment_allowed | stop_with_update_allowed }.enable :stop_environment diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index 0fc3f92b151..83558fc6659 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -7,7 +7,7 @@ class EnvironmentEntity < Grape::Entity expose :external_url expose :environment_type expose :last_deployment, using: DeploymentEntity - expose :stop_action?, as: :has_stop_action + expose :stop_action_available?, as: :has_stop_action expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment| metrics_project_environment_path(environment.project, environment) diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 5d72ebdd7fd..a78bd77cf7c 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -10,9 +10,15 @@ class MergeRequestWidgetEntity < IssuableEntity expose :merge_when_pipeline_succeeds expose :source_branch expose :source_project_id + expose :source_project_full_path do |merge_request| + merge_request.source_project&.full_path + end expose :squash expose :target_branch expose :target_project_id + expose :target_project_full_path do |merge_request| + merge_request.project&.full_path + end expose :allow_collaboration expose :should_be_rebased?, as: :should_be_rebased diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb index 46e19230328..2a337918d21 100644 --- a/app/services/access_token_validation_service.rb +++ b/app/services/access_token_validation_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AccessTokenValidationService # Results: VALID = :valid diff --git a/app/services/after_branch_delete_service.rb b/app/services/after_branch_delete_service.rb index 227e9ea9c6d..e7eb74d3e7d 100644 --- a/app/services/after_branch_delete_service.rb +++ b/app/services/after_branch_delete_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # Branch can be deleted either by DeleteBranchService # or by GitPushService. diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb index 0521393dd27..82ae66ab0f5 100644 --- a/app/services/akismet_service.rb +++ b/app/services/akismet_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AkismetService attr_accessor :owner, :text, :options diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index 5ad9a50687c..4c5e22bdd7e 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AuditEventService def initialize(author, entity, details = {}) @author, @entity, @details = author, entity, details diff --git a/app/services/base_count_service.rb b/app/services/base_count_service.rb index 975e288301c..ad1647842b8 100644 --- a/app/services/base_count_service.rb +++ b/app/services/base_count_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Base class for services that count a single resource such as the number of # issues for a project. class BaseCountService diff --git a/app/services/base_renderer.rb b/app/services/base_renderer.rb index d6e30bd7008..30a6e8c62dd 100644 --- a/app/services/base_renderer.rb +++ b/app/services/base_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseRenderer attr_reader :current_user diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 3519b7c5e7d..3e968c8f707 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BaseService include Gitlab::Allowable diff --git a/app/services/ci/stop_environments_service.rb b/app/services/ci/stop_environments_service.rb index 43c9a065fcf..439746e82bd 100644 --- a/app/services/ci/stop_environments_service.rb +++ b/app/services/ci/stop_environments_service.rb @@ -8,7 +8,7 @@ module Ci return unless @ref.present? environments.each do |environment| - next unless environment.stop_action? + next unless environment.stop_action_available? next unless can?(current_user, :stop_environment, environment) environment.stop_with_action!(current_user) diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb index 6781533af28..7a14e97f749 100644 --- a/app/services/cohorts_service.rb +++ b/app/services/cohorts_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CohortsService MONTHS_INCLUDED = 12 diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 2a69a205629..3adf8a0c1a1 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'securerandom' # Compare 2 refs for one repo or between repositories diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 9b1a4d960e2..65208b07e27 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateBranchService < BaseService def execute(branch_name, ref) create_master_branch if project.empty_repo? diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 7e5a77fb056..bb3f605da28 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateDeploymentService attr_reader :job diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb index 54ff1f74126..09c68390007 100644 --- a/app/services/create_release_service.rb +++ b/app/services/create_release_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateReleaseService < BaseService def execute(tag_name, release_description) repository = project.repository diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb index 40286dbf3bf..6f1fce4989e 100644 --- a/app/services/create_snippet_service.rb +++ b/app/services/create_snippet_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CreateSnippetService < BaseService include SpamCheckService diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index e1499dcee64..44252f7b0a6 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeleteBranchService < BaseService def execute(branch_name) repository = project.repository diff --git a/app/services/delete_merged_branches_service.rb b/app/services/delete_merged_branches_service.rb index c98d1e3c540..ff3e4783fe3 100644 --- a/app/services/delete_merged_branches_service.rb +++ b/app/services/delete_merged_branches_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class DeleteMergedBranchesService < BaseService def async_execute DeleteMergedBranchesWorker.perform_async(project.id, current_user.id) diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 44dc90b3462..e7464fd9d5f 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # EventCreateService class # # Used for creating events feed on dashboard after certain user action diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f3bfc53dcd3..29c8ce5fea3 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GitPushService < BaseService attr_accessor :push_data, :push_commits include Gitlab::Access diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 9917a39b795..3ff2d1d107d 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GitTagPushService < BaseService attr_accessor :push_data diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb index c6e52c3bb91..2a7a5dae291 100644 --- a/app/services/gravatar_service.rb +++ b/app/services/gravatar_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GravatarService def execute(email, size = nil, scale = 2, username: nil) return unless Gitlab::CurrentSettings.gravatar_enabled? diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb index 5c337a9faa5..c2dfbac5414 100644 --- a/app/services/groups/nested_create_service.rb +++ b/app/services/groups/nested_create_service.rb @@ -1,11 +1,12 @@ module Groups class NestedCreateService < Groups::BaseService - attr_reader :group_path + attr_reader :group_path, :visibility_level def initialize(user, params) @current_user, @params = user, params.dup - @group_path = @params.delete(:group_path) + @visibility_level = @params.delete(:visibility_level) || + Gitlab::CurrentSettings.current_application_settings.default_group_visibility end def execute @@ -36,11 +37,12 @@ module Groups new_params = params.reverse_merge( path: subgroup_name, name: subgroup_name, - parent: last_group + parent: last_group, + visibility_level: visibility_level ) - new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility - last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute + last_group = namespace_or_group(partial_path) || + Groups::CreateService.new(current_user, new_params).execute end last_group diff --git a/app/services/ham_service.rb b/app/services/ham_service.rb index b0e1799b489..794eb34d9ca 100644 --- a/app/services/ham_service.rb +++ b/app/services/ham_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class HamService attr_accessor :spam_log diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb index 3702c3742ef..e75a951944e 100644 --- a/app/services/import_export_clean_up_service.rb +++ b/app/services/import_export_clean_up_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ImportExportCleanUpService LAST_MODIFIED_TIME_IN_MINUTES = 1440 diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 5e06e0c61cf..7d60c65bb79 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class IssuableBaseService < BaseService private diff --git a/app/services/merge_request_metrics_service.rb b/app/services/merge_request_metrics_service.rb index 9248de14a53..4e88b77c855 100644 --- a/app/services/merge_request_metrics_service.rb +++ b/app/services/merge_request_metrics_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MergeRequestMetricsService delegate :update!, to: :@merge_request_metrics diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index 51ff9eff5e4..222a5c8c79c 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -1,35 +1,18 @@ +# frozen_string_literal: true + require 'prometheus/client/formats/text' class MetricsService - CHECKS = [ - Gitlab::HealthChecks::DbCheck, - Gitlab::HealthChecks::Redis::RedisCheck, - Gitlab::HealthChecks::Redis::CacheCheck, - Gitlab::HealthChecks::Redis::QueuesCheck, - Gitlab::HealthChecks::Redis::SharedStateCheck, - Gitlab::HealthChecks::GitalyCheck - ].freeze - def prometheus_metrics_text Prometheus::Client::Formats::Text.marshal_multiprocess(multiprocess_metrics_path) end - def health_metrics_text - metrics = CHECKS.flat_map(&:metrics) - - formatter.marshal(metrics) - end - def metrics_text - prometheus_metrics_text.concat(health_metrics_text) + prometheus_metrics_text end private - def formatter - @formatter ||= Gitlab::HealthChecks::PrometheusTextFormat.new - end - def multiprocess_metrics_path ::Prometheus::Client.configuration.multiprocess_files_dir end diff --git a/app/services/note_summary.rb b/app/services/note_summary.rb index a6f6320d573..81f6f92f75c 100644 --- a/app/services/note_summary.rb +++ b/app/services/note_summary.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class NoteSummary attr_reader :note attr_reader :metadata diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index d9834fd0ccc..4389fd89538 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # # Used by NotificationService to determine who should receive notification # diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index d7be9a925b5..4511c500fca 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # rubocop:disable GitlabSecurity/PublicSend # NotificationService class diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb index 6da4d9523cf..a15ee4911ef 100644 --- a/app/services/preview_markdown_service.rb +++ b/app/services/preview_markdown_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class PreviewMarkdownService < BaseService def execute text, commands = explain_quick_actions(params[:text]) diff --git a/app/services/push_event_payload_service.rb b/app/services/push_event_payload_service.rb index b0a389c85f9..bb1259787af 100644 --- a/app/services/push_event_payload_service.rb +++ b/app/services/push_event_payload_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Service class for creating push event payloads as stored in the # "push_event_payloads" table. # diff --git a/app/services/repair_ldap_blocked_user_service.rb b/app/services/repair_ldap_blocked_user_service.rb index 863cef7ff61..6ed42054ac3 100644 --- a/app/services/repair_ldap_blocked_user_service.rb +++ b/app/services/repair_ldap_blocked_user_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepairLdapBlockedUserService attr_accessor :user diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb index ba7be4b3f89..99a9c834352 100644 --- a/app/services/repository_archive_clean_up_service.rb +++ b/app/services/repository_archive_clean_up_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RepositoryArchiveCleanUpService LAST_MODIFIED_TIME_IN_MINUTES = 120 diff --git a/app/services/reset_project_cache_service.rb b/app/services/reset_project_cache_service.rb index a162a6eedb9..676d367a1c1 100644 --- a/app/services/reset_project_cache_service.rb +++ b/app/services/reset_project_cache_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ResetProjectCacheService < BaseService def execute @project.increment!(:jobs_cache_index) diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 1d4d03a8b7d..1b707d79b43 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SearchService include Gitlab::Allowable diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb index d4ade869777..895261925ba 100644 --- a/app/services/spam_check_service.rb +++ b/app/services/spam_check_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # SpamCheckService # # Provide helper methods for checking if a given spammable object has diff --git a/app/services/spam_service.rb b/app/services/spam_service.rb index 73ea3018fbd..f2f133dae28 100644 --- a/app/services/spam_service.rb +++ b/app/services/spam_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SpamService attr_accessor :spammable, :request, :options attr_reader :spam_log diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb index ac029fad7ea..93c2e222963 100644 --- a/app/services/submit_usage_ping_service.rb +++ b/app/services/submit_usage_ping_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SubmitUsagePingService URL = 'https://version.gitlab.com/usage_data'.freeze diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index ba7946fd23c..bd3907cdf8e 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class SystemHooksService def execute_hooks_for(model, event) data = build_event_data(model, event) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 00bf5434b7f..77494295f14 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # SystemNoteService # # Used for creating system notes (e.g., when a user references a merge request @@ -21,9 +23,11 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "added #{commits_text}\n\n" - body << commits_list(noteable, new_commits, existing_commits, oldrev) - body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" + text_parts = ["added #{commits_text}"] + text_parts << commits_list(noteable, new_commits, existing_commits, oldrev) + text_parts << "[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" + + body = text_parts.join("\n\n") create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count)) end @@ -103,18 +107,19 @@ module SystemNoteService added_labels = added_labels.map(&references).join(' ') removed_labels = removed_labels.map(&references).join(' ') - body = '' + text_parts = [] if added_labels.present? - body << "added #{added_labels}" - body << ' and ' if removed_labels.present? + text_parts << "added #{added_labels}" + text_parts << 'and' if removed_labels.present? end if removed_labels.present? - body << "removed #{removed_labels}" + text_parts << "removed #{removed_labels}" end - body << ' ' << 'label'.pluralize(labels_count) + text_parts << 'label'.pluralize(labels_count) + body = text_parts.join(' ') create_note(NoteSummary.new(noteable, project, author, body, action: 'label')) end @@ -188,8 +193,10 @@ module SystemNoteService spent_at = noteable.spent_at parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs) action = time_spent > 0 ? 'added' : 'subtracted' - body = "#{action} #{parsed_time} of time spent" - body << " at #{spent_at}" if spent_at + + text_parts = ["#{action} #{parsed_time} of time spent"] + text_parts << "at #{spent_at}" if spent_at + body = text_parts.join(' ') end create_note(NoteSummary.new(noteable, project, author, body, action: 'time_tracking')) @@ -268,17 +275,19 @@ module SystemNoteService diff_refs = change_position.diff_refs version_index = merge_request.merge_request_diffs.viewable.count - body = "changed this line in" + text_parts = ["changed this line in"] if version_params = merge_request.version_params_for(diff_refs) line_code = change_position.line_code(project.repository) url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code)) - body << " [version #{version_index} of the diff](#{url})" + text_parts << "[version #{version_index} of the diff](#{url})" else - body << " version #{version_index} of the diff" + text_parts << "version #{version_index} of the diff" end + body = text_parts.join(' ') note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body) + note = Note.create(note_attributes.merge(system: true)) note.system_note_metadata = SystemNoteMetadata.new(action: 'outdated') diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 46f12086555..0bcd53c76a9 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # TodoService class # # Used for creating/updating todos after certain user actions @@ -260,15 +262,15 @@ class TodoService end end - def create_mention_todos(parent, target, author, note = nil, skip_users = []) + def create_mention_todos(project, target, author, note = nil, skip_users = []) # Create Todos for directly addressed users - directly_addressed_users = filter_directly_addressed_users(parent, note || target, author, skip_users) - attributes = attributes_for_todo(parent, target, author, Todo::DIRECTLY_ADDRESSED, note) + directly_addressed_users = filter_directly_addressed_users(project, note || target, author, skip_users) + attributes = attributes_for_todo(project, target, author, Todo::DIRECTLY_ADDRESSED, note) create_todos(directly_addressed_users, attributes) # Create Todos for mentioned users - mentioned_users = filter_mentioned_users(parent, note || target, author, skip_users) - attributes = attributes_for_todo(parent, target, author, Todo::MENTIONED, note) + mentioned_users = filter_mentioned_users(project, note || target, author, skip_users) + attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note) create_todos(mentioned_users, attributes) end @@ -299,36 +301,36 @@ class TodoService def attributes_for_todo(project, target, author, action, note = nil) attributes_for_target(target).merge!( - project_id: project&.id, + project_id: project.id, author_id: author.id, action: action, note: note ) end - def filter_todo_users(users, parent, target) - reject_users_without_access(users, parent, target).uniq + def filter_todo_users(users, project, target) + reject_users_without_access(users, project, target).uniq end - def filter_mentioned_users(parent, target, author, skip_users = []) + def filter_mentioned_users(project, target, author, skip_users = []) mentioned_users = target.mentioned_users(author) - skip_users - filter_todo_users(mentioned_users, parent, target) + filter_todo_users(mentioned_users, project, target) end - def filter_directly_addressed_users(parent, target, author, skip_users = []) + def filter_directly_addressed_users(project, target, author, skip_users = []) directly_addressed_users = target.directly_addressed_users(author) - skip_users - filter_todo_users(directly_addressed_users, parent, target) + filter_todo_users(directly_addressed_users, project, target) end - def reject_users_without_access(users, parent, target) - if target.is_a?(Note) && target.for_issuable? + def reject_users_without_access(users, project, target) + if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?) target = target.noteable end if target.is_a?(Issuable) select_users(users, :"read_#{target.to_ability_name}", target) else - select_users(users, :read_project, parent) + select_users(users, :read_project, project) end end diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb index dc696e9c440..422ba668e35 100644 --- a/app/services/update_release_service.rb +++ b/app/services/update_release_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UpdateReleaseService < BaseService def execute(tag_name, release_description) repository = project.repository diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index 358bca73aec..15bc1046a4e 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UpdateSnippetService < BaseService include SpamCheckService diff --git a/app/services/upload_service.rb b/app/services/upload_service.rb index d5a9b344905..39909ee4f82 100644 --- a/app/services/upload_service.rb +++ b/app/services/upload_service.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + class UploadService - def initialize(model, file, uploader_class = FileUploader) - @model, @file, @uploader_class = model, file, uploader_class + def initialize(model, file, uploader_class = FileUploader, **uploader_context) + @model, @file, @uploader_class, @uploader_context = model, file, uploader_class, uploader_context end def execute return nil unless @file && @file.size <= max_attachment_size - uploader = @uploader_class.new(@model) + uploader = @uploader_class.new(@model, nil, @uploader_context) uploader.store!(@file) uploader.to_h diff --git a/app/services/user_agent_detail_service.rb b/app/services/user_agent_detail_service.rb index a1ee3df5fe1..5cb42e879a0 100644 --- a/app/services/user_agent_detail_service.rb +++ b/app/services/user_agent_detail_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UserAgentDetailService attr_accessor :spammable, :request diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb index 8630e572624..adca43660e8 100644 --- a/app/services/user_project_access_changed_service.rb +++ b/app/services/user_project_access_changed_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class UserProjectAccessChangedService def initialize(user_ids) @user_ids = Array.wrap(user_ids) diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb index 643f2ce1481..c19e2ec2043 100644 --- a/app/services/validate_new_branch_service.rb +++ b/app/services/validate_new_branch_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'base_service' class ValidateNewBranchService < BaseService diff --git a/app/services/verify_pages_domain_service.rb b/app/services/verify_pages_domain_service.rb index 13cb53dee01..07f7391f877 100644 --- a/app/services/verify_pages_domain_service.rb +++ b/app/services/verify_pages_domain_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'resolv' class VerifyPagesDomainService < BaseService diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 8a86e47f0ea..34724e0250d 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class WebHookService class InternalErrorResponse attr_reader :body, :headers, :code diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 21292ddcf44..83f7b99d2a5 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -15,7 +15,7 @@ class FileUploader < GitlabUploader prepend ObjectStorage::Extension::RecordsUploads MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)} - DYNAMIC_PATH_PATTERN = %r{(?<secret>\h{32})/(?<identifier>.*)} + DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\h{32})/(?<identifier>.*)} after :remove, :prune_store_dir @@ -67,6 +67,10 @@ class FileUploader < GitlabUploader SecureRandom.hex end + def self.extract_dynamic_path(path) + DYNAMIC_PATH_PATTERN.match(path) + end + def upload_paths(identifier) [ File.join(secret, identifier), @@ -143,7 +147,7 @@ class FileUploader < GitlabUploader return if apply_context!(value.uploader_context) # fallback to the regex based extraction - if matches = DYNAMIC_PATH_PATTERN.match(value.path) + if matches = self.class.extract_dynamic_path(value.path) @secret = matches[:secret] @identifier = matches[:identifier] end diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index c8008771236..a3773e90cfb 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -6,7 +6,7 @@ = render_if_exists 'admin/namespace_plan', f: f .form-group.row.group-description-holder - = f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2' + = f.label :avatar, _("Group avatar"), class: 'col-form-label col-sm-2' .col-sm-10 = render 'shared/choose_group_avatar_button', f: f @@ -26,12 +26,12 @@ .alert.alert-info = render 'shared/group_tips' .form-actions - = f.submit 'Create group', class: "btn btn-create" - = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" + = f.submit _('Create group'), class: "btn btn-create" + = link_to _('Cancel'), admin_groups_path, class: "btn btn-cancel" - else .form-actions - = f.submit 'Save changes', class: "btn btn-save" - = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" + = f.submit _('Save changes'), class: "btn btn-save" + = link_to _('Cancel'), admin_group_path(@group), class: "btn btn-cancel" = render_if_exists 'ldap_group_links/ldap_syncrhonizations', group: @group diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 3f96988c203..0a688b90f3a 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -3,8 +3,8 @@ %li.group-row{ class: css_class } .controls - = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' - = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove' + = link_to _('Edit'), admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' + = link_to _('Delete'), [:admin, group], data: { confirm: _("Are you sure you want to remove %{group_name}?") % { group_name: group.name } }, method: :delete, class: 'btn btn-remove' .stats %span.badge.badge-pill = storage_counter(group.storage_size) diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index c2b9807015d..8e9e1a58a17 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", @group.name, "Groups" -%h3.page-title Edit group: #{@group.name} +- page_title _("Edit"), @group.name, _("Groups") +%h3.page-title= _('Edit group: %{group_name}') % { group_name: @group.name } %hr = render 'form', visibility_level: @group.visibility_level diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 25946ba6eaf..6a9b85b4109 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,5 +1,5 @@ - @no_container = true -- page_title "Groups" +- page_title _("Groups") %div{ class: container_class } .top-area @@ -13,7 +13,7 @@ = icon("search", class: "search-icon") = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash = link_to new_admin_group_path, class: "btn btn-new" do - New group + = _('New group') %ul.content-list = render @groups diff --git a/app/views/admin/groups/new.html.haml b/app/views/admin/groups/new.html.haml index 8f9fe96249f..553e8638e52 100644 --- a/app/views/admin/groups/new.html.haml +++ b/app/views/admin/groups/new.html.haml @@ -1,4 +1,4 @@ -- page_title "New Group" -%h3.page-title New group +- page_title _("New Group") +%h3.page-title= _('New group') %hr = render 'form', visibility_level: default_group_visibility diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index a40f98ad24f..72b068ea6b5 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -1,61 +1,58 @@ -- add_to_breadcrumbs "Groups", admin_groups_path +- add_to_breadcrumbs _("Groups"), admin_groups_path - breadcrumb_title @group.name -- page_title @group.name, "Groups" +- page_title @group.name, _("Groups") %h3.page-title - Group: #{@group.full_name} + = _('Group: %{group_name}') % { group_name: @group.full_name } = link_to admin_group_edit_path(@group), class: "btn float-right" do %i.fa.fa-pencil-square-o - Edit + = _('Edit') %hr .row .col-md-6 .card .card-header - Group info: + = _('Group info:') %ul.content-list %li .avatar-container.s60 = group_icon(@group, class: "avatar s60") %li - %span.light Name: + %span.light= _('Name:') %strong= @group.name %li - %span.light Path: + %span.light= _('Path:') %strong = @group.path %li - %span.light Description: + %span.light= _('Description:') %strong = @group.description %li - %span.light Visibility level: + %span.light= _('Visibility level:') %strong = visibility_level_label(@group.visibility_level) %li - %span.light Created on: + %span.light= _('Created on:') %strong = @group.created_at.to_s(:medium) = render_if_exists 'admin/namespace_plan_info', namespace: @group %li - %span.light Storage: - %strong= storage_counter(@group.storage_size) - ( - = storage_counter(@group.repository_size) - repositories, - = storage_counter(@group.build_artifacts_size) - build artifacts, - = storage_counter(@group.lfs_objects_size) - LFS - ) + %span.light= _('Storage:') + - counter_storage = storage_counter(@group.storage_size) + - counter_repositories = storage_counter(@group.repository_size) + - counter_build_artifacts = storage_counter(@group.build_artifacts_size) + - counter_lfs_objects = storage_counter(@group.lfs_objects_size) + %strong + = _("%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)") % { counter_storage: counter_storage, counter_repositories: counter_repositories, counter_build_artifacts: counter_build_artifacts, counter_lfs_objects: counter_lfs_objects } %li - %span.light Group Git LFS status: + %span.light= _('Group Git LFS status:') %strong = group_lfs_status(@group) = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') @@ -67,7 +64,7 @@ .card .card-header %h3.card-title - Projects + = _('Projects') %span.badge.badge-pill #{@group.projects.count} %ul.content-list @@ -85,7 +82,7 @@ - if @group.shared_projects.any? .card .card-header - Projects shared with #{@group.name} + = _('Projects shared with %{group_name}') % { group_name: @group.name } %span.badge.badge-pill #{@group.shared_projects.count} %ul.content-list @@ -102,11 +99,11 @@ - if can?(current_user, :admin_group_member, @group) .card .card-header - Add user(s) to the group: + = _('Add user(s) to the group:') .card-body.form-holder %p.light - Read more about project permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + - link_to_help = link_to(_("here"), help_page_path("user/permissions"), class: "vlink") + = _('Read more about project permissions <strong>%{link_to_help}</strong>').html_safe % { link_to_help: link_to_help } = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div @@ -114,16 +111,15 @@ .prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr - = button_tag 'Add users to group', class: "btn btn-create" + = button_tag _('Add users to group'), class: "btn btn-create" = render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true .card .card-header - %strong= @group.name - group members + = _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name } %span.badge.badge-pill= @group.members.size .float-right - = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm" + = link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm" %ul.content-list.group-users-list.content-list.members-list = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } .card-footer diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index a676eba2aee..9c246e19faa 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,8 +1,8 @@ .nav-block.activities + = render 'shared/event_filter' .controls = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do %i.fa.fa-rss - = render 'shared/event_filter' .content_list = spinner diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 8b3974d97f8..d5a9cc646a6 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -30,33 +30,27 @@ .todos-filters .row-content-block.second-block - = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form d-sm-flex' do - .filter-categories.flex-fill - .filter-item.inline - - if params[:group_id].present? - = hidden_field_tag(:group_id, params[:group_id]) - = dropdown_tag(group_dropdown_label(params[:group_id], 'Group'), options: { toggle_class: 'js-group-search js-filter-submit', title: 'Filter by group', filter: true, filterInput: 'input#group-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-group js-filter-submit', - placeholder: 'Search groups', data: { data: todo_group_options, default_label: 'Group', display: 'static' } }) - .filter-item.inline - - if params[:project_id].present? - = hidden_field_tag(:project_id, params[:project_id]) - = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', - placeholder: 'Search projects', data: { data: todo_projects_options, default_label: 'Project', display: 'static' } }) - .filter-item.inline - - if params[:author_id].present? - = hidden_field_tag(:author_id, params[:author_id]) - = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', - placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } }) - .filter-item.inline - - if params[:type].present? - = hidden_field_tag(:type, params[:type]) - = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', - data: { data: todo_types_options, default_label: 'Type' } }) - .filter-item.inline.actions-filter - - if params[:action_id].present? - = hidden_field_tag(:action_id, params[:action_id]) - = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', - data: { data: todo_actions_options, default_label: 'Action' } }) + = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do + .filter-item.inline + - if params[:project_id].present? + = hidden_field_tag(:project_id, params[:project_id]) + = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', + placeholder: 'Search projects', data: { data: todo_projects_options, default_label: 'Project', display: 'static' } }) + .filter-item.inline + - if params[:author_id].present? + = hidden_field_tag(:author_id, params[:author_id]) + = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', + placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } }) + .filter-item.inline + - if params[:type].present? + = hidden_field_tag(:type, params[:type]) + = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', + data: { data: todo_types_options, default_label: 'Type' } }) + .filter-item.inline.actions-filter + - if params[:action_id].present? + = hidden_field_tag(:action_id, params[:action_id]) + = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', + data: { data: todo_actions_options, default_label: 'Action' } }) .filter-item.sort-filter .dropdown %button.dropdown-menu-toggle.dropdown-menu-toggle-sort{ type: 'button', 'data-toggle' => 'dropdown' } diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml index 84b4ce5b606..ac5cac50699 100644 --- a/app/views/doorkeeper/applications/_delete_form.html.haml +++ b/app/views/doorkeeper/applications/_delete_form.html.haml @@ -2,9 +2,9 @@ = form_tag oauth_application_path(application) do %input{ :name => "_method", :type => "hidden", :value => "delete" }/ - if defined? small - = button_tag type: "submit", class: "btn btn-transparent", data: { confirm: "Are you sure?" } do + = button_tag type: "submit", class: "btn btn-transparent", data: { confirm: _("Are you sure?") } do %span.sr-only - Destroy + = _('Destroy') = icon('trash') - else - = submit_tag 'Destroy', data: { confirm: "Are you sure?" }, class: submit_btn_css + = submit_tag _('Destroy'), data: { confirm: _("Are you sure?") }, class: submit_btn_css diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index be0935b8313..1ddd0df54cd 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -10,16 +10,14 @@ = f.text_area :redirect_uri, class: 'form-control', required: true %span.form-text.text-muted - Use one line per URI + = _('Use one line per URI') - if Doorkeeper.configuration.native_redirect_uri %span.form-text.text-muted - Use - %code= Doorkeeper.configuration.native_redirect_uri - for local tests + = _('Use <code>%{native_redirect_uri}</code> for local tests').html_safe % { native_redirect_uri: Doorkeeper.configuration.native_redirect_uri } .form-group = f.label :scopes, class: 'label-light' = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes .prepend-top-default - = f.submit 'Save application', class: "btn btn-create" + = f.submit _('Save application'), class: "btn btn-create" diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml index 49f90298a50..aad4200f240 100644 --- a/app/views/doorkeeper/applications/edit.html.haml +++ b/app/views/doorkeeper/applications/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", @application.name, "Applications" +- page_title _("Edit"), @application.name, _("Applications") - @content_class = "limit-container-width" unless fluid_layout -%h3.page-title Edit application +%h3.page-title= _('Edit application') = render 'form', application: @application diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index cdf3ff81bd9..ab3a1b100ce 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -1,4 +1,4 @@ -- page_title "Applications" +- page_title _("Applications") - @content_class = "limit-container-width" unless fluid_layout .row.prepend-top-default @@ -7,28 +7,27 @@ = page_title %p - if user_oauth_applications? - Manage applications that can use GitLab as an OAuth provider, - and applications that you've authorized to use your account. + = _("Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account.") - else - Manage applications that you've authorized to use your account. + = _("Manage applications that you've authorized to use your account.") .col-lg-8 - if user_oauth_applications? %h5.prepend-top-0 - Add new application + = _('Add new application') = render 'form', application: @application %hr - if user_oauth_applications? .oauth-applications %h5 - Your applications (#{@applications.size}) + = _("Your applications (%{size})") % { size: @applications.size } - if @applications.any? .table-responsive %table.table %thead %tr - %th Name - %th Callback URL - %th Clients + %th= _('Name') + %th= _('Callback URL') + %th= _('Clients') %th.last-heading %tbody - @applications.each do |application| @@ -41,25 +40,25 @@ %td = link_to edit_oauth_application_path(application), class: "btn btn-transparent append-right-5" do %span.sr-only - Edit + = _('Edit') = icon('pencil') = render 'delete_form', application: application, small: true - else .settings-message.text-center - You don't have any applications + = _("You don't have any applications") .oauth-authorized-applications.prepend-top-20.append-bottom-default - if user_oauth_applications? %h5 - Authorized applications (#{@authorized_tokens.size}) + = _("Authorized applications (%{size})") % { size: @authorized_tokens.size } - if @authorized_tokens.any? .table-responsive %table.table.table-striped %thead %tr - %th Name - %th Authorized At - %th Scope + %th= _('Name') + %th= _('Authorized At') + %th= _('Scope') %th %tbody - @authorized_apps.each do |app| @@ -72,12 +71,12 @@ - @authorized_anonymous_tokens.each do |token| %tr %td - Anonymous + = _('Anonymous') .form-text.text-muted - %em Authorization was granted by entering your username and password in the application. + %em= _("Authorization was granted by entering your username and password in the application.") %td= token.created_at %td= token.scopes %td= render 'doorkeeper/authorized_applications/delete_form', token: token - else .settings-message.text-center - You don't have any authorized applications + = _("You don't have any authorized applications") diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml index d3692d1f759..a66fab20d7c 100644 --- a/app/views/doorkeeper/applications/new.html.haml +++ b/app/views/doorkeeper/applications/new.html.haml @@ -1,6 +1,6 @@ -- page_title "New Application" +- page_title _("New Application") -%h3.page-title New Application +%h3.page-title= _("New Application") %hr diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml index 89ad626f73f..bb76ac6d5f6 100644 --- a/app/views/doorkeeper/applications/show.html.haml +++ b/app/views/doorkeeper/applications/show.html.haml @@ -1,27 +1,27 @@ -- add_to_breadcrumbs "Applications", oauth_applications_path +- add_to_breadcrumbs _("Applications"), oauth_applications_path - breadcrumb_title @application.name -- page_title @application.name, "Applications" +- page_title @application.name, _("Applications") - @content_class = "limit-container-width" unless fluid_layout %h3.page-title - Application: #{@application.name} + = _("Application: %{name}") % { name: @application.name } .table-holder.oauth-application-show %table.table %tr %td - Application Id + = _('Application Id') %td %code#application_id= @application.uid %tr %td - Secret: + = _('Secret:') %td %code#secret= @application.secret %tr %td - Callback url + = _('Callback url') %td - @application.redirect_uri.split.each do |uri| %div @@ -30,5 +30,5 @@ = render "shared/tokens/scopes_list", token: @application .form-actions - = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left' + = link_to _('Edit'), edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left' = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml index 6117b00149f..32b4ccb0fe6 100644 --- a/app/views/doorkeeper/authorizations/error.html.haml +++ b/app/views/doorkeeper/authorizations/error.html.haml @@ -1,3 +1,3 @@ -%h3.page-title An error has occurred +%h3.page-title= _("An error has occurred") %main{ :role => "main" } %pre= @pre_auth.error_response.body[:error_description] diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index 28cdc7607e0..ca62a59d909 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -3,34 +3,28 @@ .modal-content .modal-header %h3.page-title - Authorize - = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer' - to use your account? + - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer') + = _("Authorize %{link_to_client} to use your account?") .modal-body - if current_user.admin? .text-warning %p = icon("exclamation-triangle fw") - You are an admin, which means granting access to - %strong= @pre_auth.client.name - will allow them to interact with GitLab as an admin as well. Proceed with caution. + = _('You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution.').html_safe % { client_name: @pre_auth.client.name } %p - An application called - = link_to @pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer' - is requesting access to your GitLab account. + - link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer') + = _("An application called %{link_to_client} is requesting access to your GitLab account.").html_safe % { link_to_client: link_to_client } - auth_app_owner = @pre_auth.client.application.owner - if auth_app_owner - This application was created by - = succeed "." do - = link_to auth_app_owner.name, user_path(auth_app_owner) + - link_to_owner = link_to(auth_app_owner.name, user_path(auth_app_owner)) + = _("This application was created by %{link_to_owner}.").html_safe % { link_to_owner: link_to_owner } - Please note that this application is not provided by GitLab and you should verify its authenticity before - allowing access. + = _("Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access.") - if @pre_auth.scopes %p - This application will be able to: + = _("This application will be able to:") %ul - @pre_auth.scopes.each do |scope| %li @@ -44,7 +38,7 @@ = hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :nonce, @pre_auth.nonce - = submit_tag "Deny", class: "btn btn-danger" + = submit_tag _("Deny"), class: "btn btn-danger" = form_tag oauth_authorization_path, method: :post, class: 'inline' do = hidden_field_tag :client_id, @pre_auth.client.uid = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri @@ -52,4 +46,4 @@ = hidden_field_tag :response_type, @pre_auth.response_type = hidden_field_tag :scope, @pre_auth.scope = hidden_field_tag :nonce, @pre_auth.nonce - = submit_tag "Authorize", class: "btn btn-success prepend-left-10" + = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10" diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml index 44e868e6782..e4bfd69e7f8 100644 --- a/app/views/doorkeeper/authorizations/show.html.haml +++ b/app/views/doorkeeper/authorizations/show.html.haml @@ -1,3 +1,3 @@ -%h3.page-title Authorization code: +%h3.page-title= _("Authorization code:") %main{ :role => "main" } %code#authorization_code= params[:code] diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml index 11c1e67878e..08f2442f025 100644 --- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -6,4 +6,4 @@ = form_tag path do %input{ :name => "_method", :type => "hidden", :value => "delete" }/ - = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm' + = submit_tag _('Revoke'), onclick: "return confirm('#{_('Are you sure?')}')", class: 'btn btn-remove btn-sm' diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml index 30c9d02b72e..8e73298b250 100644 --- a/app/views/doorkeeper/authorized_applications/index.html.haml +++ b/app/views/doorkeeper/authorized_applications/index.html.haml @@ -1,12 +1,12 @@ %header - %h1 Your authorized applications + %h1= _("Your authorized applications") %main{ :role => "main" } .table-holder %table.table.table-striped %thead %tr - %th Application - %th Created At + %th= _('Application') + %th= _('Created At') %th %th %tbody diff --git a/app/views/explore/_head.html.haml b/app/views/explore/_head.html.haml index a3b0709e261..eefc797cf03 100644 --- a/app/views/explore/_head.html.haml +++ b/app/views/explore/_head.html.haml @@ -1,6 +1,6 @@ .explore-title.text-center %h2 - Explore GitLab + = _("Explore GitLab") %p.lead - Discover projects, groups and snippets. Share your projects with others + = _("Discover projects, groups and snippets. Share your projects with others") %br diff --git a/app/views/explore/groups/_nav.html.haml b/app/views/explore/groups/_nav.html.haml index ab4787c6d05..c337149a2f3 100644 --- a/app/views/explore/groups/_nav.html.haml +++ b/app/views/explore/groups/_nav.html.haml @@ -2,7 +2,7 @@ %ul.nav-links.nav.nav-tabs = nav_link(page: explore_groups_path) do = link_to explore_groups_path do - Explore Groups + = _("Explore Groups") .nav-controls = render 'shared/groups/search_form' = render 'shared/groups/dropdown' diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 0643b9cfbc5..387c37b7a91 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Groups" -- header_title "Groups", dashboard_groups_path +- page_title _("Groups") +- header_title _("Groups"), dashboard_groups_path - if current_user = render 'dashboard/groups_head' @@ -10,14 +10,14 @@ - if cookies[:explore_groups_landing_dismissed] != 'true' .explore-groups.landing.content-block.js-explore-groups-landing.hide - %button.dismiss-button{ type: 'button', 'aria-label' => 'Dismiss' }= icon('times') + %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= icon('times') .svg-container = custom_icon('icon_explore_groups_splash') .inner-content - %p Below you will find all the groups that are public. - %p You can easily contribute to them by requesting to join these groups. + %p= _("Below you will find all the groups that are public.") + %p= _("You can easily contribute to them by requesting to join these groups.") - if params[:filter].blank? && @groups.empty? - .nothing-here-block No public groups + .nothing-here-block= _("No public groups") - else = render 'groups' diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 6abb56ba6d2..b694103ccaf 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -2,16 +2,16 @@ .dropdown %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' } = icon('globe') - %span.light Visibility: + %span.light= _("Visibility:") - if params[:visibility_level].present? = visibility_level_label(params[:visibility_level].to_i) - else - Any + = _('Any') = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right %li = link_to filter_projects_path(visibility_level: nil) do - Any + = _('Any') - Gitlab::VisibilityLevel.values.each do |level| %li{ class: active_when(level.to_s == params[:visibility_level]) || 'light' } = link_to filter_projects_path(visibility_level: level) do diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml index 558cd26f1e0..bf65c19b720 100644 --- a/app/views/explore/projects/_nav.html.haml +++ b/app/views/explore/projects/_nav.html.haml @@ -2,13 +2,13 @@ %ul.nav-links.nav.nav-tabs = nav_link(page: [trending_explore_projects_path, explore_root_path]) do = link_to trending_explore_projects_path do - Trending + = _('Trending') = nav_link(page: starred_explore_projects_path) do = link_to starred_explore_projects_path do - Most stars + = _('Most stars') = nav_link(page: explore_projects_path) do = link_to explore_projects_path do - All + = _('All') .nav-controls - unless current_user diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index f00802e0af7..452f390695c 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Projects" -- header_title "Projects", dashboard_projects_path +- page_title _("Projects") +- header_title _("Projects"), dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index f00802e0af7..452f390695c 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Projects" -- header_title "Projects", dashboard_projects_path +- page_title _("Projects") +- header_title _("Projects"), dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index f00802e0af7..452f390695c 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,6 +1,6 @@ - @hide_top_links = true -- page_title "Projects" -- header_title "Projects", dashboard_projects_path +- page_title _("Projects") +- header_title _("Projects"), dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml index 577c63503a8..82a497289f3 100644 --- a/app/views/groups/_activities.html.haml +++ b/app/views/groups/_activities.html.haml @@ -1,8 +1,8 @@ .nav-block.activities + = render 'shared/event_filter' .controls = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do %i.fa.fa-rss - = render 'shared/event_filter' .content_list = spinner diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index 5e7be5cd37b..f0d1e837317 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -21,23 +21,13 @@ %th= _('Status') %tbody - @already_added_projects.each do |project| - %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } + %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) } %td = provider_project_link(provider, project.import_source) %td = link_to project.full_path, [project.namespace.becomes(Namespace), project] %td.job-status - - if project.import_status == 'finished' - %span - %i.fa.fa-check - = _('Done') - - elsif project.import_status == 'started' - %i.fa.fa-spinner.fa-spin - = _('Started') - - elsif project.import_status == 'failed' - = _('Failed') - - else - = project.human_import_status_name + = render 'import/project_status', project: project - @repos.each do |repo| %tr{ id: "repo_#{repo.id}", data: { qa: { repo_path: repo.full_name } } } @@ -61,6 +51,6 @@ = has_ci_cd_only_params? ? _('Connect') : _('Import') = icon("spinner spin", class: "loading-icon") -.js-importer-status{ data: { jobs_import_path: "#{url_for([:jobs, :import, provider])}", - import_path: "#{url_for([:import, provider])}", - ci_cd_only: "#{has_ci_cd_only_params?}" } } +.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]), + import_path: url_for([:import, provider]), + ci_cd_only: has_ci_cd_only_params?.to_s } } diff --git a/app/views/import/_project_status.html.haml b/app/views/import/_project_status.html.haml new file mode 100644 index 00000000000..280bcbc1e63 --- /dev/null +++ b/app/views/import/_project_status.html.haml @@ -0,0 +1,11 @@ +- case project.import_status +- when 'finished' + = icon('check') + = _('Done') +- when 'started' + = icon("spinner spin") + = _('Started') +- when 'failed' + = _('Failed') +- else + = project.human_import_status_name diff --git a/app/views/import/bitbucket/deploy_key.js.haml b/app/views/import/bitbucket/deploy_key.js.haml index 81b34ab5c9d..99e8ac1afa1 100644 --- a/app/views/import/bitbucket/deploy_key.js.haml +++ b/app/views/import/bitbucket/deploy_key.js.haml @@ -1,3 +1,3 @@ :plain job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>") + job.find(".import-actions").html("<p class='alert alert-danger'>#{_('Access denied! Please verify you can add deploy keys to this repository.')}</p>") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 4e8f715db4f..a75b7aa9dd2 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -1,22 +1,22 @@ -- page_title 'Bitbucket import' -- header_title 'Projects', root_path +- page_title _('Bitbucket import') +- header_title _('Projects'), root_path %h3.page-title %i.fa.fa-bitbucket - Import projects from Bitbucket + = _('Import projects from Bitbucket') - if @repos.any? %p.light - Select projects you want to import. + = _('Select projects you want to import.') %hr %p - if @incompatible_repos.any? = button_tag class: 'btn btn-import btn-success js-import-all' do - Import all compatible projects + = _('Import all compatible projects') = icon('spinner spin', class: 'loading-icon') - else = button_tag class: 'btn btn-import btn-success js-import-all' do - Import all projects + = _('Import all projects') = icon('spinner spin', class: 'loading-icon') .table-responsive @@ -26,9 +26,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From Bitbucket - %th To GitLab - %th Status + %th= _('From Bitbucket') + %th= _('To GitLab') + %th= _('Status') %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -40,10 +40,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _('done') - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _('started') - else = project.human_import_status_name @@ -66,7 +66,7 @@ = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: 'btn btn-import js-add-to-import' do - Import + = _('Import') = icon('spinner spin', class: 'loading-icon') - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } @@ -74,16 +74,13 @@ = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status - = label_tag 'Incompatible Project', nil, class: 'label badge-danger' + = label_tag _('Incompatible Project'), nil, class: 'label badge-danger' - if @incompatible_repos.any? %p - One or more of your Bitbucket projects cannot be imported into GitLab - directly because they use Subversion or Mercurial for version control, - rather than Git. Please convert - = link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview' - and go through the - = link_to 'import flow', status_import_bitbucket_path - again. + = _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.") + - link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview') + - link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path) + = _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } } diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml index 74d686b6703..b54b1af1e0c 100644 --- a/app/views/import/fogbugz/new.html.haml +++ b/app/views/import/fogbugz/new.html.haml @@ -1,26 +1,24 @@ -- page_title "FogBugz Import" -- header_title "Projects", root_path +- page_title _("FogBugz Import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-bug - Import projects from FogBugz + = _('Import projects from FogBugz') %hr = form_tag callback_import_fogbugz_path do %p - To get started you enter your FogBugz URL and login information below. - In the next steps, you'll be able to map users and select the projects - you want to import. + = _("To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import.") .form-group.row - = label_tag :uri, 'FogBugz URL', class: 'col-form-label col-md-2' + = label_tag :uri, _('FogBugz URL'), class: 'col-form-label col-md-2' .col-md-4 = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control' .form-group.row - = label_tag :email, 'FogBugz Email', class: 'col-form-label col-md-2' + = label_tag :email, _('FogBugz Email'), class: 'col-form-label col-md-2' .col-md-4 = text_field_tag :email, nil, class: 'form-control' .form-group.row - = label_tag :password, 'FogBugz Password', class: 'col-form-label col-md-2' + = label_tag :password, _('FogBugz Password'), class: 'col-form-label col-md-2' .col-md-4 = password_field_tag :password, nil, class: 'form-control' .form-actions - = submit_tag 'Continue to the next step', class: 'btn btn-create' + = submit_tag _('Continue to the next step'), class: 'btn btn-create' diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index d27c5d3c36d..ff2f989c509 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -1,39 +1,33 @@ -- page_title 'User map', 'FogBugz import' -- header_title "Projects", root_path +- page_title _('User map'), _('FogBugz import') +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-bug - Import projects from FogBugz + = _('Import projects from FogBugz') %hr = form_tag create_user_map_import_fogbugz_path do %p - Customize how FogBugz email addresses and usernames are imported into GitLab. - In the next step, you'll be able to select the projects you want to import. + = _("Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.") %p - The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below. + = _("The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below.") %ul %li - %strong Default: Map a FogBugz account ID to a full name + %strong= _("Default: Map a FogBugz account ID to a full name") %p - An empty GitLab User field will add the FogBugz user's full name - (e.g. "By John Smith") in the description of all issues and comments. - It will also associate and/or assign these issues and comments with - the project creator. + = _("An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator.") %li - %strong Map a FogBugz account ID to a GitLab user + %strong= _("Map a FogBugz account ID to a GitLab user") %p - Selecting a GitLab user will add a link to the GitLab user in the descriptions - of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also - associate and/or assign these issues and comments with the selected user. + = _('Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also associate and/or assign these issues and comments with the selected user.').html_safe .table-holder %table.table %thead %tr - %th ID - %th Name - %th Email - %th GitLab User + %th= _("ID") + %th= _("Name") + %th= _("Email") + %th= _("GitLab User") %tbody - @user_map.each do |id, user| %tr @@ -45,4 +39,4 @@ scope: :all, email_user: true, selected: user[:gitlab_user]) .form-actions - = submit_tag 'Continue to the next step', class: 'btn btn-create' + = submit_tag _('Continue to the next step'), class: 'btn btn-create' diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index 7b832c6a23a..830d141ebea 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -1,20 +1,19 @@ -- page_title "FogBugz import" -- header_title "Projects", root_path +- page_title _("FogBugz import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-bug - Import projects from FogBugz + = _('Import projects from FogBugz') - if @repos.any? %p.light - Select projects you want to import. + = _('Select projects you want to import.') %p.light - Optionally, you can - = link_to 'customize', new_user_map_import_fogbugz_path - how FogBugz email addresses and usernames are imported into GitLab. + - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path) + = _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize } %hr %p = button_tag class: 'btn btn-import btn-success js-import-all' do - Import all projects + = _('Import all projects') = icon("spinner spin", class: "loading-icon") .table-responsive @@ -24,9 +23,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From FogBugz - %th To GitLab - %th Status + %th= _("From FogBugz") + %th= _("To GitLab") + %th= _("Status") %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -38,10 +37,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _("done") - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _("started") - else = project.human_import_status_name @@ -53,7 +52,7 @@ #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - Import + = _("Import") = icon("spinner spin", class: "loading-icon") .js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } } diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml index 581576a8a3d..2b3102f9af9 100644 --- a/app/views/import/gitea/new.html.haml +++ b/app/views/import/gitea/new.html.haml @@ -1,23 +1,22 @@ -- page_title "Gitea Import" -- header_title "Projects", root_path +- page_title _("Gitea Import") +- header_title _("Projects"), root_path %h3.page-title = custom_icon('go_logo') - Import Projects from Gitea + = _('Import Projects from Gitea') %p - To get started, please enter your Gitea Host URL and a - = succeed '.' do - = link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token' + - link_to_personal_token = link_to(_('Personal Access Token'), 'https://github.com/gogits/go-gogs-client/wiki#access-token') + = _('To get started, please enter your Gitea Host URL and a %{link_to_personal_token}.').html_safe % { link_to_personal_token: link_to_personal_token } = form_tag personal_access_token_import_gitea_path do .form-group.row - = label_tag :gitea_host_url, 'Gitea Host URL', class: 'col-form-label col-sm-2' + = label_tag :gitea_host_url, _('Gitea Host URL'), class: 'col-form-label col-sm-2' .col-sm-4 = text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control' .form-group.row - = label_tag :personal_access_token, 'Personal Access Token', class: 'col-form-label col-sm-2' + = label_tag :personal_access_token, _('Personal Access Token'), class: 'col-form-label col-sm-2' .col-sm-4 = text_field_tag :personal_access_token, nil, class: 'form-control' .form-actions - = submit_tag 'List Your Gitea Repositories', class: 'btn btn-create' + = submit_tag _('List Your Gitea Repositories'), class: 'btn btn-create' diff --git a/app/views/import/gitea/status.html.haml b/app/views/import/gitea/status.html.haml index 589ca27e45d..88244fde16b 100644 --- a/app/views/import/gitea/status.html.haml +++ b/app/views/import/gitea/status.html.haml @@ -1,7 +1,7 @@ -- page_title "Gitea Import" -- header_title "Projects", root_path +- page_title _("Gitea Import") +- header_title _("Projects"), root_path %h3.page-title = custom_icon('go_logo') - Import Projects from Gitea + = _('Import Projects from Gitea') = render 'import/githubish_status', provider: 'gitea' diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index b9ebb1a39d9..6ff25f2c842 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -1,7 +1,7 @@ - title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import') - page_title title - breadcrumb_title title -- header_title "Projects", root_path +- header_title _("Projects"), root_path %h3.page-title = icon 'github', text: import_github_title diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index b00b972d9c9..be057be6d1a 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -1,7 +1,7 @@ - title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import') - page_title title - breadcrumb_title title -- header_title "Projects", root_path +- header_title _("Projects"), root_path %h3.page-title = icon 'github', text: import_github_title diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index 37734414835..b7bfbae5edf 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -1,15 +1,15 @@ -- page_title "GitLab.com import" -- header_title "Projects", root_path +- page_title _("GitLab.com import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-heart - Import projects from GitLab.com + = _('Import projects from GitLab.com') %p.light - Select projects you want to import. + = _('Select projects you want to import.') %hr %p = button_tag class: "btn btn-import btn-success js-import-all" do - Import all projects + = _('Import all projects') = icon("spinner spin", class: "loading-icon") .table-responsive @@ -19,9 +19,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From GitLab.com - %th To this GitLab instance - %th Status + %th= _('From GitLab.com') + %th= _('To this GitLab instance') + %th= _('Status') %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -33,10 +33,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _('done') - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _('started') - else = project.human_import_status_name @@ -48,7 +48,7 @@ = import_project_target(repo['namespace']['path'], repo['name']) %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - Import + = _('Import') = icon("spinner spin", class: "loading-icon") .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } } diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index cc672a5ea7c..a258fc64b1e 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -1,9 +1,9 @@ -- page_title "GitLab Import" -- header_title "Projects", root_path +- page_title _("GitLab Import") +- header_title _("Projects"), root_path %h3.page-title = icon('gitlab') - Import an exported GitLab project + = _('Import an exported GitLab project') %hr = form_tag import_gitlab_project_path, class: 'new_project', multipart: true do @@ -24,19 +24,19 @@ #{user_url(current_user.username)}/ = hidden_field_tag :namespace_id, value: current_user.namespace_id .form-group.col-12.col-sm-6.project-path - = label_tag :path, 'Project name', class: 'label-light' + = label_tag :path, _('Project name'), class: 'label-light' = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true .row .form-group.col-md-12 - To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here. + = _("To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here.") .row .form-group.col-sm-12 = hidden_field_tag :namespace_id, @namespace.id - = label_tag :file, 'GitLab project export', class: 'label-light' + = label_tag :file, _('GitLab project export'), class: 'label-light' .form-group = file_field_tag :file, class: '' .row .form-actions.col-sm-12 - = submit_tag 'Import project', class: 'btn btn-create' - = link_to 'Cancel', new_project_path, class: 'btn btn-cancel' + = submit_tag _('Import project'), class: 'btn btn-create' + = link_to _('Cancel'), new_project_path, class: 'btn btn-cancel' diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index 2f1fb8d9c56..fd6e4726fc5 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -1,62 +1,62 @@ -- page_title "Google Code import" -- header_title "Projects", root_path +- page_title _("Google Code import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-google - Import projects from Google Code + = _('Import projects from Google Code') %hr = form_tag callback_import_google_code_path, multipart: true do %p - Follow the steps below to export your Google Code project data. - In the next step, you'll be able to select the projects you want to import. + = _('Follow the steps below to export your Google Code project data.') + = _("In the next step, you'll be able to select the projects you want to import.") %ol %li %p - Go to - #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer'}. + - link_to_google_takeout = link_to(_("Google Takeout"), "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer') + = _("Go to %{link_to_google_takeout}.").html_safe % { link_to_google_takeout: link_to_google_takeout } %li %p - Make sure you're logged into the account that owns the projects you'd like to import. + = _("Make sure you're logged into the account that owns the projects you'd like to import.") %li %p - Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting". + = _('Click the <strong>Select none</strong> button on the right, since we only need "Google Code Project Hosting".').html_safe %li %p - Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right. + = _('Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right.').html_safe %li %p - Choose <strong>Next</strong> at the bottom of the page. + = _('Choose <strong>Next</strong> at the bottom of the page.').html_safe %li %p - Leave the "File type" and "Delivery method" options on their default values. + = _('Leave the "File type" and "Delivery method" options on their default values.') %li %p - Choose <strong>Create archive</strong> and wait for archiving to complete. + = _('Choose <strong>Create archive</strong> and wait for archiving to complete.').html_safe %li %p - Click the <strong>Download</strong> button and wait for downloading to complete. + = _('Click the <strong>Download</strong> button and wait for downloading to complete.').html_safe %li %p - Find the downloaded ZIP file and decompress it. + = _('Find the downloaded ZIP file and decompress it.') %li %p - Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file. + = _('Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file.').html_safe %li %p - Upload <code>GoogleCodeProjectHosting.json</code> here: + = _('Upload <code>GoogleCodeProjectHosting.json</code> here:').html_safe %p %input{ type: "file", name: "dump_file", id: "dump_file" } %li %p - Do you want to customize how Google Code email addresses and usernames are imported into GitLab? + = _('Do you want to customize how Google Code email addresses and usernames are imported into GitLab?') %p = label_tag :create_user_map_0 do = radio_button_tag :create_user_map, 0, true - No, directly import the existing email addresses and usernames. + = _('No, directly import the existing email addresses and usernames.') %p = label_tag :create_user_map_1 do = radio_button_tag :create_user_map, 1, false - Yes, let me map Google Code users to full names or GitLab users. + = _('Yes, let me map Google Code users to full names or GitLab users.') %li %p - = submit_tag 'Continue to the next step', class: "btn btn-create" + = submit_tag _('Continue to the next step'), class: "btn btn-create" diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml index 91c774f575c..baaaf6bdc63 100644 --- a/app/views/import/google_code/new_user_map.html.haml +++ b/app/views/import/google_code/new_user_map.html.haml @@ -1,44 +1,36 @@ -- page_title "User map", "Google Code import" -- header_title "Projects", root_path +- page_title _("User map"), _("Google Code import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-google - Import projects from Google Code + = _('Import projects from Google Code') %hr = form_tag create_user_map_import_google_code_path do %p - Customize how Google Code email addresses and usernames are imported into GitLab. - In the next step, you'll be able to select the projects you want to import. + = _("Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.") %p - The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side. + = _("The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side.").html_safe %ul %li - %strong Default: Directly import the Google Code email address or username + %strong= _("Default: Directly import the Google Code email address or username") %p - <code>"johnsmith@example.com": "johnsm...@example.com"</code> - will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. - The email address or username is masked to ensure the user's privacy. + = _('<code>"johnsmith@example.com": "johnsm...@example.com"</code> will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user\'s privacy.').html_safe %li - %strong Map a Google Code user to a GitLab user + %strong= _("Map a Google Code user to a GitLab user") %p - <code>"johnsmith@example.com": "@johnsmith"</code> - will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, - and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com. + = _('<code>"johnsmith@example.com": "@johnsmith"</code> will add "By <a href="#">@johnsmith</a>" to all issues and comments originally created by johnsmith@example.com, and will set <a href="#">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com.').html_safe %li - %strong Map a Google Code user to a full name + %strong= _("Map a Google Code user to a full name") %p - <code>"johnsmith@example.com": "John Smith"</code> - will add "By John Smith" to all issues and comments originally created by johnsmith@example.com. + = _('<code>"johnsmith@example.com": "John Smith"</code> will add "By John Smith" to all issues and comments originally created by johnsmith@example.com.').html_safe %li - %strong Map a Google Code user to a full email address + %strong= _("Map a Google Code user to a full email address") %p - <code>"johnsmith@example.com": "johnsmith@example.com"</code> - will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. - By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address. + = _('<code>"johnsmith@example.com": "johnsmith@example.com"</code> will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user\'s privacy. Use this option if you want to show the full email address.').html_safe .form-group.row .col-sm-12 = text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15 .form-actions - = submit_tag 'Continue to the next step', class: "btn btn-create" + = submit_tag _('Continue to the next step'), class: "btn btn-create" diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index acf7a108cb0..347e2820f94 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -1,25 +1,24 @@ -- page_title "Google Code import" -- header_title "Projects", root_path +- page_title _("Google Code import") +- header_title _("Projects"), root_path %h3.page-title %i.fa.fa-google - Import projects from Google Code + = _('Import projects from Google Code') - if @repos.any? %p.light - Select projects you want to import. + = _('Select projects you want to import.') %p.light - Optionally, you can - = link_to "customize", new_user_map_import_google_code_path - how Google Code email addresses and usernames are imported into GitLab. + - link_to_customize = link_to(_("customize"), new_user_map_import_google_code_path) + = _("Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab.").html_safe % { link_to_customize: link_to_customize } %hr %p - if @incompatible_repos.any? = button_tag class: "btn btn-import btn-success js-import-all" do - Import all compatible projects + = _("Import all compatible projects") = icon("spinner spin", class: "loading-icon") - else = button_tag class: "btn btn-import btn-success js-import-all" do - Import all projects + = _("Import all projects") = icon("spinner spin", class: "loading-icon") .table-responsive @@ -29,9 +28,9 @@ %colgroup.import-jobs-status-col %thead %tr - %th From Google Code - %th To GitLab - %th Status + %th= _("From Google Code") + %th= _("To GitLab") + %th= _("Status") %tbody - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } @@ -43,10 +42,10 @@ - if project.import_status == 'finished' %span %i.fa.fa-check - done + = _("done") - elsif project.import_status == 'started' %i.fa.fa-spinner.fa-spin - started + = _("started") - else = project.human_import_status_name @@ -58,7 +57,7 @@ #{current_user.username}/#{repo.name} %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do - Import + = _("Import") = icon("spinner spin", class: "loading-icon") - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.id}" } @@ -66,15 +65,12 @@ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status - = label_tag "Incompatible Project", nil, class: "label badge-danger" + = label_tag _("Incompatible Project"), nil, class: "label badge-danger" - if @incompatible_repos.any? %p - One or more of your Google Code projects cannot be imported into GitLab - directly because they use Subversion or Mercurial for version control, - rather than Git. Please convert them to Git on Google Code, and go - through the - = link_to "import flow", new_import_google_code_path - again. + = _("One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.") + - link_to_import_flow = link_to(_("import flow"), new_import_google_code_path) + = _("Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again.").html_safe % { link_to_import_flow: link_to_import_flow } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } } diff --git a/app/views/import/manifest/_form.html.haml b/app/views/import/manifest/_form.html.haml new file mode 100644 index 00000000000..763beb5958f --- /dev/null +++ b/app/views/import/manifest/_form.html.haml @@ -0,0 +1,23 @@ += form_tag upload_import_manifest_path, multipart: true do + .form-group + = label_tag :group_id, nil, class: 'label-light' do + = _('Group') + .input-group + .input-group-prepend.has-tooltip{ title: root_url } + .input-group-text + = root_url + = select_tag :group_id, namespaces_options(nil, display_path: true, groups_only: true), { class: 'select2 js-select-namespace' } + .form-text.text-muted + = _('Choose the top-level group for your repository imports.') + + .form-group + = label_tag :manifest, class: 'label-light' do + = _('Manifest') + = file_field_tag :manifest, class: 'form-control-file', required: true + .form-text.text-muted + = _('Import multiple repositories by uploading a manifest file.') + = link_to icon('question-circle'), help_page_path('user/project/import/manifest') + + .append-bottom-10 + = submit_tag _('List available repositories'), class: 'btn btn-success' + = link_to _('Cancel'), new_project_path, class: 'btn btn-cancel' diff --git a/app/views/import/manifest/new.html.haml b/app/views/import/manifest/new.html.haml new file mode 100644 index 00000000000..056e4922b9e --- /dev/null +++ b/app/views/import/manifest/new.html.haml @@ -0,0 +1,12 @@ +- page_title "Manifest file import" +- header_title "Projects", root_path + +%h3.page-title + = _('Manifest file import') + +- if @errors.present? + .alert.alert-danger + - @errors.each do |error| + = error + += render 'form' diff --git a/app/views/import/manifest/status.html.haml b/app/views/import/manifest/status.html.haml new file mode 100644 index 00000000000..5b2e1005398 --- /dev/null +++ b/app/views/import/manifest/status.html.haml @@ -0,0 +1,42 @@ +- page_title "Manifest import" +- header_title "Projects", root_path +- provider = 'manifest' + +%h3.page-title + = _('Manifest file import') + +%p + = button_tag class: "btn btn-import btn-success js-import-all" do + = import_all_githubish_repositories_button_label + = icon("spinner spin", class: "loading-icon") + +.table-responsive + %table.table.import-jobs + %thead + %tr + %th= _('Repository URL') + %th= _('To GitLab') + %th= _('Status') + %tbody + - @already_added_projects.each do |project| + %tr{ id: "project_#{project.id}", class: project_status_css_class(project.import_status) } + %td + = project.import_url + %td + = link_to_project project + %td.job-status + = render 'import/project_status', project: project + + - @pending_repositories.each do |repository| + %tr{ id: "repo_#{repository[:id]}" } + %td + = repository[:url] + %td.import-target + = import_project_target(@group.full_path, repository[:path]) + %td.import-actions.job-status + = button_tag class: "btn btn-import js-add-to-import" do + = _('Import') + = icon("spinner spin", class: "loading-icon") + +.js-importer-status{ data: { jobs_import_path: url_for([:jobs, :import, provider]), + import_path: url_for([:import, provider]) } } diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 1f701f2aa1b..6bf21570d41 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,10 +1,9 @@ %div{ class: container_class } .nav-block.activity-filter-block.activities + = render 'shared/event_filter' .controls = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do = icon('rss') - = render 'shared/event_filter' - .content_list.project-activity{ :"data-href" => activity_project_path(@project) } = spinner diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 8f535b9d789..3da6db08580 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -1,49 +1,62 @@ - active_tab = local_assigns.fetch(:active_tab, 'blank') -- f = local_assigns.fetch(:f) .project-import .form-group.import-btn-container.clearfix - = f.label :visibility_level, class: 'label-light' do #the label here seems wrong + %h5 Import project from .import-buttons - if gitlab_project_import_enabled? .import_gitlab_project.has-tooltip{ data: { container: 'body' } } = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = icon('gitlab', text: 'GitLab export') - %div - - if github_import_enabled? + + - if github_import_enabled? + %div = link_to new_import_github_path, class: 'btn js-import-github' do = icon('github', text: 'GitHub') - %div - - if bitbucket_import_enabled? + + - if bitbucket_import_enabled? + %div = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do = icon('bitbucket', text: 'Bitbucket') - unless bitbucket_import_configured? = render 'bitbucket_import_modal' - %div - - if gitlab_import_enabled? + + - if gitlab_import_enabled? + %div = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do = icon('gitlab', text: 'GitLab.com') - unless gitlab_import_configured? = render 'gitlab_import_modal' - %div - - if google_code_import_enabled? + + - if google_code_import_enabled? + %div = link_to new_import_google_code_path, class: 'btn import_google_code' do = icon('google', text: 'Google Code') - %div - - if fogbugz_import_enabled? + + - if fogbugz_import_enabled? + %div = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do = icon('bug', text: 'Fogbugz') - %div - - if gitea_import_enabled? + + - if gitea_import_enabled? + %div = link_to new_import_gitea_path, class: 'btn import_gitea' do = custom_icon('go_logo') Gitea - %div - - if git_import_enabled? + + - if git_import_enabled? + %div %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } } = icon('git', text: 'Repo by URL') + + - if manifest_import_enabled? + %div + = link_to new_import_manifest_path, class: 'btn import_manifest' do + = icon('file-text-o', text: 'Manifest file') + .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } - %hr + = form_for @project, html: { class: 'new_project' } do |f| + %hr = render "shared/import_form", f: f = render 'new_project_fields', f: f, project_name_id: "import-url-name" diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index a33bc9d4ce6..c7890b37381 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -16,7 +16,7 @@ ? .modal-body %p= s_('Environments|Are you sure you want to stop this environment?') - - unless @environment.stop_action? + - unless @environment.stop_action_available? .warning_message %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, emphasis_end: '</strong>'.html_safe, diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml index 8a390cf8700..1a9ab288683 100644 --- a/app/views/projects/merge_requests/_mr_box.html.haml +++ b/app/views/projects/merge_requests/_mr_box.html.haml @@ -1,4 +1,4 @@ -.detail-page-description.content-block +.detail-page-description %h2.title = markdown_field(@merge_request, :title) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 5bb1bfb7059..6c363345e38 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -55,13 +55,12 @@ = render 'project_templates', f: f .tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' } - = form_for @project, html: { class: 'new_project' } do |f| - - if import_sources_enabled? - = render 'import_project_pane', f: f, active_tab: active_tab - - else - .nothing-here-block - %h4 No import options available - %p Contact an administrator to enable options for importing your project. + - if import_sources_enabled? + = render 'import_project_pane', active_tab: active_tab + - else + .nothing-here-block + %h4 No import options available + %p Contact an administrator to enable options for importing your project. .save-project-loader.d-none .center diff --git a/app/views/projects/protected_branches/_update_protected_branch.html.haml b/app/views/projects/protected_branches/_update_protected_branch.html.haml index f242459f69b..74bfaa9ff80 100644 --- a/app/views/projects/protected_branches/_update_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_update_protected_branch.html.haml @@ -6,5 +6,5 @@ %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level = dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') , - options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header', + options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header', data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }}) diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml index 82ef08272d3..05cee483c0e 100644 --- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -2,7 +2,7 @@ %tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } } %td - %span.ref-name.qa-protected-branch-name= protected_branch.name + %span.ref-name= protected_branch.name - if @project.root_ref?(protected_branch.name) %span.badge.badge-info.prepend-left-5 default diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index ecb5b1c6ebc..7afb7b3a93b 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,4 +1,4 @@ -.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller +.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.flex-fill .fade-left= icon('angle-left') .fade-right= icon('angle-right') %ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs diff --git a/bin/changelog b/bin/changelog index d7b2a1a2de9..758c036161e 100755 --- a/bin/changelog +++ b/bin/changelog @@ -23,6 +23,8 @@ module ChangelogHelpers Abort = Class.new(StandardError) Done = Class.new(StandardError) + MAX_FILENAME_LENGTH = 140 # ecryptfs has a limit of 140 characters + def capture_stdout(cmd) output = IO.popen(cmd, &:read) fail_with "command failed: #{cmd.join(' ')}" unless $?.success? @@ -142,7 +144,9 @@ class ChangelogEntry def initialize(options) @options = options + end + def execute assert_feature_branch! assert_title! assert_new_file! @@ -221,10 +225,12 @@ class ChangelogEntry end def file_path - File.join( + base_path = File.join( unreleased_path, - branch_name.gsub(/[^\w-]/, '-') << '.yml' - ) + branch_name.gsub(/[^\w-]/, '-')) + + # Add padding for .yml extension + base_path[0..MAX_FILENAME_LENGTH - 5] + '.yml' end def unreleased_path @@ -250,7 +256,7 @@ end if $0 == __FILE__ begin options = ChangelogOptionParser.parse(ARGV) - ChangelogEntry.new(options) + ChangelogEntry.new(options).execute rescue ChangelogHelpers::Abort => ex $stderr.puts ex.message exit 1 diff --git a/changelogs/unreleased/46930-fix-updated_at-if-created_at-is-set-note-api.yml b/changelogs/unreleased/46930-fix-updated_at-if-created_at-is-set-note-api.yml new file mode 100644 index 00000000000..d95714a5267 --- /dev/null +++ b/changelogs/unreleased/46930-fix-updated_at-if-created_at-is-set-note-api.yml @@ -0,0 +1,5 @@ +--- +title: Fix updated_at if created_at is set for Note API +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/48745-project-exports-fail-when-uploads-have-been-migrated-to-object-storage.yml b/changelogs/unreleased/48745-project-exports-fail-when-uploads-have-been-migrated-to-object-storage.yml new file mode 100644 index 00000000000..7552e0d3878 --- /dev/null +++ b/changelogs/unreleased/48745-project-exports-fail-when-uploads-have-been-migrated-to-object-storage.yml @@ -0,0 +1,5 @@ +--- +title: Add uploader support to Import/Export uploads +merge_request: 20484 +author: +type: added diff --git a/changelogs/unreleased/48789-remove-event-listeners-scroll.yml b/changelogs/unreleased/48789-remove-event-listeners-scroll.yml new file mode 100644 index 00000000000..9cc3f7adc36 --- /dev/null +++ b/changelogs/unreleased/48789-remove-event-listeners-scroll.yml @@ -0,0 +1,6 @@ +--- +title: Improves performance on Merge Request diff tab by removing the scroll event + listeners being added to every file +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/48894-fix-rss-button-interaction.yml b/changelogs/unreleased/48894-fix-rss-button-interaction.yml new file mode 100644 index 00000000000..546a4233d7e --- /dev/null +++ b/changelogs/unreleased/48894-fix-rss-button-interaction.yml @@ -0,0 +1,5 @@ +--- +title: Fix RSS button interaction on Dashboard, Project and Group activities +merge_request: 20549 +author: +type: fixed diff --git a/changelogs/unreleased/48934.yml b/changelogs/unreleased/48934.yml new file mode 100644 index 00000000000..8e2e53ed198 --- /dev/null +++ b/changelogs/unreleased/48934.yml @@ -0,0 +1,5 @@ +--- +title: Improve danger confirmation modals by focusing input field +merge_request: +author: Jamie Schembri +type: added diff --git a/changelogs/unreleased/49291-fix-memory-graph-component-typo.yml b/changelogs/unreleased/49291-fix-memory-graph-component-typo.yml new file mode 100644 index 00000000000..f21bd454e84 --- /dev/null +++ b/changelogs/unreleased/49291-fix-memory-graph-component-typo.yml @@ -0,0 +1,5 @@ +--- +title: Fix typo in CSS transform property for Memory Graph component +merge_request: 20650 +author: +type: fixed diff --git a/changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml b/changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml new file mode 100644 index 00000000000..4942688d00f --- /dev/null +++ b/changelogs/unreleased/an-no-healthcheck-until-brooklyn.yml @@ -0,0 +1,5 @@ +--- +title: Remove healthchecks from prometheus endpoint +merge_request: 20565 +author: +type: fixed diff --git a/changelogs/unreleased/dz-manifest-import.yml b/changelogs/unreleased/dz-manifest-import.yml new file mode 100644 index 00000000000..b0d29b0869f --- /dev/null +++ b/changelogs/unreleased/dz-manifest-import.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to import multiple repositories by uploading a manifest file +merge_request: 20304 +author: +type: added diff --git a/changelogs/unreleased/fix-gb-add-missing-before-sha-predefined-variable.yml b/changelogs/unreleased/fix-gb-add-missing-before-sha-predefined-variable.yml new file mode 100644 index 00000000000..7e9e8c33a71 --- /dev/null +++ b/changelogs/unreleased/fix-gb-add-missing-before-sha-predefined-variable.yml @@ -0,0 +1,5 @@ +--- +title: Add missing predefined variable and fix docs +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fix-gb-fix-project-settings-build-time-validation.yml b/changelogs/unreleased/fix-gb-fix-project-settings-build-time-validation.yml new file mode 100644 index 00000000000..adf582e34a2 --- /dev/null +++ b/changelogs/unreleased/fix-gb-fix-project-settings-build-time-validation.yml @@ -0,0 +1,5 @@ +--- +title: Limit maximum project build timeout setting to 1 month +merge_request: 20591 +author: +type: fixed diff --git a/changelogs/unreleased/fix-performance-problem-of-tags-query.yml b/changelogs/unreleased/fix-performance-problem-of-tags-query.yml new file mode 100644 index 00000000000..4649775be9c --- /dev/null +++ b/changelogs/unreleased/fix-performance-problem-of-tags-query.yml @@ -0,0 +1,5 @@ +--- +title: Fix performance problem of accessing tag list for projects api endpoints +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/frozen-string-enable-app-services.yml b/changelogs/unreleased/frozen-string-enable-app-services.yml new file mode 100644 index 00000000000..cfc1f356e3a --- /dev/null +++ b/changelogs/unreleased/frozen-string-enable-app-services.yml @@ -0,0 +1,5 @@ +--- +title: Enable frozen string in apps/uploaders/*.rb +merge_request: 20401 +author: gfyoung +type: other diff --git a/changelogs/unreleased/gitaly-serverservice-info-timeout.yml b/changelogs/unreleased/gitaly-serverservice-info-timeout.yml new file mode 100644 index 00000000000..7f2fe8b9c93 --- /dev/null +++ b/changelogs/unreleased/gitaly-serverservice-info-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Use appropriate timeout on Gitaly server info checks, avoid error on timeout +merge_request: 20552 +author: +type: fixed diff --git a/changelogs/unreleased/ide-pipeline-icon-open.yml b/changelogs/unreleased/ide-pipeline-icon-open.yml new file mode 100644 index 00000000000..3a73ff2170f --- /dev/null +++ b/changelogs/unreleased/ide-pipeline-icon-open.yml @@ -0,0 +1,5 @@ +--- +title: Clicking CI icon in Web IDE now opens up pipelines panel +merge_request: +author: +type: added diff --git a/changelogs/unreleased/ide-row-dropdown-design-update.yml b/changelogs/unreleased/ide-row-dropdown-design-update.yml new file mode 100644 index 00000000000..e0fe64c944e --- /dev/null +++ b/changelogs/unreleased/ide-row-dropdown-design-update.yml @@ -0,0 +1,5 @@ +--- +title: Updated design of new entry dropdown in Web IDE +merge_request: 20526 +author: +type: changed diff --git a/changelogs/unreleased/issue_47709.yml b/changelogs/unreleased/issue_47709.yml new file mode 100644 index 00000000000..c3ef55fd692 --- /dev/null +++ b/changelogs/unreleased/issue_47709.yml @@ -0,0 +1,5 @@ +--- +title: 'Allow to toggle notifications for issues due soon' +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-stderr-pipe-consumption.yml b/changelogs/unreleased/sh-fix-stderr-pipe-consumption.yml new file mode 100644 index 00000000000..b7366cf2569 --- /dev/null +++ b/changelogs/unreleased/sh-fix-stderr-pipe-consumption.yml @@ -0,0 +1,5 @@ +--- +title: Avoid process deadlock in popen by consuming input pipes +merge_request: 20600 +author: +type: fixed diff --git a/changelogs/unreleased/sh-optimize-wiki-empty-check.yml b/changelogs/unreleased/sh-optimize-wiki-empty-check.yml new file mode 100644 index 00000000000..31ca7497b5a --- /dev/null +++ b/changelogs/unreleased/sh-optimize-wiki-empty-check.yml @@ -0,0 +1,5 @@ +--- +title: Optimize ProjectWiki#empty? check +merge_request: 20573 +author: +type: performance diff --git a/changelogs/unreleased/tweak-sql-buckets.yml b/changelogs/unreleased/tweak-sql-buckets.yml new file mode 100644 index 00000000000..00a0f733ee1 --- /dev/null +++ b/changelogs/unreleased/tweak-sql-buckets.yml @@ -0,0 +1,5 @@ +--- +title: Add a 10 ms bucket for SQL timings +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/update-issue-closing-pattern.yml b/changelogs/unreleased/update-issue-closing-pattern.yml new file mode 100644 index 00000000000..95488adf449 --- /dev/null +++ b/changelogs/unreleased/update-issue-closing-pattern.yml @@ -0,0 +1,5 @@ +--- +title: Update issue closing pattern +merge_request: 20554 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/winh-upgrade-grape-path-helpers.yml b/changelogs/unreleased/winh-upgrade-grape-path-helpers.yml new file mode 100644 index 00000000000..62addff1d0f --- /dev/null +++ b/changelogs/unreleased/winh-upgrade-grape-path-helpers.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade grape-path-helpers to 1.0.6 +merge_request: 20601 +author: +type: other diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 693a2934a1b..4b9cc59ec45 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -135,7 +135,7 @@ Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled']. Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?: *,? +and +| *, *)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)|[Ii]mplement(?:s|ed|ing)?)(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?: *,? +and +| *,? *)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10 diff --git a/config/routes/import.rb b/config/routes/import.rb index c378253bf15..efd0260ff60 100644 --- a/config/routes/import.rb +++ b/config/routes/import.rb @@ -45,4 +45,10 @@ namespace :import do resource :gitlab_project, only: [:create, :new] do post :create end + + resource :manifest, only: [:create, :new], controller: :manifest do + get :status + get :jobs + post :upload + end end diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile new file mode 100644 index 00000000000..0374de24520 --- /dev/null +++ b/danger/changelog/Dangerfile @@ -0,0 +1,68 @@ +# rubocop:disable Style/SignalException + +require 'yaml' + +NO_CHANGELOG_LABELS = %w[backstage QA test].freeze +SEE_DOC = "See [the documentation](https://docs.gitlab.com/ce/development/changelog.html).".freeze +MISSING_CHANGELOG_MESSAGE = <<~MSG.freeze +**[CHANGELOG missing](https://docs.gitlab.com/ce/development/changelog.html).** + +You can create one with: + +``` +bin/changelog -m %<mr_iid>s +``` + +If your merge request doesn't warrant a CHANGELOG entry, +consider adding any of the %<labels>s labels. +#{SEE_DOC} +MSG + +def ee? + ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md') +end + +def ee_changelog?(changelog_path) + changelog_path =~ /unreleased-ee/ +end + +def ce_port_changelog?(changelog_path) + ee? && !ee_changelog?(changelog_path) +end + +def check_changelog(path) + yaml = YAML.safe_load(File.read(path)) + + fail "`title` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["title"].nil? + fail "`type` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["type"].nil? + + if yaml["merge_request"].nil? + message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}" + elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !ce_port_changelog?(path) + fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}" + end +rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias + # YAML could not be parsed, fail the build. + fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}" +rescue StandardError => e + warn "There was a problem trying to check the Changelog. Exception: #{e.name} - #{e.message}" +end + +def presented_no_changelog_labels + NO_CHANGELOG_LABELS.map { |label| "~#{label}" }.join(', ') +end + +changelog_needed = (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty? +changelog_found = git.added_files.find { |path| path =~ %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} } + +if git.modified_files.include?("CHANGELOG.md") + fail "CHANGELOG.md was edited. Please remove the additions and create an entry with `bin/changelog -m #{gitlab.mr_json["iid"]}` instead." +end + +if changelog_needed + if changelog_found + check_changelog(changelog_found) + else + warn format(MISSING_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], labels: presented_no_changelog_labels) + end +end diff --git a/danger/changes_size/Dangerfile b/danger/changes_size/Dangerfile new file mode 100644 index 00000000000..8251d0d5cbf --- /dev/null +++ b/danger/changes_size/Dangerfile @@ -0,0 +1,17 @@ +# FIXME: git.info_for_file raises the following error +# /usr/local/bundle/gems/git-1.4.0/lib/git/lib.rb:956:in `command': (Danger::DSLError) +# [!] Invalid `Dangerfile` file: +# [!] Invalid `Dangerfile` file: git '--git-dir=/builds/gitlab-org/gitlab-ce/.git' '--work-tree=/builds/gitlab-org/gitlab-ce' cat-file '-t' '' 2>&1:fatal: Not a valid object name +# This seems to be the same as https://github.com/danger/danger/issues/535. + +# locale_files_updated = git.modified_files.select { |path| path.start_with?('locale') } +# locale_files_updated.each do |locale_file_updated| +# git_stats = git.info_for_file(locale_file_updated) +# message "Git stats for #{locale_file_updated}: #{git_stats[:insertions]} insertions, #{git_stats[:deletions]} insertions" +# end + +if git.lines_of_code > 2_000 + warn "This merge request is definitely too big (more than #{git.lines_of_code} lines changed), please split it into multiple merge requests." +elsif git.lines_of_code > 500 + warn "This merge request is quite big (more than #{git.lines_of_code} lines changed), please consider splitting it into multiple merge requests." +end diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile new file mode 100644 index 00000000000..6f48994945a --- /dev/null +++ b/danger/database/Dangerfile @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# All the files/directories that should be reviewed by the DB team. +DB_FILES = [ + 'db/', + 'app/models/project_authorization.rb', + 'app/services/users/refresh_authorized_projects_service.rb', + 'lib/gitlab/background_migration.rb', + 'lib/gitlab/background_migration/', + 'lib/gitlab/database.rb', + 'lib/gitlab/database/', + 'lib/gitlab/github_import.rb', + 'lib/gitlab/github_import/', + 'lib/gitlab/sql/', + 'rubocop/cop/migration', + 'ee/db/', + 'ee/lib/gitlab/database/' +].freeze + +SCHEMA_NOT_UPDATED_MESSAGE = <<~MSG +**New %<migrations>s added but %<schema>s wasn't updated.** + +Usually, when adding new %<migrations>s, %<schema>s should be +updated too (unless the migration isn't changing the DB schema +and isn't the most recent one). +MSG + +def database_paths_requiring_review(files) + to_review = [] + + files.each do |file| + review = DB_FILES.any? do |pattern| + file.start_with?(pattern) + end + + to_review << file if review + end + + to_review +end + +all_files = git.added_files + git.modified_files + +non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/schema\.rb/}).empty? +geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb/}).empty? + +non_geo_migration_created = !git.added_files.grep(%r{\A(db/(post_)?migrate)/}).empty? +geo_migration_created = !git.added_files.grep(%r{\Aee/db/geo/(post_)?migrate/}).empty? + +if non_geo_migration_created && !non_geo_db_schema_updated + warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'migrations', schema: gitlab.html_link("db/schema.rb")) +end + +if geo_migration_created && !geo_db_schema_updated + warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'Geo migrations', schema: gitlab.html_link("ee/db/geo/schema.rb")) +end + +db_paths_to_review = database_paths_requiring_review(all_files) + +unless db_paths_to_review.empty? + message 'This merge request adds or changes files that require a ' \ + 'review from the Database team.' + + markdown(<<~MARKDOWN.strip) +## Database Review + +The following files require a review from the Database team: + +* #{db_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")} + +To make sure these changes are reviewed, take the following steps: + +1. Edit your merge request, and add `gl-database` to the list of Group + approvers. +1. Mention `@gl-database` in a separate comment, and explain what needs to be + reviewed by the team. Please don't mention the team until your changes are + ready for review. + MARKDOWN + + unless gitlab.mr_labels.include?('database') + warn 'This merge request is missing the ~database label.' + end +end diff --git a/danger/gemfile/Dangerfile b/danger/gemfile/Dangerfile new file mode 100644 index 00000000000..8ef4a464fe4 --- /dev/null +++ b/danger/gemfile/Dangerfile @@ -0,0 +1,24 @@ +GEMFILE_LOCK_NOT_UPDATED_MESSAGE = <<~MSG.freeze +**%<gemfile>s was updated but %<gemfile_lock>s wasn't updated.** + +Usually, when %<gemfile>s is updated, you should run +``` +bundle install && \ + BUNDLE_GEMFILE=Gemfile.rails5 bundle install +``` + +or + +``` +bundle update <the-added-or-updated-gem> +``` + +and commit the %<gemfile_lock>s changes. +MSG + +gemfile_modified = git.modified_files.include?("Gemfile") +gemfile_lock_modified = git.modified_files.include?("Gemfile.lock") + +if gemfile_modified && !gemfile_lock_modified + warn format(GEMFILE_LOCK_NOT_UPDATED_MESSAGE, gemfile: gitlab.html_link("Gemfile"), gemfile_lock: gitlab.html_link("Gemfile.lock")) +end diff --git a/danger/metadata/Dangerfile b/danger/metadata/Dangerfile new file mode 100644 index 00000000000..3cfaa04e01b --- /dev/null +++ b/danger/metadata/Dangerfile @@ -0,0 +1,25 @@ +# rubocop:disable Style/SignalException + +if gitlab.mr_body.size < 5 + fail "Please provide a proper merge request description." +end + +if gitlab.mr_labels.empty? + fail "Please add labels to this merge request." +end + +unless gitlab.mr_json["assignee"] + warn "This merge request does not have any assignee yet. Setting an assignee clarifies who needs to take action on the merge request at any given time." +end + +has_milestone = !gitlab.mr_json["milestone"].nil? + +unless has_milestone + warn "This merge request does not refer to an existing milestone.", sticky: false +end + +has_pick_into_stable_label = gitlab.mr_labels.find { |label| label.start_with?('Pick into') } + +if gitlab.branch_for_base != "master" && !has_pick_into_stable_label + warn "Most of the time, all merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label." +end diff --git a/danger/specs/Dangerfile b/danger/specs/Dangerfile new file mode 100644 index 00000000000..934ea0beadb --- /dev/null +++ b/danger/specs/Dangerfile @@ -0,0 +1,12 @@ +NO_NEW_SPEC_MESSAGE = <<~MSG.freeze +You've made some app changes, but didn't add any tests. +That's OK as long as you're refactoring existing code, +but please consider adding the ~backstage label in that case. +MSG + +has_app_changes = !git.modified_files.grep(%r{\A(ee/)?(app|lib|db/(geo/)?(post_)?migrate)/}).empty? +has_spec_changes = !git.modified_files.grep(/spec/).empty? + +if has_app_changes && !has_spec_changes + warn NO_NEW_SPEC_MESSAGE, sticky: false +end diff --git a/db/fixtures/development/19_environments.rb b/db/fixtures/development/19_environments.rb index 65089f6ba4e..3e227928a29 100644 --- a/db/fixtures/development/19_environments.rb +++ b/db/fixtures/development/19_environments.rb @@ -30,7 +30,7 @@ class Gitlab::Seeder::Environments def create_merge_request_review_deployments! @project .merge_requests - .select { |mr| mr.source_branch.match(/[^a-zA-Z0-9]+/) } + .select { |mr| mr.source_branch.match?(/[a-zA-Z0-9]+/) } .sample(4) .each do |merge_request| next unless merge_request.diff_head_sha diff --git a/db/migrate/20180608091413_add_group_to_todos.rb b/db/migrate/20180608091413_add_group_to_todos.rb deleted file mode 100644 index af3ee48b29d..00000000000 --- a/db/migrate/20180608091413_add_group_to_todos.rb +++ /dev/null @@ -1,32 +0,0 @@ -class AddGroupToTodos < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - def up - add_column :todos, :group_id, :integer - add_concurrent_foreign_key :todos, :namespaces, column: :group_id, on_delete: :cascade - add_concurrent_index :todos, :group_id - - change_column_null :todos, :project_id, true - end - - def down - return unless group_id_exists? - - remove_foreign_key :todos, column: :group_id - remove_index :todos, :group_id if index_exists?(:todos, :group_id) - remove_column :todos, :group_id - - execute "DELETE FROM todos WHERE project_id IS NULL" - change_column_null :todos, :project_id, false - end - - private - - def group_id_exists? - column_exists?(:todos, :group_id) - end -end diff --git a/db/schema.rb b/db/schema.rb index 75a1960e2f8..d2aa31fae30 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1950,7 +1950,7 @@ ActiveRecord::Schema.define(version: 20180704204006) do create_table "todos", force: :cascade do |t| t.integer "user_id", null: false - t.integer "project_id" + t.integer "project_id", null: false t.integer "target_id" t.string "target_type", null: false t.integer "author_id", null: false @@ -1960,12 +1960,10 @@ ActiveRecord::Schema.define(version: 20180704204006) do t.datetime "updated_at" t.integer "note_id" t.string "commit_id" - t.integer "group_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree - add_index "todos", ["group_id"], name: "index_todos_on_group_id", using: :btree add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree @@ -2339,7 +2337,6 @@ ActiveRecord::Schema.define(version: 20180704204006) do add_foreign_key "term_agreements", "users", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade - add_foreign_key "todos", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "todos", "notes", name: "fk_91d1f47b13", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "users", column: "author_id", name: "fk_ccf0373936", on_delete: :cascade diff --git a/doc/api/README.md b/doc/api/README.md index 6267618d3bc..4566319ad45 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -388,7 +388,7 @@ For example, `/` is represented by `%2F`: GET /api/v4/projects/diaspora%2Fdiaspora ``` -## Branches & tags name encoding +## Branches and tags name encoding If your branch or tag contains a `/`, make sure the branch/tag name is URL-encoded. @@ -399,6 +399,36 @@ For example, `/` is represented by `%2F`: GET /api/v4/projects/1/branches/my%2Fbranch/commits ``` +## Encoding API parameters of `array` and `hash` types + +When making an API call with parameters of type `array` and/or `hash`, the parameters may be +specified as shown below. + +### `array` + +`import_sources` is a parameter of type `array`: + +``` +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \ +-d "import_sources[]=github" \ +-d "import_sources[]=bitbucket" \ +"https://gitlab.example.com/api/v4/some_endpoint +``` + +### `hash` + +`override_params` is a parameter of type `hash`: + +``` +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" \ +--form "namespace=email" \ +--form "path=impapi" \ +--form "file=@/path/to/somefile.txt" +--form "override_params[visibility]=private" \ +--form "override_params[some_other_param]=some_value" \ +https://gitlab.example.com/api/v4/projects/import +``` + ## `id` vs `iid` When you work with the API, you may notice two similar fields in API entities: diff --git a/doc/api/notes.md b/doc/api/notes.md index d29c5b94915..c271d46688f 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -218,6 +218,7 @@ Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) - `snippet_id` (required) - The ID of a snippet - `body` (required) - The content of a note +- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note @@ -340,6 +341,7 @@ Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) - `merge_request_iid` (required) - The IID of a merge request - `body` (required) - The content of a note +- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z ### Modify existing merge request note diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index 085437c801a..83e405141f1 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -28,8 +28,11 @@ POST /projects/:id/export | `upload[url]` | string | yes | The URL to upload the project | | `upload[http_method]` | string | no | The HTTP method to upload the exported project. Only `PUT` and `POST` methods allowed. Default is `PUT` | + ```console -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export --data "description=FooBar&upload[http_method]=PUT&upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export \ + --data "upload[http_method]=PUT" \ + --data-urlencode "upload[url]=https://example-bucket.s3.eu-west-3.amazonaws.com/backup?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMBJHN2O62W8IELQ%2F20180312%2Feu-west-3%2Fs3%2Faws4_request&X-Amz-Date=20180312T110328Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=8413facb20ff33a49a147a0b4abcff4c8487cc33ee1f7e450c46e8f695569dbd" ``` ```json @@ -125,6 +128,29 @@ by `@`. For example: curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "path=api-project" --form "file=@/path/to/file" https://gitlab.example.com/api/v4/projects/import ``` +cURL doesn't support posting a file from a remote server. Importing a project from a remote server can be accomplished through something like the following: + +```python +import requests +import urllib +import json +import sys + +s3_file = urllib.urlopen(presigned_url) + +url = 'https://gitlab.example.com/api/v4/projects/import' +files = {'file': s3_file} +data = { + "path": "example-project", + "namespace": "example-group" +} +headers = { + 'Private-Token': "9koXpg98eAheJpvBs5tK" +} + +requests.post(url, headers=headers, data=data, files=files) +``` + ```json { "id": 1, diff --git a/doc/api/settings.md b/doc/api/settings.md index e6b207d8746..b6f2101fc7b 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -105,7 +105,7 @@ PUT /application/settings | `housekeeping_gc_period` | integer | no | Number of Git pushes after which 'git gc' is run. | | `housekeeping_incremental_repack_period` | integer | no | Number of Git pushes after which an incremental 'git repack' is run. | | `html_emails_enabled` | boolean | no | Enable HTML emails | -| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project | +| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project manifest | | `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. | | `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. | | `max_artifacts_size` | integer | no | Maximum artifacts size in MB | diff --git a/doc/api/todos.md b/doc/api/todos.md index 0843e4eedc6..27e623007cc 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -18,7 +18,6 @@ Parameters: | `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, `approval_required`, `unmergeable` or `directly_addressed`. | | `author_id` | integer | no | The ID of an author | | `project_id` | integer | no | The ID of a project | -| `group_id` | integer | no | The ID of a group | | `state` | string | no | The state of the todo. Can be either `pending` or `done` | | `type` | string | no | The type of a todo. Can be either `Issue` or `MergeRequest` | diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 9f6476edc34..84bd64d50cd 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -47,6 +47,7 @@ future GitLab releases.** | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | | **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. | | **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built | +| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. | | **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. | | **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. | | **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message | @@ -118,6 +119,7 @@ future GitLab releases.** | `CI_BUILD_ID` | `CI_JOB_ID` | | `CI_BUILD_REF` | `CI_COMMIT_SHA` | | `CI_BUILD_TAG` | `CI_COMMIT_TAG` | +| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` | | `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` | | `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` | | `CI_BUILD_NAME` | `CI_JOB_NAME` | diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 6ca3e9e5a0a..3e417a44ec1 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -197,4 +197,4 @@ Note: It is recommended to log into the `git` user using `sudo -i -u git` or `su ## GitLab.com -We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users. +We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/) but this is probably over the top unless you have millions of users. diff --git a/doc/development/fe_guide/img/vue_arch.png b/doc/development/fe_guide/img/vue_arch.png Binary files differdeleted file mode 100644 index a67706c7c1e..00000000000 --- a/doc/development/fe_guide/img/vue_arch.png +++ /dev/null diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index 219b98ac696..f6cbd11042c 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -2,27 +2,24 @@ To get started with Vue, read through [their documentation][vue-docs]. -## Vue architecture +## Examples -All new features built with Vue.js must follow a [Flux architecture][flux]. -The main goal we are trying to achieve is to have only one data flow and only one data entry. -In order to achieve this goal, you can either use [vuex](#vuex) or use the [store pattern][state-management], explained below: +What is described in the following sections can be found in these examples: -Each Vue bundle needs a Store - where we keep all the data -, a Service - that we use to communicate with the server - and a main Vue component. +- web ide: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/ide/stores +- security products: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports +- registry: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/registry/stores -Think of the Main Vue Component as the entry point of your application. This is the only smart -component that should exist in each Vue feature. -This component is responsible for: -1. Calling the Service to get data from the server -1. Calling the Store to store the data received -1. Mounting all the other components +## Vue architecture - +All new features built with Vue.js must follow a [Flux architecture][flux]. +The main goal we are trying to achieve is to have only one data flow and only one data entry. +In order to achieve this goal we use [vuex](#vuex). You can also read about this architecture in vue docs about [state management][state-management] and about [one way data flow][one-way-data-flow]. -### Components, Stores and Services +### Components and Store In some features implemented with Vue.js, like the [issue board][issue-boards] or [environments table][environments-table] @@ -33,10 +30,8 @@ new_feature ├── components │ └── component.vue │ └── ... -├── stores +├── store │ └── new_feature_store.js -├── services # only when not using vuex -│ └── new_feature_service.js ├── index.js ``` _For consistency purposes, we recommend you to follow the same structure._ @@ -125,217 +120,6 @@ You can read more about components in Vue.js site, [Component System][component- #### Vuex Check this [page](vuex.md) for more details. -#### Flux like state management -The Store is a class that allows us to manage the state in a single -source of truth. It is not aware of the service or the components. - -The concept we are trying to follow is better explained by Vue documentation -itself, please read this guide: [State Management][state-management] - -### A folder for the Service - -**If you are using Vuex you won't need this step** - -The Service is a class used only to communicate with the server. -It does not store or manipulate any data. It is not aware of the store or the components. -We use [axios][axios] to communicate with the server. -Refer to [axios](axios.md) for more details. - -Axios instance should only be imported in the service file. - -```javascript -import axios from '~/lib/utils/axios_utils'; -``` - -### End Result - -The following example shows an application: - -```javascript -// store.js -export default class Store { - - /** - * This is where we will iniatialize the state of our data. - * Usually in a small SPA you don't need any options when starting the store. - * In that case you do need guarantee it's an Object and it's documented. - * - * @param {Object} options - */ - constructor(options) { - this.options = options; - - // Create a state object to handle all our data in the same place - this.todos = []; - } - - setTodos(todos = []) { - this.todos = todos; - } - - addTodo(todo) { - this.todos.push(todo); - } - - removeTodo(todoID) { - const state = this.todos; - - const newState = state.filter((element) => {element.id !== todoID}); - - this.todos = newState; - } -} - -// service.js -import axios from '~/lib/utils/axios_utils' - -export default class Service { - constructor(options) { - this.todos = axios.create({ - baseURL: endpoint.todosEndpoint - }); - - } - - getTodos() { - return this.todos.get(); - } - - addTodo(todo) { - return this.todos.put(todo); - } -} -// todo_component.vue -<script> -export default { - props: { - data: { - type: Object, - required: true, - }, - }, -}; -</script> -<template> - <div> - <h1> - Title: {{data.title}} - </h1> - <p> - {{data.text}} - </p> - </div> -</template> - -// todos_main_component.vue -<script> -import Store from 'store'; -import Service from 'service'; -import TodoComponent from 'todoComponent'; -export default { - components: { - todo: TodoComponent, - }, - /** - * Although most data belongs in the store, each component it's own state. - * We want to show a loading spinner while we are fetching the todos, this state belong - * in the component. - * - * We need to access the store methods through all methods of our component. - * We need to access the state of our store. - */ - data() { - const store = new Store(); - - return { - isLoading: false, - store: store, - todos: store.todos, - }; - }, - - created() { - this.service = new Service('/todos'); - - this.getTodos(); - }, - - methods: { - getTodos() { - this.isLoading = true; - - this.service - .getTodos() - .then(response => { - this.store.setTodos(response); - this.isLoading = false; - }) - .catch(() => { - this.isLoading = false; - // Show an error - }); - }, - - addTodo(event) { - this.service - .addTodo({ - title: 'New entry', - text: `You clicked on ${event.target.tagName}`, - }) - .then(response => { - this.store.addTodo(response); - }) - .catch(() => { - // Show an error - }); - }, - }, -}; -</script> -<template> - <div class="container"> - <div v-if="isLoading"> - <i - class="fa fa-spin fa-spinner" - aria-hidden="true" /> - </div> - - <div - v-if="!isLoading" - class="js-todo-list"> - <template v-for='todo in todos'> - <todo :data="todo" /> - </template> - - <button - @click="addTodo" - class="js-add-todo"> - Add Todo - </button> - </div> - <div> -</template> - -// index.js -import todoComponent from 'todos_main_component.vue'; - -new Vue({ - el: '.js-todo-app', - components: { - todoComponent, - }, - render: createElement => createElement('todo-component' { - props: { - someProp: [], - } - }), -}); - -``` - -The [issue boards service][issue-boards-service] -is a good example of this pattern. - ## Style guide Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js) @@ -446,6 +230,5 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e [state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch [one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow [vue-test]: https://vuejs.org/v2/guide/unit-testing.html -[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6 [flux]: https://facebook.github.io/flux [axios]: https://github.com/axios/axios diff --git a/doc/install/installation.md b/doc/install/installation.md index 4b68090f8d3..8c7f80fd8e8 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -92,9 +92,9 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.16.3.tar.gz - echo 'dda229e9c73f4fbb7d4324e0d993e11311673df03f73b194c554c2e9451e17cd git-2.16.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.16.3.tar.gz - cd git-2.16.3/ + curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz + echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz + cd git-2.18.0/ ./configure make prefix=/usr/local all diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index d04829eaeb8..de1d366adc3 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -527,7 +527,7 @@ repo or by specifying a project variable: - **Bundled chart** - If your project has a `./chart` directory with a `Chart.yaml` file in it, Auto DevOps will detect the chart and use it instead of the [default - one](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). + one](https://gitlab.com/charts/auto-deploy-app). This can be a great way to control exactly how your application is deployed. - **Project variable** - Create a [project variable](../../ci/variables/README.md#secret-variables) `AUTO_DEVOPS_CHART` with the URL of a custom chart to use. diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md index 4efbb8c65cf..b9c14395a3a 100644 --- a/doc/update/10.6-to-10.7.md +++ b/doc/update/10.6-to-10.7.md @@ -340,7 +340,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production If all items are green, then congratulations, the upgrade is complete! -## Things went south? Revert to previous version (10.5) +## Things went south? Revert to previous version (10.6) ### 1. Revert the code to the previous version diff --git a/doc/user/project/clusters/eks_and_gitlab/index.md b/doc/user/project/clusters/eks_and_gitlab/index.md index 2d8fdf0d1da..ec8467da14f 100644 --- a/doc/user/project/clusters/eks_and_gitlab/index.md +++ b/doc/user/project/clusters/eks_and_gitlab/index.md @@ -62,7 +62,7 @@ Click on `Add Kubernetes cluster`, the cluster is now connected to GitLab. At th If you would like to utilize your own CI/CD scripts to deploy to the cluster, you can stop here. -## Disable Role Based-Access Control (RBAC) +## Disable Role-Based Access Control (RBAC) Presently, Auto DevOps and one-click app installs do not support [Kubernetes role-based access control](https://kubernetes.io/docs/reference/access-authn-authz/rbac/). Support is [being worked on](https://gitlab.com/groups/gitlab-org/-/epics/136), but in the interim RBAC must be disabled to utilize for these features. diff --git a/doc/user/project/import/img/manifest_status.png b/doc/user/project/import/img/manifest_status.png Binary files differnew file mode 100644 index 00000000000..b706116a2ac --- /dev/null +++ b/doc/user/project/import/img/manifest_status.png diff --git a/doc/user/project/import/img/manifest_upload.png b/doc/user/project/import/img/manifest_upload.png Binary files differnew file mode 100644 index 00000000000..d6bf4b157dd --- /dev/null +++ b/doc/user/project/import/img/manifest_upload.png diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index 72cc58546b7..b55435e5b4f 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -11,6 +11,7 @@ 1. [From SVN](svn.md) 1. [From TFS](tfs.md) 1. [From repo by URL](repo_by_url.md) +1. [By uploading a manifest file](manifest.md) In addition to the specific migration documentation above, you can import any Git repository via HTTP from the New Project page. Be aware that if the diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md new file mode 100644 index 00000000000..812ecf05faf --- /dev/null +++ b/doc/user/project/import/manifest.md @@ -0,0 +1,48 @@ +# Import multiple repositories by uploading a manifest file + +GitLab allows you to import all the required git repositories +based a manifest file like the one used by the Android repository. + + +>**Note:** +This feature requires [subgroups](../../group/subgroups/index.md) to be supported by your database. + +You can do it by following next steps: + +1. From your GitLab dashboard click **New project** +1. Switch to the **Import project** tab +1. Click on the **Manifest file** button +1. Provide GitLab with a manifest xml file +1. Select a group you want to import to (you need to create a group first if you don't have one) +1. Click **List available repositories** +1. You will be redirected to the import status page with projects list based on manifest file +1. Check the list and click 'Import all repositories' to start import. + + + + + +### Manifest format + +A manifest must be an XML file. There must be one `remote` tag with `review` attribute +that contains a URL to a git server. Each `project` tag must have `name` and `path` attribute. +GitLab will build URL to the repository by combining URL from `remote` tag with a project name. +A path attribute will be used to represent project path in GitLab system. + +Below is a valid example of manifest file. + +```xml +<manifest> + <remote review="https://android-review.googlesource.com/" /> + + <project path="build/make" name="platform/build" /> + <project path="build/blueprint" name="platform/build/blueprint" /> +</manifest> +``` + +As result next projects will be created: + +| GitLab | Import URL | +|---|---| +| https://gitlab/YOUR_GROUP/build/make | https://android-review.googlesource.com/platform/build | +| https://gitlab/YOUR_GROUP/build/blueprint | https://android-review.googlesource.com/platform/build/blueprint | diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index e97b5d05529..860edb8e6f7 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -364,12 +364,12 @@ When dragging issues between lists, different behavior occurs depending on the s 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 | Number of Group Issue Boards | Configurable Project Issue Boards | Configurable Group Issue Boards | Assignee Lists +| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable 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 | +| Core | 1 | 1 | No | No | +| Starter | Multiple | 1 | Yes | No | +| Premium | Multiple | Multiple | Yes | Yes | +| Ultimate | Multiple | Multiple | Yes | Yes | ## Tips diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index dda82352c67..760cd87d4cc 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -109,7 +109,6 @@ There are four kinds of filters you can use on your Todos dashboard. | Filter | Description | | ------- | ----------- | | Project | Filter by project | -| Group | Filter by group | | Author | Filter by the author that triggered the Todo | | Type | Filter by issue or merge request | | Action | Filter by the action that triggered the Todo | diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 964780cba6a..92329465b2c 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -6,6 +6,18 @@ module API before { authorize! :download_code, user_project } + helpers do + def user_access + @user_access ||= Gitlab::UserAccess.new(current_user, project: user_project) + end + + def authorize_push_to_branch!(branch) + unless user_access.can_push_to_branch?(branch) + forbidden!("You are not allowed to push into this branch") + end + end + end + params do requires :id, type: String, desc: 'The ID of a project' end @@ -67,7 +79,7 @@ module API optional :author_name, type: String, desc: 'Author name for commit' end post ':id/repository/commits' do - authorize! :push_code, user_project + authorize_push_to_branch!(params[:branch]) attrs = declared_params attrs[:branch_name] = attrs.delete(:branch) @@ -142,7 +154,7 @@ module API requires :branch, type: String, desc: 'The name of the branch' end post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do - authorize! :push_code, user_project + authorize_push_to_branch!(params[:branch]) commit = user_project.commit(params[:sha]) not_found!('Commit') unless commit diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 40df1e79bc7..b256c33c631 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -135,10 +135,13 @@ module API expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes def self.preload_relation(projects_relation, options = {}) + # Preloading tags, should be done with using only `:tags`, + # as `:tags` are defined as: `has_many :tags, through: :taggings` + # N+1 is solved then by using `subject.tags.map(&:name)` + # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555 projects_relation.preload(:project_feature, :route) - .preload(:import_state) - .preload(namespace: [:route, :owner], - tags: :taggings) + .preload(:import_state, :tags) + .preload(namespace: [:route, :owner]) end end @@ -212,11 +215,15 @@ module API expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics def self.preload_relation(projects_relation, options = {}) + # Preloading tags, should be done with using only `:tags`, + # as `:tags` are defined as: `has_many :tags, through: :taggings` + # N+1 is solved then by using `subject.tags.map(&:name)` + # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555 super(projects_relation).preload(:group) .preload(project_group_links: :group, fork_network: :root_project, forked_project_link: :forked_from_project, - forked_from_project: [:route, :forks, namespace: :route, tags: :taggings]) + forked_from_project: [:route, :forks, :tags, namespace: :route]) end def self.forks_counting_projects(projects_relation) @@ -775,33 +782,28 @@ module API class Todo < Grape::Entity expose :id - expose :project, using: Entities::ProjectIdentity, if: -> (todo, _) { todo.project_id } - expose :group, using: 'API::Entities::NamespaceBasic', if: -> (todo, _) { todo.group_id } + expose :project, using: Entities::BasicProjectDetails expose :author, using: Entities::UserBasic expose :action_name expose :target_type expose :target do |todo, options| - todo_target_class(todo.target_type).represent(todo.target, options) + Entities.const_get(todo.target_type).represent(todo.target, options) end expose :target_url do |todo, options| target_type = todo.target_type.underscore - target_url = "#{todo.parent.class.to_s.underscore}_#{target_type}_url" + target_url = "namespace_project_#{target_type}_url" target_anchor = "note_#{todo.note_id}" if todo.note_id? Gitlab::Routing .url_helpers - .public_send(target_url, todo.parent, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend + .public_send(target_url, todo.project.namespace, todo.project, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend end expose :body expose :state expose :created_at - - def todo_target_class(target_type) - ::API::Entities.const_get(target_type) - end end class NamespaceBasic < Grape::Entity diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index b4bfb677d72..e2984b08eca 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -97,6 +97,8 @@ module API current_user.admin? || parent.owned_by?(current_user) end + opts[:updated_at] = opts[:created_at] if opts[:created_at] + project = parent if parent.is_a?(Project) ::Notes::CreateService.new(project, current_user, opts).execute end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 02ef89f997f..1ca7d23203b 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -25,7 +25,7 @@ module API optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility' optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' - optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], + optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources' optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.' diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb index 2760211fee8..f95e423ef22 100644 --- a/lib/gitaly/server.rb +++ b/lib/gitaly/server.rb @@ -50,7 +50,7 @@ module Gitaly @info ||= begin Gitlab::GitalyClient::ServerService.new(@storage).info - rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded + rescue GRPC::Unavailable, GRPC::DeadlineExceeded # This will show the server as being out of date Gitaly::ServerInfoResponse.new(git_version: '', server_version: '', storage_statuses: []) end diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb index f9f24ecc48d..7426688fc55 100644 --- a/lib/gitlab/git/popen.rb +++ b/lib/gitlab/git/popen.rb @@ -21,6 +21,10 @@ module Gitlab Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| stdout.set_encoding(Encoding::ASCII_8BIT) + # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082 + # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273 + err_reader = Thread.new { stderr.read } + yield(stdin) if block_given? stdin.close @@ -32,7 +36,7 @@ module Gitlab cmd_output << stdout.read end - cmd_output << stderr.read + cmd_output << err_reader.value cmd_status = wait_thr.value.exitstatus end @@ -55,16 +59,20 @@ module Gitlab rerr, werr = IO.pipe pid = Process.spawn(vars, *cmd, out: wout, err: werr, chdir: path, pgroup: true) + # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082 + # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273 + out_reader = Thread.new { rout.read } + err_reader = Thread.new { rerr.read } begin - status = process_wait_with_timeout(pid, timeout) - # close write ends so we could read them wout.close werr.close - cmd_output = rout.readlines.join - cmd_output << rerr.readlines.join # Copying the behaviour of `popen` which merges stderr into output + status = process_wait_with_timeout(pid, timeout) + + cmd_output = out_reader.value + cmd_output << err_reader.value # Copying the behaviour of `popen` which merges stderr into output [cmd_output, status.exitstatus] rescue Timeout::Error => e diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index a1a050647b9..3c23b588f77 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -86,9 +86,6 @@ module Gitlab # Relative path of repo attr_reader :relative_path - # Rugged repo object - attr_reader :rugged - attr_reader :gitlab_projects, :storage, :gl_repository, :relative_path # This initializer method is only used on the client side (gitlab-ce). @@ -112,8 +109,9 @@ module Gitlab [storage, relative_path] == [other.storage, other.relative_path] end + # This method will be removed when Gitaly reaches v1.1. def path - @path ||= File.join( + File.join( Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path ) end @@ -127,8 +125,9 @@ module Gitlab raise Gitlab::Git::CommandError.new(e.message) end + # This method will be removed when Gitaly reaches v1.1. def rugged - @rugged ||= circuit_breaker.perform do + circuit_breaker.perform do Rugged::Repository.new(path, alternates: alternate_object_directories) end rescue Rugged::RepositoryError, Rugged::OSError @@ -168,24 +167,9 @@ module Gitlab # Directly find a branch with a simple name (e.g. master) # - # force_reload causes a new Rugged repository to be instantiated - # - # This is to work around a bug in libgit2 that causes in-memory refs to - # be stale/invalid when packed-refs is changed. - # See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333 - def find_branch(name, force_reload = false) - gitaly_migrate(:find_branch) do |is_enabled| - if is_enabled - gitaly_ref_client.find_branch(name) - else - reload_rugged if force_reload - - rugged_ref = rugged.branches[name] - if rugged_ref - target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) - end - end + def find_branch(name) + wrapped_gitaly_errors do + gitaly_ref_client.find_branch(name) end end @@ -197,20 +181,8 @@ module Gitlab # Returns the number of valid branches def branch_count - gitaly_migrate(:branch_names) do |is_enabled| - if is_enabled - gitaly_ref_client.count_branch_names - else - rugged.branches.each(:local).count do |ref| - begin - ref.name && ref.target # ensures the branch is valid - - true - rescue Rugged::ReferenceError - false - end - end - end + wrapped_gitaly_errors do + gitaly_ref_client.count_branch_names end end @@ -233,12 +205,8 @@ module Gitlab # Returns the number of valid tags def tag_count - gitaly_migrate(:tag_names) do |is_enabled| - if is_enabled - gitaly_ref_client.count_tag_names - else - rugged.tags.count - end + wrapped_gitaly_errors do + gitaly_ref_client.count_tag_names end end @@ -261,13 +229,8 @@ module Gitlab # # Ref names must start with `refs/`. def ref_exists?(ref_name) - gitaly_migrate(:ref_exists, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_exists?(ref_name) - else - rugged_ref_exists?(ref_name) - end + wrapped_gitaly_errors do + gitaly_ref_exists?(ref_name) end end @@ -275,12 +238,8 @@ module Gitlab # # name - The name of the tag as a String. def tag_exists?(name) - gitaly_migrate(:ref_exists_tags, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_exists?("refs/tags/#{name}") - else - rugged_tag_exists?(name) - end + wrapped_gitaly_errors do + gitaly_ref_exists?("refs/tags/#{name}") end end @@ -288,12 +247,8 @@ module Gitlab # # name - The name of the branch as a String. def branch_exists?(name) - gitaly_migrate(:ref_exists_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_exists?("refs/heads/#{name}") - else - rugged_branch_exists?(name) - end + wrapped_gitaly_errors do + gitaly_ref_exists?("refs/heads/#{name}") end end @@ -311,12 +266,8 @@ module Gitlab end def delete_all_refs_except(prefixes) - gitaly_migrate(:ref_delete_refs) do |is_enabled| - if is_enabled - gitaly_ref_client.delete_refs(except_with_prefixes: prefixes) - else - delete_refs(*all_ref_names_except(prefixes)) - end + wrapped_gitaly_errors do + gitaly_ref_client.delete_refs(except_with_prefixes: prefixes) end end @@ -329,12 +280,6 @@ module Gitlab end.map(&:name) end - def rugged_head - rugged.head - rescue Rugged::ReferenceError - nil - end - def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:) ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) @@ -564,11 +509,6 @@ module Gitlab @refs_hash end - # Lookup for rugged object by oid or ref name - def lookup(oid_or_ref_name) - rugged.rev_parse(oid_or_ref_name) - end - # Returns url for submodule # # Ex. @@ -713,33 +653,18 @@ module Gitlab Gitlab::Git.committer_hash(email: user.email, name: user.name) end - def create_commit(params = {}) - params[:message].delete!("\r") - - Rugged::Commit.create(rugged, params) - end - # Delete the specified branch from the repository def delete_branch(branch_name) - gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.delete_branch(branch_name) - else - rugged.branches.delete(branch_name) - end + wrapped_gitaly_errors do + gitaly_ref_client.delete_branch(branch_name) end - rescue Rugged::ReferenceError, CommandError => e + rescue CommandError => e raise DeleteBranchError, e end def delete_refs(*ref_names) - gitaly_migrate(:delete_refs, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_delete_refs(*ref_names) - else - git_delete_refs(*ref_names) - end + wrapped_gitaly_errors do + gitaly_delete_refs(*ref_names) end end @@ -749,12 +674,8 @@ module Gitlab # create_branch("feature") # create_branch("other-feature", "master") def create_branch(ref, start_point = "HEAD") - gitaly_migrate(:create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| - if is_enabled - gitaly_ref_client.create_branch(ref, start_point) - else - rugged_create_branch(ref, start_point) - end + wrapped_gitaly_errors do + gitaly_ref_client.create_branch(ref, start_point) end end @@ -984,20 +905,6 @@ module Gitlab Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit) end - def commit_index(user, branch_name, index, options) - committer = user_to_committer(user) - - OperationService.new(user, self).with_branch(branch_name) do - commit_params = options.merge( - tree: index.write_tree(rugged), - author: committer, - committer: committer - ) - - create_commit(commit_params) - end - end - def fsck msg, status = gitaly_repository_client.fsck @@ -1182,7 +1089,7 @@ module Gitlab end def can_be_merged?(source_sha, target_branch) - if target_sha = find_branch(target_branch, true)&.target + if target_sha = find_branch(target_branch)&.target !gitaly_conflicts_client(source_sha, target_sha).conflicts? else false @@ -1424,23 +1331,6 @@ module Gitlab end end - # We are trying to deprecate this method because it does a lot of work - # but it seems to be used only to look up submodule URL's. - # https://gitlab.com/gitlab-org/gitaly/issues/329 - def submodules(ref) - commit = rev_parse_target(ref) - return {} unless commit - - begin - content = blob_content(commit, ".gitmodules") - rescue InvalidBlobName - return {} - end - - parser = GitmodulesParser.new(content) - fill_submodule_ids(commit, parser.parse) - end - def gitaly_submodule_url_for(ref, path) # We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited. commit_object = gitaly_commit_client.tree_entry(ref, path, 1) @@ -1463,68 +1353,6 @@ module Gitlab Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact end - # Get the content of a blob for a given commit. If the blob is a commit - # (for submodules) then return the blob's OID. - def blob_content(commit, blob_name) - blob_entry = tree_entry(commit, blob_name) - - unless blob_entry - raise InvalidBlobName.new("Invalid blob name: #{blob_name}") - end - - case blob_entry[:type] - when :commit - blob_entry[:oid] - when :tree - raise InvalidBlobName.new("#{blob_name} is a tree, not a blob") - when :blob - rugged.lookup(blob_entry[:oid]).content - end - end - - # Fill in the 'id' field of a submodule hash from its values - # as-of +commit+. Return a Hash consisting only of entries - # from the submodule hash for which the 'id' field is filled. - def fill_submodule_ids(commit, submodule_data) - submodule_data.each do |path, data| - id = begin - blob_content(commit, path) - rescue InvalidBlobName - nil - end - data['id'] = id - end - submodule_data.select { |path, data| data['id'] } - end - - # Find the entry for +path+ in the tree for +commit+ - def tree_entry(commit, path) - pathname = Pathname.new(path) - first = true - tmp_entry = nil - - pathname.each_filename do |dir| - if first - tmp_entry = commit.tree[dir] - first = false - elsif tmp_entry.nil? - return nil - else - begin - tmp_entry = rugged.lookup(tmp_entry[:oid]) - rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError - return nil - end - - return nil unless tmp_entry.type == :tree - - tmp_entry = tmp_entry[dir] - end - end - - tmp_entry - end - # Return the Rugged patches for the diff between +from+ and +to+. def diff_patches(from, to, options = {}, *paths) options ||= {} @@ -1556,125 +1384,14 @@ module Gitlab # Returns true if the given ref name exists # # Ref names must start with `refs/`. - def rugged_ref_exists?(ref_name) - raise ArgumentError, 'invalid refname' unless ref_name.start_with?('refs/') - - rugged.references.exist?(ref_name) - rescue Rugged::ReferenceError - false - end - - # Returns true if the given ref name exists - # - # Ref names must start with `refs/`. def gitaly_ref_exists?(ref_name) gitaly_ref_client.ref_exists?(ref_name) end - # Returns true if the given tag exists - # - # name - The name of the tag as a String. - def rugged_tag_exists?(name) - !!rugged.tags[name] - end - - # Returns true if the given branch exists - # - # name - The name of the branch as a String. - def rugged_branch_exists?(name) - rugged.branches.exists?(name) - - # If the branch name is invalid (e.g. ".foo") Rugged will raise an error. - # Whatever code calls this method shouldn't have to deal with that so - # instead we just return `false` (which is true since a branch doesn't - # exist when it has an invalid name). - rescue Rugged::ReferenceError - false - end - - def rugged_create_branch(ref, start_point) - rugged_ref = rugged.branches.create(ref, start_point) - 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 => e - raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'} - - raise InvalidRef.new("Invalid reference #{start_point}") - end - def gitaly_copy_gitattributes(revision) gitaly_repository_client.apply_gitattributes(revision) end - def rugged_copy_gitattributes(ref) - begin - commit = lookup(ref) - rescue Rugged::ReferenceError - raise InvalidRef.new("Ref #{ref} is invalid") - end - - # Create the paths - info_dir_path = File.join(path, 'info') - info_attributes_path = File.join(info_dir_path, 'attributes') - - begin - # Retrieve the contents of the blob - gitattributes_content = blob_content(commit, '.gitattributes') - rescue InvalidBlobName - # No .gitattributes found. Should now remove any info/attributes and return - File.delete(info_attributes_path) if File.exist?(info_attributes_path) - return - end - - # Create the info directory if needed - Dir.mkdir(info_dir_path) unless File.directory?(info_dir_path) - - # Write the contents of the .gitattributes file to info/attributes - # Use binary mode to prevent Rails from converting ASCII-8BIT to UTF-8 - File.open(info_attributes_path, "wb") do |file| - file.write(gitattributes_content) - end - end - - def rugged_cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - OperationService.new(user, self).with_branch( - branch_name, - start_branch_name: start_branch_name, - start_repository: start_repository - ) do |start_commit| - - Gitlab::Git.check_namespace!(commit, start_repository) - - cherry_pick_tree_id = check_cherry_pick_content(commit, start_commit.sha) - raise CreateTreeError unless cherry_pick_tree_id - - committer = user_to_committer(user) - - create_commit(message: message, - author: { - email: commit.author_email, - name: commit.author_name, - time: commit.authored_date - }, - committer: committer, - tree: cherry_pick_tree_id, - parents: [start_commit.sha]) - end - end - - def check_cherry_pick_content(target_commit, source_sha) - args = [target_commit.sha, source_sha] - args << 1 if target_commit.merge_commit? - - cherry_pick_index = rugged.cherrypick_commit(*args) - return false if cherry_pick_index.conflicts? - - tree_id = cherry_pick_index.write_tree(rugged) - return false unless diff_exists?(source_sha, tree_id) - - tree_id - end - def local_fetch_ref(source_path, source_ref:, target_ref:) args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) run_git(args) @@ -1694,20 +1411,6 @@ module Gitlab remote_update(remote_name, url: url) end - def git_delete_refs(*ref_names) - instructions = ref_names.map do |ref| - "delete #{ref}\x00\x00" - end - - message, status = run_git(%w[update-ref --stdin -z]) do |stdin| - stdin.write(instructions.join) - end - - unless status.zero? - raise GitError.new("Could not delete refs #{ref_names}: #{message}") - end - end - def gitaly_delete_refs(*ref_names) gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any? end diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index 5fdad077eea..2ba68343aa5 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -12,35 +12,12 @@ module Gitlab end # This method returns an array of new commit references - def new_refs - repository.rev_list(including: newrev, excluding: :all).split("\n") - end - - # Finds newly added objects - # Returns an array of shas + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/1233 # - # Can skip objects which do not have a path using required_path: true - # This skips commit objects and root trees, which might not be needed when - # looking for blobs - # - # When given a block it will yield objects as a lazy enumerator so - # the caller can limit work done instead of processing megabytes of data - def new_objects(options: [], require_path: nil, not_in: nil, &lazy_block) - opts = { - including: newrev, - options: options, - excluding: not_in.nil? ? :all : not_in, - require_path: require_path - } - - get_objects(opts, &lazy_block) - end - - def all_objects(options: [], require_path: nil, &lazy_block) - get_objects(including: :all, - options: options, - require_path: require_path, - &lazy_block) + def new_refs + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repository.rev_list(including: newrev, excluding: :all).split("\n") + end end private diff --git a/lib/gitlab/gitaly_client/server_service.rb b/lib/gitlab/gitaly_client/server_service.rb index 2e1076d1f66..ad898278353 100644 --- a/lib/gitlab/gitaly_client/server_service.rb +++ b/lib/gitlab/gitaly_client/server_service.rb @@ -9,7 +9,7 @@ module Gitlab end def info - GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new) + GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new, timeout: GitalyClient.fast_timeout) end end end diff --git a/lib/gitlab/import_export/avatar_saver.rb b/lib/gitlab/import_export/avatar_saver.rb index 998c21e2586..31ef0490cb3 100644 --- a/lib/gitlab/import_export/avatar_saver.rb +++ b/lib/gitlab/import_export/avatar_saver.rb @@ -11,7 +11,12 @@ module Gitlab def save return true unless @project.avatar.exists? - copy_files(avatar_path, avatar_export_path) + Gitlab::ImportExport::UploadsManager.new( + project: @project, + shared: @shared, + relative_export_path: 'avatar', + from: avatar_path + ).save rescue => e @shared.error(e) false @@ -19,10 +24,6 @@ module Gitlab private - def avatar_export_path - File.join(@shared.export_path, 'avatar', @project.avatar_identifier) - end - def avatar_path @project.avatar.path end diff --git a/lib/gitlab/import_export/uploads_manager.rb b/lib/gitlab/import_export/uploads_manager.rb new file mode 100644 index 00000000000..1110149712d --- /dev/null +++ b/lib/gitlab/import_export/uploads_manager.rb @@ -0,0 +1,101 @@ +module Gitlab + module ImportExport + class UploadsManager + include Gitlab::ImportExport::CommandLineUtil + + UPLOADS_BATCH_SIZE = 100 + + def initialize(project:, shared:, relative_export_path: 'uploads', from: nil) + @project = project + @shared = shared + @relative_export_path = relative_export_path + @from = from || default_uploads_path + end + + def save + copy_files(@from, uploads_export_path) if File.directory?(@from) + + if File.file?(@from) && @relative_export_path == 'avatar' + copy_files(@from, File.join(uploads_export_path, @project.avatar.filename)) + end + + copy_from_object_storage + + true + rescue => e + @shared.error(e) + false + end + + def restore + Dir["#{uploads_export_path}/**/*"].each do |upload| + next if File.directory?(upload) + + add_upload(upload) + end + + true + rescue => e + @shared.error(e) + false + end + + private + + def add_upload(upload) + uploader_context = FileUploader.extract_dynamic_path(upload).named_captures.symbolize_keys + + UploadService.new(@project, File.open(upload, 'r'), FileUploader, uploader_context).execute + end + + def copy_from_object_storage + return unless Gitlab::ImportExport.object_storage? + + each_uploader do |uploader| + next unless uploader.file + next if uploader.upload.local? # Already copied, using the old method + + download_and_copy(uploader) + end + end + + def default_uploads_path + FileUploader.absolute_base_dir(@project) + end + + def uploads_export_path + @uploads_export_path ||= File.join(@shared.export_path, @relative_export_path) + end + + def each_uploader + avatar_path = @project.avatar&.upload&.path + + if @relative_export_path == 'avatar' + yield(@project.avatar) + else + project_uploads_except_avatar(avatar_path).find_each(batch_size: UPLOADS_BATCH_SIZE) do |upload| + yield(upload.build_uploader) + end + end + end + + def project_uploads_except_avatar(avatar_path) + return @project.uploads unless avatar_path + + @project.uploads.where("path != ?", avatar_path) + end + + def download_and_copy(upload) + secret = upload.try(:secret) || '' + upload_path = File.join(uploads_export_path, secret, upload.filename) + + mkdir_p(File.join(uploads_export_path, secret)) + + File.open(upload_path, 'w') do |file| + # Download (stream) file from the uploader's location + IO.copy_stream(URI.parse(upload.file.url).open, file) + end + end + end + end +end diff --git a/lib/gitlab/import_export/uploads_restorer.rb b/lib/gitlab/import_export/uploads_restorer.rb index df19354b76e..25f85936227 100644 --- a/lib/gitlab/import_export/uploads_restorer.rb +++ b/lib/gitlab/import_export/uploads_restorer.rb @@ -2,13 +2,30 @@ module Gitlab module ImportExport class UploadsRestorer < UploadsSaver def restore - return true unless File.directory?(uploads_export_path) + if Gitlab::ImportExport.object_storage? + Gitlab::ImportExport::UploadsManager.new( + project: @project, + shared: @shared + ).restore + elsif File.directory?(uploads_export_path) + copy_files(uploads_export_path, uploads_path) - copy_files(uploads_export_path, uploads_path) + true + else + true # Proceed without uploads + end rescue => e @shared.error(e) false end + + def uploads_path + FileUploader.absolute_base_dir(@project) + end + + def uploads_export_path + @uploads_export_path ||= File.join(@shared.export_path, 'uploads') + end end end end diff --git a/lib/gitlab/import_export/uploads_saver.rb b/lib/gitlab/import_export/uploads_saver.rb index 2f08dda55fd..b3f17af5661 100644 --- a/lib/gitlab/import_export/uploads_saver.rb +++ b/lib/gitlab/import_export/uploads_saver.rb @@ -9,21 +9,14 @@ module Gitlab end def save - return true unless File.directory?(uploads_path) - - copy_files(uploads_path, uploads_export_path) + Gitlab::ImportExport::UploadsManager.new( + project: @project, + shared: @shared + ).save rescue => e @shared.error(e) false end - - def uploads_path - FileUploader.absolute_base_dir(@project) - end - - def uploads_export_path - File.join(@shared.export_path, 'uploads') - end end end end diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb index 60d5fa4d29a..af9b880ef9e 100644 --- a/lib/gitlab/import_sources.rb +++ b/lib/gitlab/import_sources.rb @@ -16,7 +16,8 @@ module Gitlab ImportSource.new('fogbugz', 'FogBugz', Gitlab::FogbugzImport::Importer), ImportSource.new('git', 'Repo by URL', nil), ImportSource.new('gitlab_project', 'GitLab export', Gitlab::ImportExport::Importer), - ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer) + ImportSource.new('gitea', 'Gitea', Gitlab::LegacyGithubImport::Importer), + ImportSource.new('manifest', 'Manifest file', nil) ].freeze class << self diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index a42e312b5d3..e58927a40b9 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -4,10 +4,18 @@ module Gitlab file_name_noext + '.log' end + def self.debug(message) + build.debug(message) + end + def self.error(message) build.error(message) end + def self.warn(message) + build.warn(message) + end + def self.info(message) build.info(message) end diff --git a/lib/gitlab/manifest_import/manifest.rb b/lib/gitlab/manifest_import/manifest.rb new file mode 100644 index 00000000000..4d6034fb956 --- /dev/null +++ b/lib/gitlab/manifest_import/manifest.rb @@ -0,0 +1,81 @@ +# Class to parse manifest file and build a list of repositories for import +# +# <manifest> +# <remote review="https://android-review.googlesource.com/" /> +# <project path="platform-common" name="platform" /> +# <project path="platform/art" name="platform/art" /> +# <project path="platform/device" name="platform/device" /> +# </manifest> +# +# 1. Project path must be uniq and can't be part of other project path. +# For example, you can't have projects with 'foo' and 'foo/bar' paths. +# 2. Remote must be present with review attribute so GitLab knows +# where to fetch source code +module Gitlab + module ManifestImport + class Manifest + attr_reader :parsed_xml, :errors + + def initialize(file) + @parsed_xml = Nokogiri::XML(file) { |config| config.strict } + @errors = [] + rescue Nokogiri::XML::SyntaxError + @errors = ['The uploaded file is not a valid XML file.'] + end + + def projects + raw_projects.each_with_index.map do |project, i| + { + id: i, + name: project['name'], + path: project['path'], + url: repository_url(project['name']) + } + end + end + + def valid? + return false if @errors.any? + + unless validate_remote + @errors << 'Make sure a <remote> tag is present and is valid.' + end + + unless validate_projects + @errors << 'Make sure every <project> tag has name and path attributes.' + end + + @errors.empty? + end + + private + + def validate_remote + remote.present? && URI.parse(remote).host + rescue URI::Error + false + end + + def validate_projects + raw_projects.all? do |project| + project['name'] && project['path'] + end + end + + def repository_url(name) + URI.join(remote, name).to_s + end + + def remote + return @remote if defined?(@remote) + + remote_tag = parsed_xml.css('manifest > remote').first + @remote = remote_tag['review'] if remote_tag + end + + def raw_projects + @raw_projects ||= parsed_xml.css('manifest > project') + end + end + end +end diff --git a/lib/gitlab/manifest_import/project_creator.rb b/lib/gitlab/manifest_import/project_creator.rb new file mode 100644 index 00000000000..b5967c93735 --- /dev/null +++ b/lib/gitlab/manifest_import/project_creator.rb @@ -0,0 +1,41 @@ +module Gitlab + module ManifestImport + class ProjectCreator + attr_reader :repository, :destination, :current_user + + def initialize(repository, destination, current_user) + @repository = repository + @destination = destination + @current_user = current_user + end + + def execute + group_full_path, _, project_path = repository[:path].rpartition('/') + group_full_path = File.join(destination.full_path, group_full_path) if destination + group = create_group_with_parents(group_full_path) + + params = { + import_url: repository[:url], + import_type: 'manifest', + namespace_id: group.id, + path: project_path, + name: project_path, + visibility_level: destination.visibility_level + } + + Projects::CreateService.new(current_user, params).execute + end + + private + + def create_group_with_parents(full_path) + params = { + group_path: full_path, + visibility_level: destination.visibility_level + } + + Groups::NestedCreateService.new(current_user, params).execute + end + end + end +end diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 38f119cf06d..c205f348023 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -20,7 +20,7 @@ module Gitlab define_histogram :gitlab_sql_duration_seconds do docstring 'SQL time' base_labels Transaction::BASE_LABELS - buckets [0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0] + buckets [0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0] end def current_transaction diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index b9832a724c4..d0cb7c1a7cf 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -34,11 +34,16 @@ module Gitlab start = Time.now Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082 + # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273 + out_reader = Thread.new { stdout.read } + err_reader = Thread.new { stderr.read } + yield(stdin) if block_given? stdin.close - cmd_stdout = stdout.read - cmd_stderr = stderr.read + cmd_stdout = out_reader.value + cmd_stderr = err_reader.value cmd_status = wait_thr.value end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 83ff735580e..1b68156a503 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -77,6 +77,9 @@ msgstr "" msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" +msgid "%{counter_storage} (%{counter_repositories} repositories, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS)" +msgstr "" + msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "" @@ -202,6 +205,21 @@ msgstr "" msgid "404|Please contact your GitLab administrator if you think this is a mistake." msgstr "" +msgid "<code>\"johnsmith@example.com\": \"@johnsmith\"</code> will add \"By <a href=\"#\">@johnsmith</a>\" to all issues and comments originally created by johnsmith@example.com, and will set <a href=\"#\">@johnsmith</a> as the assignee on all issues originally assigned to johnsmith@example.com." +msgstr "" + +msgid "<code>\"johnsmith@example.com\": \"John Smith\"</code> will add \"By John Smith\" to all issues and comments originally created by johnsmith@example.com." +msgstr "" + +msgid "<code>\"johnsmith@example.com\": \"johnsm...@example.com\"</code> will add \"By johnsm...@example.com\" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy." +msgstr "" + +msgid "<code>\"johnsmith@example.com\": \"johnsmith@example.com\"</code> will add \"By <a href=\"#\">johnsmith@example.com</a>\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address." +msgstr "" + +msgid "<strong>%{group_name}</strong> group members" +msgstr "" + msgid "<strong>Removes</strong> source branch" msgstr "" @@ -238,6 +256,9 @@ msgstr "" msgid "Access Tokens" msgstr "" +msgid "Access denied! Please verify you can add deploy keys to this repository." +msgstr "" + msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" @@ -274,6 +295,9 @@ msgstr "" msgid "Add Readme" msgstr "" +msgid "Add new application" +msgstr "" + msgid "Add new directory" msgstr "" @@ -283,6 +307,12 @@ msgstr "" msgid "Add todo" msgstr "" +msgid "Add user(s) to the group:" +msgstr "" + +msgid "Add users to group" +msgstr "" + msgid "AdminArea|Stop all jobs" msgstr "" @@ -367,9 +397,18 @@ msgstr "" msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import." msgstr "" +msgid "An application called %{link_to_client} is requesting access to your GitLab account." +msgstr "" + +msgid "An empty GitLab User field will add the FogBugz user's full name (e.g. \"By John Smith\") in the description of all issues and comments. It will also associate and/or assign these issues and comments with the project creator." +msgstr "" + msgid "An error accured whilst committing your changes." msgstr "" +msgid "An error has occurred" +msgstr "" + msgid "An error occured creating the new branch." msgstr "" @@ -463,9 +502,24 @@ msgstr "" msgid "An error occurred. Please try again." msgstr "" +msgid "Anonymous" +msgstr "" + +msgid "Any" +msgstr "" + msgid "Appearance" msgstr "" +msgid "Application" +msgstr "" + +msgid "Application Id" +msgstr "" + +msgid "Application: %{name}" +msgstr "" + msgid "Applications" msgstr "" @@ -481,6 +535,9 @@ msgstr "" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "" +msgid "Are you sure you want to remove %{group_name}?" +msgstr "" + msgid "Are you sure you want to remove this identity?" msgstr "" @@ -544,6 +601,24 @@ msgstr "" msgid "Author" msgstr "" +msgid "Authorization code:" +msgstr "" + +msgid "Authorization was granted by entering your username and password in the application." +msgstr "" + +msgid "Authorize" +msgstr "" + +msgid "Authorize %{link_to_client} to use your account?" +msgstr "" + +msgid "Authorized At" +msgstr "" + +msgid "Authorized applications (%{size})" +msgstr "" + msgid "Authors: %{authors}" msgstr "" @@ -685,6 +760,12 @@ msgstr "" msgid "Below are examples of regex for existing tools:" msgstr "" +msgid "Below you will find all the groups that are public." +msgstr "" + +msgid "Bitbucket import" +msgstr "" + msgid "Boards" msgstr "" @@ -900,6 +981,12 @@ msgstr "" msgid "CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages." msgstr "" +msgid "Callback URL" +msgstr "" + +msgid "Callback url" +msgstr "" + msgid "Can't find HEAD commit for this branch" msgstr "" @@ -960,6 +1047,12 @@ msgstr "" msgid "Cherry-pick this merge request" msgstr "" +msgid "Choose <strong>Create archive</strong> and wait for archiving to complete." +msgstr "" + +msgid "Choose <strong>Next</strong> at the bottom of the page." +msgstr "" + msgid "Choose File ..." msgstr "" @@ -975,6 +1068,9 @@ msgstr "" msgid "Choose file..." msgstr "" +msgid "Choose the top-level group for your repository imports." +msgstr "" + msgid "Choose which repositories you want to import." msgstr "" @@ -1068,9 +1164,15 @@ msgstr "" msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone." msgstr "" +msgid "Click the <strong>Download</strong> button and wait for downloading to complete." +msgstr "" + msgid "Click the <strong>Promote</strong> button in the top right corner to promote it to a group milestone." msgstr "" +msgid "Click the <strong>Select none</strong> button on the right, since we only need \"Google Code Project Hosting\"." +msgstr "" + msgid "Click the button below to begin the install process by navigating to the Kubernetes page" msgstr "" @@ -1080,6 +1182,9 @@ msgstr "" msgid "Click to expand text" msgstr "" +msgid "Clients" +msgstr "" + msgid "Clone repository" msgstr "" @@ -1615,6 +1720,9 @@ msgstr "" msgid "Continue" msgstr "" +msgid "Continue to the next step" +msgstr "" + msgid "Continuous Integration and Deployment" msgstr "" @@ -1657,9 +1765,6 @@ msgstr "" msgid "Copy commit SHA to clipboard" msgstr "" -msgid "Copy file name to clipboard" -msgstr "" - msgid "Copy file path to clipboard" msgstr "" @@ -1699,6 +1804,9 @@ msgstr "" msgid "Create file" msgstr "" +msgid "Create group" +msgstr "" + msgid "Create group label" msgstr "" @@ -1720,6 +1828,9 @@ msgstr "" msgid "Create new file" msgstr "" +msgid "Create new file or directory" +msgstr "" + msgid "Create new label" msgstr "" @@ -1741,9 +1852,15 @@ msgstr "" msgid "Created" msgstr "" +msgid "Created At" +msgstr "" + msgid "Created by me" msgstr "" +msgid "Created on:" +msgstr "" + msgid "Cron Timezone" msgstr "" @@ -1765,6 +1882,12 @@ msgstr "" msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." msgstr "" +msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import." +msgstr "" + +msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import." +msgstr "" + msgid "Cycle Analytics" msgstr "" @@ -1804,6 +1927,12 @@ msgstr "" msgid "Decline and sign out" msgstr "" +msgid "Default: Directly import the Google Code email address or username" +msgstr "" + +msgid "Default: Map a FogBugz account ID to a full name" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "" @@ -1816,6 +1945,9 @@ msgstr "" msgid "Delete list" msgstr "" +msgid "Deny" +msgstr "" + msgid "Deploy" msgid_plural "Deploys" msgstr[0] "" @@ -1950,6 +2082,12 @@ msgstr "" msgid "Description" msgstr "" +msgid "Description:" +msgstr "" + +msgid "Destroy" +msgstr "" + msgid "Details" msgstr "" @@ -1977,9 +2115,18 @@ msgstr "" msgid "Discard draft" msgstr "" +msgid "Discover projects, groups and snippets. Share your projects with others" +msgstr "" + +msgid "Dismiss" +msgstr "" + msgid "Dismiss Cycle Analytics introduction box" msgstr "" +msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?" +msgstr "" + msgid "Domain" msgstr "" @@ -2037,9 +2184,15 @@ msgstr "" msgid "Edit Snippet" msgstr "" +msgid "Edit application" +msgstr "" + msgid "Edit files in the editor and commit changes here" msgstr "" +msgid "Edit group: %{group_name}" +msgstr "" + msgid "Edit identity for %{user_name}" msgstr "" @@ -2247,6 +2400,12 @@ msgstr "" msgid "Expand sidebar" msgstr "" +msgid "Explore GitLab" +msgstr "" + +msgid "Explore Groups" +msgstr "" + msgid "Explore groups" msgstr "" @@ -2307,6 +2466,12 @@ msgstr "" msgid "Find file" msgstr "" +msgid "Find the downloaded ZIP file and decompress it." +msgstr "" + +msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file." +msgstr "" + msgid "Finished" msgstr "" @@ -2316,6 +2481,24 @@ msgstr "" msgid "FirstPushedBy|pushed by" msgstr "" +msgid "FogBugz Email" +msgstr "" + +msgid "FogBugz Import" +msgstr "" + +msgid "FogBugz Password" +msgstr "" + +msgid "FogBugz URL" +msgstr "" + +msgid "FogBugz import" +msgstr "" + +msgid "Follow the steps below to export your Google Code project data." +msgstr "" + msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)" msgstr "" @@ -2348,6 +2531,18 @@ msgstr "" msgid "From %{provider_title}" msgstr "" +msgid "From Bitbucket" +msgstr "" + +msgid "From FogBugz" +msgstr "" + +msgid "From GitLab.com" +msgstr "" + +msgid "From Google Code" +msgstr "" + msgid "From issue creation until deploy to production" msgstr "" @@ -2369,6 +2564,9 @@ msgstr "" msgid "Generate a default set of labels" msgstr "" +msgid "Git" +msgstr "" + msgid "Git repository URL" msgstr "" @@ -2393,9 +2591,21 @@ msgstr "" msgid "GitLab Group Runners can execute code for all the projects in this group." msgstr "" +msgid "GitLab Import" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "GitLab User" +msgstr "" + +msgid "GitLab project export" +msgstr "" + +msgid "GitLab.com import" +msgstr "" + msgid "Gitaly" msgstr "" @@ -2405,18 +2615,33 @@ msgstr "" msgid "Gitaly|Address" msgstr "" +msgid "Gitea Host URL" +msgstr "" + +msgid "Gitea Import" +msgstr "" + msgid "Go Back" msgstr "" msgid "Go back" msgstr "" +msgid "Go to %{link_to_google_takeout}." +msgstr "" + msgid "Go to your fork" msgstr "" msgid "GoToYourFork|Fork" msgstr "" +msgid "Google Code import" +msgstr "" + +msgid "Google Takeout" +msgstr "" + msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" @@ -2426,18 +2651,33 @@ msgstr "" msgid "Graph" msgstr "" +msgid "Group" +msgstr "" + msgid "Group CI/CD settings" msgstr "" +msgid "Group Git LFS status:" +msgstr "" + msgid "Group ID" msgstr "" msgid "Group Runners" msgstr "" +msgid "Group avatar" +msgstr "" + +msgid "Group info:" +msgstr "" + msgid "Group maintainers can register group runners in the %{link}" msgstr "" +msgid "Group: %{group_name}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -2462,6 +2702,9 @@ msgstr "" msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}" msgstr "" +msgid "Groups" +msgstr "" + msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}." msgstr "" @@ -2620,21 +2863,57 @@ msgstr "" msgid "Import" msgstr "" +msgid "Import Projects from Gitea" +msgstr "" + +msgid "Import all compatible projects" +msgstr "" + +msgid "Import all projects" +msgstr "" + msgid "Import all repositories" msgstr "" +msgid "Import an exported GitLab project" +msgstr "" + msgid "Import in progress" msgstr "" +msgid "Import multiple repositories by uploading a manifest file." +msgstr "" + +msgid "Import project" +msgstr "" + +msgid "Import projects from Bitbucket" +msgstr "" + +msgid "Import projects from FogBugz" +msgstr "" + +msgid "Import projects from GitLab.com" +msgstr "" + +msgid "Import projects from Google Code" +msgstr "" + msgid "Import repositories from GitHub" msgstr "" msgid "Import repository" msgstr "" +msgid "In the next step, you'll be able to select the projects you want to import." +msgstr "" + msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept." msgstr "" +msgid "Incompatible Project" +msgstr "" + msgid "Inline" msgstr "" @@ -2832,9 +3111,18 @@ msgstr "" msgid "Leave project" msgstr "" +msgid "Leave the \"File type\" and \"Delivery method\" options on their default values." +msgstr "" + msgid "List" msgstr "" +msgid "List Your Gitea Repositories" +msgstr "" + +msgid "List available repositories" +msgstr "" + msgid "List your GitHub repositories" msgstr "" @@ -2862,9 +3150,21 @@ msgstr "" msgid "Locked to current projects" msgstr "" +msgid "Make sure you're logged into the account that owns the projects you'd like to import." +msgstr "" + +msgid "Manage access" +msgstr "" + msgid "Manage all notifications" msgstr "" +msgid "Manage applications that can use GitLab as an OAuth provider, and applications that you've authorized to use your account." +msgstr "" + +msgid "Manage applications that you've authorized to use your account." +msgstr "" + msgid "Manage group labels" msgstr "" @@ -2874,6 +3174,24 @@ msgstr "" msgid "Manage project labels" msgstr "" +msgid "Manifest" +msgstr "" + +msgid "Manifest file import" +msgstr "" + +msgid "Map a FogBugz account ID to a GitLab user" +msgstr "" + +msgid "Map a Google Code user to a GitLab user" +msgstr "" + +msgid "Map a Google Code user to a full email address" +msgstr "" + +msgid "Map a Google Code user to a full name" +msgstr "" + msgid "Mar" msgstr "" @@ -3024,6 +3342,9 @@ msgstr "" msgid "More information is available|here" msgstr "" +msgid "Most stars" +msgstr "" + msgid "Move" msgstr "" @@ -3039,6 +3360,9 @@ msgstr "" msgid "Name your individual key via a title" msgstr "" +msgid "Name:" +msgstr "" + msgid "Nav|Help" msgstr "" @@ -3054,6 +3378,12 @@ msgstr "" msgid "New" msgstr "" +msgid "New Application" +msgstr "" + +msgid "New Group" +msgstr "" + msgid "New Identity" msgstr "" @@ -3152,12 +3482,18 @@ msgstr "" msgid "No messages were logged" msgstr "" +msgid "No public groups" +msgstr "" + msgid "No repository" msgstr "" msgid "No schedules" msgstr "" +msgid "No, directly import the existing email addresses and usernames." +msgstr "" + msgid "None" msgstr "" @@ -3269,6 +3605,12 @@ msgstr "" msgid "OfSearchInADropdown|Filter" msgstr "" +msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git." +msgstr "" + +msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git." +msgstr "" + msgid "Online IDE integration settings." msgstr "" @@ -3293,6 +3635,12 @@ msgstr "" msgid "Operations" msgstr "" +msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab." +msgstr "" + +msgid "Optionally, you can %{link_to_customize} how Google Code email addresses and usernames are imported into GitLab." +msgstr "" + msgid "Options" msgstr "" @@ -3338,6 +3686,9 @@ msgstr "" msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key." msgstr "" +msgid "Path:" +msgstr "" + msgid "Pause" msgstr "" @@ -3524,6 +3875,15 @@ msgstr "" msgid "Please accept the Terms of Service before continuing." msgstr "" +msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again." +msgstr "" + +msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again." +msgstr "" + +msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access." +msgstr "" + msgid "Please select at least one filter to see results" msgstr "" @@ -3683,6 +4043,9 @@ msgstr "" msgid "Project export started. A download link will be sent by email." msgstr "" +msgid "Project name" +msgstr "" + msgid "ProjectActivityRSS|Subscribe" msgstr "" @@ -3698,6 +4061,9 @@ msgstr "" msgid "Projects" msgstr "" +msgid "Projects shared with %{group_name}" +msgstr "" + msgid "ProjectsDropdown|Frequently visited" msgstr "" @@ -3824,6 +4190,9 @@ msgstr "" msgid "Read more" msgstr "" +msgid "Read more about project permissions <strong>%{link_to_help}</strong>" +msgstr "" + msgid "Readme" msgstr "" @@ -3890,6 +4259,9 @@ msgstr "" msgid "Repository Settings" msgstr "" +msgid "Repository URL" +msgstr "" + msgid "Repository maintenance" msgstr "" @@ -3955,6 +4327,9 @@ msgstr "" msgid "Reviewing (merge request !%{mergeRequestId})" msgstr "" +msgid "Revoke" +msgstr "" + msgid "Runner token" msgstr "" @@ -3979,6 +4354,9 @@ msgstr "" msgid "Save" msgstr "" +msgid "Save application" +msgstr "" + msgid "Save changes" msgstr "" @@ -4000,6 +4378,12 @@ msgstr "" msgid "Scheduling Pipelines" msgstr "" +msgid "Scope" +msgstr "" + +msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right." +msgstr "" + msgid "Scroll to bottom" msgstr "" @@ -4039,6 +4423,9 @@ msgstr "" msgid "Seconds to wait for a storage access attempt" msgstr "" +msgid "Secret:" +msgstr "" + msgid "Select" msgstr "" @@ -4069,12 +4456,18 @@ msgstr "" msgid "Select project to choose zone" msgstr "" +msgid "Select projects you want to import." +msgstr "" + msgid "Select source branch" msgstr "" msgid "Select target branch" msgstr "" +msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user." +msgstr "" + msgid "Send email" msgstr "" @@ -4380,6 +4773,9 @@ msgstr "" msgid "Storage" msgstr "" +msgid "Storage:" +msgstr "" + msgid "Subgroups" msgstr "" @@ -4580,6 +4976,12 @@ msgstr "" msgid "The time taken by each data entry gathered by that stage." msgstr "" +msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side." +msgstr "" + +msgid "The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames will be imported into GitLab. You can change this by populating the table below." +msgstr "" + msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "" @@ -4622,6 +5024,12 @@ msgstr "" msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area." msgstr "" +msgid "This application was created by %{link_to_owner}." +msgstr "" + +msgid "This application will be able to:" +msgstr "" + msgid "This diff is collapsed." msgstr "" @@ -4887,6 +5295,12 @@ msgstr "" msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}." msgstr "" +msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import." +msgstr "" + +msgid "To get started, please enter your Gitea Host URL and a %{link_to_personal_token}." +msgstr "" + msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import." msgstr "" @@ -4896,9 +5310,15 @@ msgstr "" msgid "To import an SVN repository, check out %{svn_link}." msgstr "" +msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here." +msgstr "" + msgid "To start serving your jobs you can add Runners to your group" msgstr "" +msgid "To this GitLab instance" +msgstr "" + msgid "To validate your GitLab CI configurations, go to 'CI/CD → Pipelines' inside your project, and click on the 'CI Lint' button." msgstr "" @@ -4935,6 +5355,9 @@ msgstr "" msgid "Track time with quick actions" msgstr "" +msgid "Trending" +msgstr "" + msgid "Trigger this manual action" msgstr "" @@ -4997,6 +5420,9 @@ msgstr[1] "" msgid "Update your group name, description, avatar, and other general settings." msgstr "" +msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:" +msgstr "" + msgid "Upload New File" msgstr "" @@ -5015,9 +5441,15 @@ msgstr "" msgid "Usage statistics" msgstr "" +msgid "Use <code>%{native_redirect_uri}</code> for local tests" +msgstr "" + msgid "Use group milestones to manage issues from multiple projects in the same milestone." msgstr "" +msgid "Use one line per URI" +msgstr "" + msgid "Use the following registration token during setup:" msgstr "" @@ -5027,6 +5459,9 @@ msgstr "" msgid "User and IP Rate Limits" msgstr "" +msgid "User map" +msgstr "" + msgid "Users" msgstr "" @@ -5075,6 +5510,12 @@ msgstr "" msgid "Visibility and access controls" msgstr "" +msgid "Visibility level:" +msgstr "" + +msgid "Visibility:" +msgstr "" + msgid "VisibilityLevel|Internal" msgstr "" @@ -5252,6 +5693,12 @@ msgstr "" msgid "Yes, add it" msgstr "" +msgid "Yes, let me map Google Code users to full names or GitLab users." +msgstr "" + +msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" @@ -5279,6 +5726,9 @@ msgstr "" msgid "You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}" msgstr "" +msgid "You can easily contribute to them by requesting to join these groups." +msgstr "" + msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" @@ -5300,6 +5750,12 @@ msgstr "" msgid "You do not have any assigned merge requests" msgstr "" +msgid "You don't have any applications" +msgstr "" + +msgid "You don't have any authorized applications" +msgstr "" + msgid "You have no permissions" msgstr "" @@ -5366,6 +5822,12 @@ msgstr "" msgid "Your Todos" msgstr "" +msgid "Your applications (%{size})" +msgstr "" + +msgid "Your authorized applications" +msgstr "" + msgid "Your changes can be committed to %{branch_name} because a merge request is open." msgstr "" @@ -5402,6 +5864,9 @@ msgstr "" msgid "connecting" msgstr "" +msgid "customize" +msgstr "" + msgid "day" msgid_plural "days" msgstr[0] "" @@ -5413,6 +5878,9 @@ msgstr "" msgid "disabled" msgstr "" +msgid "done" +msgstr "" + msgid "enabled" msgstr "" @@ -5422,6 +5890,12 @@ msgstr "" msgid "for this project" msgstr "" +msgid "here" +msgstr "" + +msgid "import flow" +msgstr "" + msgid "importing" msgstr "" @@ -5633,6 +6107,9 @@ msgstr "" msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgstr "" +msgid "started" +msgstr "" + msgid "this document" msgstr "" diff --git a/package.json b/package.json index 26b87c70e98..9dd7d80945f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "autosize": "^4.0.0", "axios": "^0.17.1", "babel-core": "^6.26.3", - "babel-loader": "^7.1.4", + "babel-loader": "^7.1.5", "babel-plugin-transform-define": "^1.3.0", "babel-preset-latest": "^6.24.1", "babel-preset-stage-2": "^6.24.1", @@ -36,7 +36,7 @@ "compression-webpack-plugin": "^1.1.11", "core-js": "^2.4.1", "cropper": "^2.3.0", - "css-loader": "^0.28.11", + "css-loader": "^1.0.0", "d3-array": "^1.2.1", "d3-axis": "^1.0.8", "d3-brush": "^1.0.4", @@ -90,15 +90,15 @@ "url-loader": "^1.0.1", "visibilityjs": "^1.2.4", "vue": "^2.5.16", - "vue-loader": "^15.2.0", + "vue-loader": "^15.2.4", "vue-resource": "^1.5.0", "vue-router": "^3.0.1", "vue-template-compiler": "^2.5.16", "vue-virtual-scroll-list": "^1.2.5", "vuex": "^3.0.1", - "webpack": "^4.11.1", - "webpack-bundle-analyzer": "^2.11.1", - "webpack-cli": "^3.0.2", + "webpack": "^4.16.0", + "webpack-bundle-analyzer": "^2.13.1", + "webpack-cli": "^3.0.8", "webpack-stats-plugin": "^0.2.1", "worker-loader": "^2.0.0" }, @@ -123,15 +123,16 @@ "ignore": "^3.3.7", "istanbul": "^0.4.5", "jasmine-core": "^2.9.0", + "jasmine-diff": "^0.1.3", "jasmine-jquery": "^2.1.1", - "karma": "^2.0.2", + "karma": "^2.0.4", "karma-chrome-launcher": "^2.2.0", "karma-coverage-istanbul-reporter": "^1.4.2", - "karma-jasmine": "^1.1.1", + "karma-jasmine": "^1.1.2", "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "3.0.0", - "nodemon": "^1.17.3", + "karma-webpack": "^4.0.0-beta.0", + "nodemon": "^1.18.2", "prettier": "1.12.1", "webpack-dev-server": "^3.1.4" } diff --git a/qa/README.md b/qa/README.md index a4b4398645e..be4cf89ebbc 100644 --- a/qa/README.md +++ b/qa/README.md @@ -55,7 +55,7 @@ Since the arguments would be passed to `rspec`, you could use all `rspec` options there. For example, passing `--backtrace` and also line number: ``` -bin/qa Test::Instance http://localhost qa/specs/features/login/standard_spec.rb:3 --backtrace +bin/qa Test::Instance http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace ``` ### Overriding the authenticated user @@ -1,5 +1,7 @@ $: << File.expand_path(File.dirname(__FILE__)) +Encoding.default_external = 'UTF-8' + module QA ## # GitLab QA runtime classes, mostly singletons. @@ -41,14 +43,17 @@ module QA autoload :Project, 'qa/factory/resource/project' autoload :MergeRequest, 'qa/factory/resource/merge_request' autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github' + autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork' autoload :DeployKey, 'qa/factory/resource/deploy_key' autoload :Branch, 'qa/factory/resource/branch' autoload :SecretVariable, 'qa/factory/resource/secret_variable' autoload :Runner, 'qa/factory/resource/runner' autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token' autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster' + autoload :User, 'qa/factory/resource/user' autoload :ProjectMilestone, 'qa/factory/resource/project_milestone' autoload :Wiki, 'qa/factory/resource/wiki' + autoload :Fork, 'qa/factory/resource/fork' end module Repository @@ -107,6 +112,7 @@ module QA module Main autoload :Login, 'qa/page/main/login' autoload :OAuth, 'qa/page/main/oauth' + autoload :SignUp, 'qa/page/main/sign_up' end module Settings @@ -167,6 +173,10 @@ module QA autoload :Index, 'qa/page/project/issue/index' end + module Fork + autoload :New, 'qa/page/project/fork/new' + end + module Milestone autoload :New, 'qa/page/project/milestone/new' autoload :Index, 'qa/page/project/milestone/index' @@ -200,6 +210,10 @@ module QA autoload :Sidebar, 'qa/page/issuable/sidebar' end + module Layout + autoload :Banner, 'qa/page/layout/banner' + end + module MergeRequest autoload :New, 'qa/page/merge_request/new' autoload :Show, 'qa/page/merge_request/show' diff --git a/qa/qa/factory/repository/project_push.rb b/qa/qa/factory/repository/project_push.rb index 48674c08a8d..4f78098d348 100644 --- a/qa/qa/factory/repository/project_push.rb +++ b/qa/qa/factory/repository/project_push.rb @@ -11,6 +11,8 @@ module QA factory.output end + product(:project) { |factory| factory.project } + def initialize @file_name = 'file.txt' @file_content = '# This is test project' diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb index 4f97e65b091..5b7ebf6c41f 100644 --- a/qa/qa/factory/repository/push.rb +++ b/qa/qa/factory/repository/push.rb @@ -5,7 +5,8 @@ module QA module Repository class Push < Factory::Base attr_accessor :file_name, :file_content, :commit_message, - :branch_name, :new_branch, :output, :repository_uri + :branch_name, :new_branch, :output, :repository_uri, + :user attr_writer :remote_branch @@ -31,9 +32,20 @@ module QA def fabricate! Git::Repository.perform do |repository| repository.uri = repository_uri + repository.use_default_credentials + username = 'GitLab QA' + email = 'root@gitlab.com' + + if user + repository.username = user.username + repository.password = user.password + username = user.name + email = user.email + end + repository.clone - repository.configure_identity('GitLab QA', 'root@gitlab.com') + repository.configure_identity(username, email) if new_branch repository.checkout_new_branch(branch_name) diff --git a/qa/qa/factory/resource/branch.rb b/qa/qa/factory/resource/branch.rb index 7fb0633ec90..bc252bf3148 100644 --- a/qa/qa/factory/resource/branch.rb +++ b/qa/qa/factory/resource/branch.rb @@ -9,18 +9,6 @@ module QA project.name = 'protected-branch-project' end - product :name do - Page::Project::Settings::Repository.act do - expand_protected_branches(&:last_branch_name) - end - end - - product :push_allowance do - Page::Project::Settings::Repository.act do - expand_protected_branches(&:last_push_allowance) - end - end - def initialize @branch_name = 'test/branch' @allow_to_push = true @@ -80,15 +68,6 @@ module QA end page.protect_branch - - # Avoid Selenium::WebDriver::Error::StaleElementReferenceError - # without sleeping. I.e. this completes fast on fast machines. - page.refresh - - # It is possible for the protected branch row to "disappear" at first - page.wait do - page.has_content?(branch_name) - end end end end diff --git a/qa/qa/factory/resource/fork.rb b/qa/qa/factory/resource/fork.rb new file mode 100644 index 00000000000..1d0c76a3d30 --- /dev/null +++ b/qa/qa/factory/resource/fork.rb @@ -0,0 +1,24 @@ +module QA + module Factory + module Resource + class Fork < Factory::Base + dependency Factory::Repository::ProjectPush, as: :push + + dependency Factory::Resource::User, as: :user + + product(:user) { |factory| factory.user } + + def fabricate! + push.project.visit! + Page::Project::Show.act { fork_project } + + Page::Project::Fork::New.perform do |fork_new| + fork_new.choose_namespace(user.name) + end + + Page::Layout::Banner.act { has_notice?('The project was successfully forked.') } + end + end + end + end +end diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb index f32cf985e9d..1c9e5f94b22 100644 --- a/qa/qa/factory/resource/kubernetes_cluster.rb +++ b/qa/qa/factory/resource/kubernetes_cluster.rb @@ -36,6 +36,9 @@ module QA if @install_helm_tiller Page::Project::Operations::Kubernetes::Show.perform do |page| + # We must wait a few seconds for permissions to be setup correctly for new cluster + sleep 10 + # Helm must be installed before everything else page.install!(:helm) page.await_installed(:helm) diff --git a/qa/qa/factory/resource/merge_request_from_fork.rb b/qa/qa/factory/resource/merge_request_from_fork.rb new file mode 100644 index 00000000000..6caaf65f673 --- /dev/null +++ b/qa/qa/factory/resource/merge_request_from_fork.rb @@ -0,0 +1,24 @@ +module QA + module Factory + module Resource + class MergeRequestFromFork < MergeRequest + attr_accessor :fork_branch + + dependency Factory::Resource::Fork, as: :fork + + dependency Factory::Repository::ProjectPush, as: :push do |push, factory| + push.project = factory.fork + push.branch_name = factory.fork_branch + push.file_name = 'file2.txt' + push.user = factory.fork.user + end + + def fabricate! + fork.visit! + Page::Project::Show.act { new_merge_request } + Page::MergeRequest::New.act { create_merge_request } + end + end + end + end +end diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb index 7bc64c6ae5d..7fff22b5468 100644 --- a/qa/qa/factory/resource/project.rb +++ b/qa/qa/factory/resource/project.rb @@ -37,6 +37,7 @@ module QA page.choose_test_namespace page.choose_name(@name) page.add_description(@description) + page.set_visibility('Public') page.create_new_project end end diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb new file mode 100644 index 00000000000..e08df9e0cd0 --- /dev/null +++ b/qa/qa/factory/resource/user.rb @@ -0,0 +1,34 @@ +require 'securerandom' + +module QA + module Factory + module Resource + class User < Factory::Base + attr_accessor :name, :username, :email, :password + + def initialize + @name = "name-#{SecureRandom.hex(8)}" + @username = "username-#{SecureRandom.hex(8)}" + @email = "mail#{SecureRandom.hex(8)}@mail.com" + @password = 'password' + end + + product(:name) { |factory| factory.name } + + product(:username) { |factory| factory.username } + + product(:email) { |factory| factory.email } + + product(:password) { |factory| factory.password } + + def fabricate! + Page::Menu::Main.act { sign_out } + Page::Main::Login.act { switch_to_register_tab } + Page::Main::SignUp.perform do |page| + page.sign_up!(name: name, username: username, email: email, password: password) + end + end + end + end + end +end diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb index dec2ce1eab3..f207264e24f 100644 --- a/qa/qa/page/issuable/sidebar.rb +++ b/qa/qa/page/issuable/sidebar.rb @@ -4,6 +4,7 @@ module QA class Sidebar < Page::Base view 'app/views/shared/issuable/_sidebar.html.haml' do element :labels_block, ".issuable-show-labels" + element :milestones_block, '.block.milestone' end def has_label?(label) @@ -11,6 +12,12 @@ module QA !!find('span', text: label) end end + + def has_milestone?(milestone) + page.within('.block.milestone') do + !!find("[href*='/milestones/']", text: milestone) + end + end end end end diff --git a/qa/qa/page/layout/banner.rb b/qa/qa/page/layout/banner.rb new file mode 100644 index 00000000000..e7654bdafc9 --- /dev/null +++ b/qa/qa/page/layout/banner.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Layout + class Banner < Page::Base + view 'app/views/layouts/header/_read_only_banner.html.haml' do + element :flash_notice, ".flash-notice" + end + + def has_notice?(message) + page.within('.flash-notice') do + !!find('span', text: message) + end + end + end + end + end +end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 26c99efc53d..6cdfbd1c125 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -25,19 +25,24 @@ module QA element :standard_tab, "link_to 'Standard'" end + view 'app/views/devise/shared/_tabs_normal.html.haml' do + element :sign_in_tab, /nav-link.*login-pane.*Sign in/ + element :register_tab, /nav-link.*register-pane.*Register/ + end + def initialize # The login page is usually the entry point for all the scenarios so # we need to wait for the instance to start. That said, in some cases # we are already logged-in so we check both cases here. wait(max: 500) do page.has_css?('.login-page') || - Page::Menu::Main.act { has_personal_area? } + Page::Menu::Main.act { has_personal_area?(wait: 0) } end end def sign_in_using_credentials # Don't try to log-in if we're already logged-in - return if Page::Menu::Main.act { has_personal_area? } + return if Page::Menu::Main.act { has_personal_area?(wait: 0) } using_wait_time 0 do set_initial_password_if_present @@ -48,12 +53,22 @@ module QA sign_in_using_gitlab_credentials end end + + Page::Menu::Main.act { has_personal_area? } end def self.path '/users/sign_in' end + def switch_to_sign_in_tab + click_on 'Sign in' + end + + def switch_to_register_tab + click_on 'Register' + end + private def sign_in_using_ldap_credentials diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb index 6f548148363..618f114e058 100644 --- a/qa/qa/page/main/oauth.rb +++ b/qa/qa/page/main/oauth.rb @@ -3,7 +3,7 @@ module QA module Main class OAuth < Page::Base view 'app/views/doorkeeper/authorizations/new.html.haml' do - element :authorization_button, 'submit_tag "Authorize"' + element :authorization_button, 'submit_tag _("Authorize")' end def needs_authorization? diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb new file mode 100644 index 00000000000..9a834e94b81 --- /dev/null +++ b/qa/qa/page/main/sign_up.rb @@ -0,0 +1,27 @@ +module QA + module Page + module Main + class SignUp < Page::Base + view 'app/views/devise/shared/_signup_box.html.haml' do + element :name, 'text_field :name' + element :username, 'text_field :username' + element :email_field, 'email_field :email' + element :email_confirmation, 'email_field :email_confirmation' + element :password, 'password_field :password' + element :register_button, 'submit "Register"' + end + + def sign_up!(name:, username:, email:, password:) + fill_in :new_user_name, with: name + fill_in :new_user_username, with: username + fill_in :new_user_email, with: email + fill_in :new_user_email_confirmation, with: email + fill_in :new_user_password, with: password + click_button 'Register' + + Page::Menu::Main.act { has_personal_area? } + end + end + end + end +end diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb index aef5c9f9c82..36e7285f7b7 100644 --- a/qa/qa/page/menu/main.rb +++ b/qa/qa/page/menu/main.rb @@ -60,9 +60,9 @@ module QA end end - def has_personal_area? + def has_personal_area?(wait: Capybara.default_max_wait_time) # No need to wait, either we're logged-in, or not. - using_wait_time(0) { page.has_selector?('.qa-user-avatar') } + using_wait_time(wait) { page.has_selector?('.qa-user-avatar') } end private diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index f3200160a78..c200f14f4fb 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -79,12 +79,6 @@ module QA click_element :squash_checkbox end - - def has_milestone?(milestone_title) - page.within('.issuable-sidebar') do - !!find("[href*='/milestones/']", text: milestone_title, wait: 1) - end - end end end end diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb new file mode 100644 index 00000000000..ed92df956bf --- /dev/null +++ b/qa/qa/page/project/fork/new.rb @@ -0,0 +1,17 @@ +module QA + module Page + module Project + module Fork + class New < Page::Base + view 'app/views/projects/forks/_fork_button.html.haml' do + element :namespace, 'link_to project_forks_path' + end + + def choose_namespace(namespace = Runtime::Namespace.path) + click_on namespace + end + end + end + end + end +end diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index 7976e96d43b..9e812fa7c74 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -14,6 +14,7 @@ module QA element :project_path, 'text_field :path' element :project_description, 'text_area :description' element :project_create_button, "submit 'Create project'" + element :visibility_radios, 'visibility_level:' end view 'app/views/projects/_import_project_pane.html.haml' do @@ -42,6 +43,10 @@ module QA click_on 'Create project' end + def set_visibility(visibility) + choose visibility + end + def go_to_github_import click_link 'GitHub' end diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index e572ae12132..76591a4e3fe 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -16,7 +16,6 @@ module QA end view 'app/views/projects/protected_branches/_update_protected_branch.html.haml' do - element :allowed_to_push element :allowed_to_merge end @@ -24,10 +23,6 @@ module QA element :protected_branches_list end - view 'app/views/projects/protected_branches/shared/_protected_branch.html.haml' do - element :protected_branch_name - end - def select_branch(branch_name) click_element :protected_branch_select @@ -62,18 +57,6 @@ module QA click_on 'Protect' end - def last_branch_name - within_element(:protected_branches_list) do - all('.qa-protected-branch-name').last - end - end - - def last_push_allowance - within_element(:protected_branches_list) do - all('.qa-allowed-to-push').last - end - end - private def click_allow(action, text) diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 1dcdb59490a..88861d5772d 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -22,6 +22,11 @@ module QA element :branches_dropdown end + view 'app/views/projects/buttons/_fork.html.haml' do + element :fork_label, "%span= s_('GoToYourFork|Fork')" + element :fork_link, "link_to new_project_fork_path(@project)" + end + view 'app/views/projects/_files.html.haml' do element :tree_holder, '.tree-holder' end @@ -61,6 +66,10 @@ module QA click_link 'New issue' end + + def fork_project + click_on 'Fork' + end end end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index cee381f3379..877864fb40c 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -85,6 +85,10 @@ module QA driver.browser.save_screenshot(path) end + Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| + File.join(QA::Runtime::Namespace.name, example.file_path.sub('./qa/specs/features/', '')) + end + Capybara.configure do |config| config.default_driver = :chrome config.javascript_driver = :chrome diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb index ccfa8b44db3..f1c8ef11f94 100644 --- a/qa/qa/runtime/namespace.rb +++ b/qa/qa/runtime/namespace.rb @@ -8,7 +8,7 @@ module QA end def name - 'qa-test-' + time.strftime('%d-%m-%Y-%H-%M-%S') + "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}" end def path diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb deleted file mode 100644 index 254f47cf217..00000000000 --- a/qa/qa/specs/features/login/standard_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -module QA - describe 'standard user login', :core do - it 'user logs in using credentials' do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - - # TODO, since `Signed in successfully` message was removed - # this is the only way to tell if user is signed in correctly. - # - Page::Menu::Main.perform do |menu| - expect(menu).to have_personal_area - end - end - end -end diff --git a/qa/qa/specs/features/merge_request/create_spec.rb b/qa/qa/specs/features/merge_request/create_spec.rb index 5807e539699..36d7efb02e1 100644 --- a/qa/qa/specs/features/merge_request/create_spec.rb +++ b/qa/qa/specs/features/merge_request/create_spec.rb @@ -20,11 +20,12 @@ module QA merge_request.milestone = current_milestone end - Page::MergeRequest::Show.perform do |merge_request| - expect(page).to have_content('This is a merge request with a milestone') - expect(page).to have_content('Great feature with milestone') - expect(page).to have_content(/Opened [\w\s]+ ago/) - expect(merge_request).to have_milestone(current_milestone.title) + expect(page).to have_content('This is a merge request with a milestone') + expect(page).to have_content('Great feature with milestone') + expect(page).to have_content(/Opened [\w\s]+ ago/) + + Page::Issuable::Sidebar.perform do |sidebar| + expect(sidebar).to have_milestone(current_milestone.title) end end end diff --git a/qa/qa/specs/features/project/fork_project_spec.rb b/qa/qa/specs/features/project/fork_project_spec.rb new file mode 100644 index 00000000000..8ad0120305a --- /dev/null +++ b/qa/qa/specs/features/project/fork_project_spec.rb @@ -0,0 +1,23 @@ +module QA + describe 'Project fork', :core do + it 'can submit merge requests to upstream master' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request| + merge_request.fork_branch = 'feature-branch' + end + + Page::Menu::Main.act { sign_out } + Page::Main::Login.act do + switch_to_sign_in_tab + sign_in_using_credentials + end + + merge_request.visit! + Page::MergeRequest::Show.act { merge! } + + expect(page).to have_content('The changes were merged') + end + end +end diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb index 29ea2e69ec7..c2de94516d9 100644 --- a/qa/qa/specs/features/repository/protected_branches_spec.rb +++ b/qa/qa/specs/features/repository/protected_branches_spec.rb @@ -13,27 +13,20 @@ module QA Page::Main::Login.act { sign_in_using_credentials } end - after do |example| + after do # We need to clear localStorage because we're using it for the dropdown, # and capybara doesn't do this for us. # https://github.com/teamcapybara/capybara/issues/1702 Capybara.execute_script 'localStorage.clear()' - - # In order to help diagnose a false failure - # https://gitlab.com/gitlab-org/gitlab-ce/issues/48241 - log_push_output if example.exception end context 'when developers and maintainers are allowed to push to a protected branch' do - let!(:protected_branch) { create_protected_branch(allow_to_push: true) } - it 'user with push rights successfully pushes to the protected branch' do - expect(protected_branch.name).to have_content(branch_name) - expect(protected_branch.push_allowance).to have_content('Developers + Maintainers') + create_protected_branch(allow_to_push: true) - @push = push_new_file(branch_name) + push = push_new_file(branch_name) - expect(@push.output).to match(/remote: To create a merge request for protected-branch, visit/) + expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/) end end @@ -41,11 +34,11 @@ module QA it 'user without push rights fails to push to the protected branch' do create_protected_branch(allow_to_push: false) - @push = push_new_file(branch_name) + push = push_new_file(branch_name) - expect(@push.output) + expect(push.output) .to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/) - expect(@push.output) + expect(push.output) .to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/) end end @@ -69,13 +62,5 @@ module QA resource.new_branch = false end end - - def log_push_output - if defined?(@push) - filename = File.join('tmp', "push-output-#{project.name}") - puts "Exception detected. Push output will be saved to #{filename}" - IO.binwrite(filename, @push.output) - end - end end end diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb index f278043028f..9dc4edf97d1 100644 --- a/spec/bin/changelog_spec.rb +++ b/spec/bin/changelog_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' load File.expand_path('../../bin/changelog', __dir__) describe 'bin/changelog' do + let(:options) { OpenStruct.new(title: 'Test title', type: 'fixed', dry_run: true) } + + describe ChangelogEntry do + it 'truncates the file path' do + entry = described_class.new(options) + + allow(entry).to receive(:ee?).and_return(false) + allow(entry).to receive(:branch_name).and_return('long-branch-' * 100) + + file_path = entry.send(:file_path) + expect(file_path.length).to eq(140) + end + end + describe ChangelogOptionParser do describe '.parse' do it 'parses --amend' do diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 7376841fac8..c7c83369d7c 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -15,55 +15,16 @@ describe MetricsController do allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir) allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true) allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip, whitelisted_ip_range]) + allow_any_instance_of(MetricsService).to receive(:metrics_text).and_return("prometheus_counter 1") end describe '#index' do shared_examples_for 'endpoint providing metrics' do - it 'returns DB ping metrics' do + it 'returns prometheus metrics' do get :index - expect(response.body).to match(/^db_ping_timeout 0$/) - expect(response.body).to match(/^db_ping_success 1$/) - expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/) - end - - it 'returns Redis ping metrics' do - get :index - - expect(response.body).to match(/^redis_ping_timeout 0$/) - expect(response.body).to match(/^redis_ping_success 1$/) - expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/) - end - - it 'returns Caching ping metrics' do - get :index - - expect(response.body).to match(/^redis_cache_ping_timeout 0$/) - expect(response.body).to match(/^redis_cache_ping_success 1$/) - expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/) - end - - it 'returns Queues ping metrics' do - get :index - - expect(response.body).to match(/^redis_queues_ping_timeout 0$/) - expect(response.body).to match(/^redis_queues_ping_success 1$/) - expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/) - end - - it 'returns SharedState ping metrics' do - get :index - - expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/) - expect(response.body).to match(/^redis_shared_state_ping_success 1$/) - expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/) - end - - it 'returns Gitaly metrics' do - get :index - - expect(response.body).to match(/^gitaly_health_check_success{shard="default"} 1$/) - expect(response.body).to match(/^gitaly_health_check_latency_seconds{shard="default"} [0-9\.]+$/) + expect(response.status).to eq(200) + expect(response.body).to match(/^prometheus_counter 1$/) end context 'prometheus metrics are disabled' do @@ -101,7 +62,7 @@ describe MetricsController do allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip) end - it 'returns proper response' do + it 'returns the expected error response' do get :index expect(response.status).to eq(404) diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index 58f2817c7cc..1ce7e84bef9 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -5,29 +5,10 @@ describe Projects::TodosController do let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } - let(:parent) { project } - - shared_examples 'project todos actions' do - it_behaves_like 'todos actions' - - context 'when not authorized for resource' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) - project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) - sign_in(user) - end - - it "doesn't create todo" do - expect { post_create }.not_to change { user.todos.count } - expect(response).to have_gitlab_http_status(404) - end - end - end context 'Issues' do describe 'POST create' do - def post_create + def go post :create, namespace_id: project.namespace, project_id: project, @@ -36,13 +17,66 @@ describe Projects::TodosController do format: 'html' end - it_behaves_like 'project todos actions' + context 'when authorized' do + before do + sign_in(user) + project.add_developer(user) + end + + it 'creates todo for issue' do + expect do + go + end.to change { user.todos.count }.by(1) + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns todo path and pending count' do + go + + expect(response).to have_gitlab_http_status(200) + expect(json_response['count']).to eq 1 + expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}}) + end + end + + context 'when not authorized for project' do + it 'does not create todo for issue that user has no access to' do + sign_in(user) + expect do + go + end.to change { user.todos.count }.by(0) + + expect(response).to have_gitlab_http_status(404) + end + + it 'does not create todo for issue when user not logged in' do + expect do + go + end.to change { user.todos.count }.by(0) + + expect(response).to have_gitlab_http_status(302) + end + end + + context 'when not authorized for issue' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + sign_in(user) + end + + it "doesn't create todo" do + expect { go }.not_to change { user.todos.count } + expect(response).to have_gitlab_http_status(404) + end + end end end context 'Merge Requests' do describe 'POST create' do - def post_create + def go post :create, namespace_id: project.namespace, project_id: project, @@ -51,7 +85,60 @@ describe Projects::TodosController do format: 'html' end - it_behaves_like 'project todos actions' + context 'when authorized' do + before do + sign_in(user) + project.add_developer(user) + end + + it 'creates todo for merge request' do + expect do + go + end.to change { user.todos.count }.by(1) + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns todo path and pending count' do + go + + expect(response).to have_gitlab_http_status(200) + expect(json_response['count']).to eq 1 + expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}}) + end + end + + context 'when not authorized for project' do + it 'does not create todo for merge request user has no access to' do + sign_in(user) + expect do + go + end.to change { user.todos.count }.by(0) + + expect(response).to have_gitlab_http_status(404) + end + + it 'does not create todo for merge request user has no access to' do + expect do + go + end.to change { user.todos.count }.by(0) + + expect(response).to have_gitlab_http_status(302) + end + end + + context 'when not authorized for merge_request' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) + sign_in(user) + end + + it "doesn't create todo" do + expect { go }.not_to change { user.todos.count } + expect(response).to have_gitlab_http_status(404) + end + end end end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 14486c80341..94f8caedfa6 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,8 +1,8 @@ FactoryBot.define do factory :todo do project - author { project&.creator || user } - user { project&.creator || user } + author { project.creator } + user { project.creator } target factory: :issue action { Todo::ASSIGNED } diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index b45f6f30e40..a81b2169b89 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -28,6 +28,13 @@ FactoryBot.define do secret SecureRandom.hex end + trait :with_file do + after(:create) do |upload| + FileUtils.mkdir_p(File.dirname(upload.absolute_path)) + FileUtils.touch(upload.absolute_path) + end + end + trait :object_storage do store ObjectStorage::Store::REMOTE end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 053e3b189c3..e62bd6f8187 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -154,6 +154,12 @@ describe 'Group' do end end + it 'focuses confirmation field on remove group' do + click_button('Remove group') + + expect(page).to have_selector '#confirm_name_input:focus' + end + it 'removes group' do expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1) expect(group.members.all.count).to be_zero diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb new file mode 100644 index 00000000000..e381d073804 --- /dev/null +++ b/spec/features/import/manifest_import_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'Import multiple repositories by uploading a manifest file', :js, :postgresql do + include Select2Helper + + let(:user) { create(:admin) } + let(:group) { create(:group) } + + before do + sign_in(user) + + group.add_owner(user) + end + + it 'parses manifest file and list repositories' do + visit new_import_manifest_path + + attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml')) + click_on 'List available repositories' + + expect(page).to have_button('Import all repositories') + expect(page).to have_content('https://android-review.googlesource.com/platform/build/blueprint') + end + + it 'imports succesfully imports a project' do + visit new_import_manifest_path + + attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml')) + click_on 'List available repositories' + + page.within(first_row) do + click_on 'Import' + + expect(page).to have_content 'Done' + expect(page).to have_content("#{group.full_path}/build/make") + end + end + + it 'renders an error if invalid file was provided' do + visit new_import_manifest_path + + attach_file('manifest', Rails.root.join('spec/fixtures/banana_sample.gif')) + click_on 'List available repositories' + + expect(page).to have_content 'The uploaded file is not a valid XML file.' + end + + def first_row + page.all('table.import-jobs tbody tr')[0] + end +end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index df8528e79dd..bbe08ff83ff 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -25,6 +25,22 @@ describe 'New project' do expect(page).to have_link('GitLab export') end + describe 'manifest import option' do + before do + visit new_project_path + + find('#import-project-tab').click + end + + context 'when using postgres', :postgresql do + it { expect(page).to have_link('Manifest file') } + end + + context 'when using mysql', :mysql do + it { expect(page).not_to have_link('Manifest file') } + end + end + context 'Visibility level selector', :js do Gitlab::VisibilityLevel.options.each do |key, level| it "sets selector to #{key}" do @@ -201,5 +217,16 @@ describe 'New project' do expect(current_path).to eq new_import_google_code_path end end + + context 'from manifest file', :postgresql do + before do + first('.import_manifest').click + end + + it 'shows import instructions' do + expect(page).to have_content('Manifest file import') + expect(current_path).to eq new_import_manifest_path + end + end end end diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb index 96b7cf1f93b..2fdbc04fa62 100644 --- a/spec/features/projects/settings/user_transfers_a_project_spec.rb +++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb @@ -10,7 +10,7 @@ describe 'Projects > Settings > User transfers a project', :js do sign_in(user) end - def transfer_project(project, group) + def transfer_project(project, group, confirm: true) visit edit_project_path(project) page.within('.js-project-transfer-form') do @@ -21,6 +21,8 @@ describe 'Projects > Settings > User transfers a project', :js do click_button('Transfer project') + return unless confirm + fill_in 'confirm_name_input', with: project.name click_button 'Confirm' @@ -28,6 +30,11 @@ describe 'Projects > Settings > User transfers a project', :js do wait_for_requests end + it 'focuses on the confirmation field' do + transfer_project(project, group, confirm: false) + expect(page).to have_selector '#confirm_name_input:focus' + end + it 'allows transferring a project to a group' do old_path = project_path(project) transfer_project(project, group) diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb index 31b105229be..546619e88ec 100644 --- a/spec/features/projects/show/user_manages_notifications_spec.rb +++ b/spec/features/projects/show/user_manages_notifications_spec.rb @@ -16,4 +16,36 @@ describe 'Projects > Show > User manages notifications', :js do expect(page).to have_content 'On mention' end end + + context 'custom notification settings' do + let(:email_events) do + [ + :new_note, + :new_issue, + :reopen_issue, + :close_issue, + :reassign_issue, + :issue_due, + :new_merge_request, + :push_to_merge_request, + :reopen_merge_request, + :close_merge_request, + :reassign_merge_request, + :merge_merge_request, + :failed_pipeline, + :success_pipeline + ] + end + + it 'shows notification settings checkbox' do + first('.notifications-btn').click + page.find('a[data-notification-level="custom"]').click + + page.within('.custom-notifications-form') do + email_events.each do |event_name| + expect(page).to have_selector("input[name='notification_setting[#{event_name}]']") + end + end + end + end end diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb index 057b49cc68c..d3aa4912099 100644 --- a/spec/features/projects/tree/create_directory_spec.rb +++ b/spec/features/projects/tree/create_directory_spec.rb @@ -22,9 +22,7 @@ describe 'Multi-file editor new directory', :js do end it 'creates directory in current directory' do - find('.add-to-tree').click - - click_link('New directory') + all('.ide-tree-header button').last.click page.within('.modal') do find('.form-control').set('folder name') @@ -32,9 +30,7 @@ describe 'Multi-file editor new directory', :js do click_button('Create directory') end - find('.add-to-tree').click - - click_link('New file') + first('.ide-tree-header button').click page.within('.modal-dialog') do find('.form-control').set('file name') diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb index b324ab01383..f836783cbff 100644 --- a/spec/features/projects/tree/create_file_spec.rb +++ b/spec/features/projects/tree/create_file_spec.rb @@ -22,9 +22,7 @@ describe 'Multi-file editor new file', :js do end it 'creates file in current directory' do - find('.add-to-tree').click - - click_link('New file') + first('.ide-tree-header button').click page.within('.modal') do find('.form-control').set('file name') diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb index 28da0a87f22..dcf7d314f8e 100644 --- a/spec/features/projects/tree/upload_file_spec.rb +++ b/spec/features/projects/tree/upload_file_spec.rb @@ -24,14 +24,10 @@ describe 'Multi-file editor upload file', :js do end it 'uploads text file' do - find('.add-to-tree').click - # make the field visible so capybara can use it execute_script('document.querySelector("#file-upload").classList.remove("hidden")') attach_file('file-upload', txt_file) - find('.add-to-tree').click - expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt') expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline)) end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 00946bccd9a..39b47d99040 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -155,6 +155,12 @@ describe 'Project' do visit edit_project_path(project) end + it 'focuses on the confirmation field' do + click_button 'Remove project' + + expect(page).to have_selector '#confirm_name_input:focus' + end + it 'removes a project' do expect { remove_with_confirm('Remove project', project.path) }.to change { Project.count }.by(-1) expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted." diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index 6061021d3b0..9747b9402a7 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -5,76 +5,12 @@ describe TodosFinder do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, namespace: group) } - let(:issue) { create(:issue, project: project) } - let(:merge_request) { create(:merge_request, source_project: project) } let(:finder) { described_class } before do group.add_developer(user) end - describe '#execute' do - context 'visibility' do - let(:private_group_access) { create(:group, :private) } - let(:private_group_hidden) { create(:group, :private) } - let(:public_project) { create(:project, :public) } - let(:private_project_hidden) { create(:project) } - let(:public_group) { create(:group) } - - let!(:todo1) { create(:todo, user: user, project: project, group: nil) } - let!(:todo2) { create(:todo, user: user, project: public_project, group: nil) } - let!(:todo3) { create(:todo, user: user, project: private_project_hidden, group: nil) } - let!(:todo4) { create(:todo, user: user, project: nil, group: group) } - let!(:todo5) { create(:todo, user: user, project: nil, group: private_group_access) } - let!(:todo6) { create(:todo, user: user, project: nil, group: private_group_hidden) } - let!(:todo7) { create(:todo, user: user, project: nil, group: public_group) } - - before do - private_group_access.add_developer(user) - end - - it 'returns only todos with a target a user has access to' do - todos = finder.new(user).execute - - expect(todos).to match_array([todo1, todo2, todo4, todo5, todo7]) - end - end - - context 'filtering' do - let!(:todo1) { create(:todo, user: user, project: project, target: issue) } - let!(:todo2) { create(:todo, user: user, group: group, target: merge_request) } - - it 'returns correct todos when filtered by a project' do - todos = finder.new(user, { project_id: project.id }).execute - - expect(todos).to match_array([todo1]) - end - - it 'returns correct todos when filtered by a group' do - todos = finder.new(user, { group_id: group.id }).execute - - expect(todos).to match_array([todo1, todo2]) - end - - it 'returns correct todos when filtered by a type' do - todos = finder.new(user, { type: 'Issue' }).execute - - expect(todos).to match_array([todo1]) - end - - context 'with subgroups', :nested_groups do - let(:subgroup) { create(:group, parent: group) } - let!(:todo3) { create(:todo, user: user, group: subgroup, target: issue) } - - it 'returns todos from subgroups when filtered by a group' do - todos = finder.new(user, { group_id: group.id }).execute - - expect(todos).to match_array([todo1, todo2, todo3]) - end - end - end - end - describe '#sort' do context 'by date' do let!(:todo1) { create(:todo, user: user, project: project) } diff --git a/spec/fixtures/aosp_manifest.xml b/spec/fixtures/aosp_manifest.xml new file mode 100644 index 00000000000..cfd0094b735 --- /dev/null +++ b/spec/fixtures/aosp_manifest.xml @@ -0,0 +1,685 @@ +<?xml version="1.0" encoding="UTF-8"?> +<manifest> + + <remote name="aosp" + fetch=".." + review="https://android-review.googlesource.com/" /> + <default revision="master" + remote="aosp" + sync-j="4" /> + + <project path="build/make" name="platform/build" groups="pdk" > + <copyfile src="core/root.mk" dest="Makefile" /> + <linkfile src="CleanSpec.mk" dest="build/CleanSpec.mk" /> + <linkfile src="buildspec.mk.default" dest="build/buildspec.mk.default" /> + <linkfile src="core" dest="build/core" /> + <linkfile src="envsetup.sh" dest="build/envsetup.sh" /> + <linkfile src="target" dest="build/target" /> + <linkfile src="tools" dest="build/tools" /> + </project> + <project path="build/blueprint" name="platform/build/blueprint" groups="pdk,tradefed" /> + <project path="build/kati" name="platform/build/kati" groups="pdk,tradefed" /> + <project path="build/soong" name="platform/build/soong" groups="pdk,tradefed" > + <linkfile src="root.bp" dest="Android.bp" /> + <linkfile src="bootstrap.bash" dest="bootstrap.bash" /> + </project> + <project path="art" name="platform/art" groups="pdk" /> + <project path="bionic" name="platform/bionic" groups="pdk" /> + <project path="bootable/recovery" name="platform/bootable/recovery" groups="pdk" /> + <project path="compatibility/cdd" name="platform/compatibility/cdd" groups="pdk" /> + <project path="cts" name="platform/cts" groups="cts,pdk-cw-fs,pdk-fs" /> + <project path="dalvik" name="platform/dalvik" groups="pdk-cw-fs,pdk-fs" /> + <project path="developers/build" name="platform/developers/build" groups="developers" /> + <project path="developers/demos" name="platform/developers/demos" groups="developers" /> + <project path="developers/samples/android" name="platform/developers/samples/android" groups="developers" /> + <project path="development" name="platform/development" groups="developers,pdk-cw-fs,pdk-fs" /> + <project path="device/asus/fugu" name="device/asus/fugu" groups="device,fugu,broadcom_pdk" /> + <project path="device/asus/fugu-kernel" name="device/asus/fugu-kernel" groups="device,fugu,broadcom_pdk" clone-depth="1" /> + <project path="device/common" name="device/common" groups="pdk-cw-fs,pdk" /> + <project path="device/generic/arm64" name="device/generic/arm64" groups="pdk" /> + <project path="device/generic/armv7-a-neon" name="device/generic/armv7-a-neon" groups="pdk" /> + <project path="device/generic/car" name="device/generic/car" groups="pdk" /> + <project path="device/generic/common" name="device/generic/common" groups="pdk" /> + <project path="device/generic/goldfish" name="device/generic/goldfish" groups="pdk" /> + <project path="device/generic/goldfish-opengl" name="device/generic/goldfish-opengl" groups="pdk" /> + <project path="device/generic/mini-emulator-arm64" name="device/generic/mini-emulator-arm64" groups="pdk" /> + <project path="device/generic/mini-emulator-armv7-a-neon" name="device/generic/mini-emulator-armv7-a-neon" groups="pdk" /> + <project path="device/generic/mini-emulator-x86" name="device/generic/mini-emulator-x86" groups="pdk" /> + <project path="device/generic/mini-emulator-x86_64" name="device/generic/mini-emulator-x86_64" groups="pdk" /> + <project path="device/generic/qemu" name="device/generic/qemu" groups="pdk" /> + <project path="device/generic/uml" name="device/generic/uml" groups="device,pdk" /> + <project path="device/generic/x86" name="device/generic/x86" groups="pdk" /> + <project path="device/generic/x86_64" name="device/generic/x86_64" groups="pdk" /> + <project path="device/google/accessory/arduino" name="device/google/accessory/arduino" groups="device,pdk" /> + <project path="device/google/accessory/demokit" name="device/google/accessory/demokit" groups="device,pdk" /> + <project path="device/google/atv" name="device/google/atv" groups="device,broadcom_pdk,generic_fs,pdk" /> + <project path="device/google/contexthub" name="device/google/contexthub" groups="device,marlin,pdk" /> + <project path="device/google/cuttlefish" name="device/google/cuttlefish" groups="device" /> + <project path="device/google/cuttlefish_common" name="device/google/cuttlefish_common" groups="device" /> + <project path="device/google/cuttlefish_kernel" name="device/google/cuttlefish_kernel" groups="device" clone-depth="1" /> + <project path="device/google/dragon" name="device/google/dragon" groups="device,dragon" /> + <project path="device/google/dragon-kernel" name="device/google/dragon-kernel" groups="device,dragon" clone-depth="1" /> + <project path="device/google/marlin" name="device/google/marlin" groups="device,marlin,pdk" /> + <project path="device/google/marlin-kernel" name="device/google/marlin-kernel" groups="device,marlin,pdk" clone-depth="1" /> + <project path="device/google/muskie" name="device/google/muskie" groups="device,muskie" /> + <project path="device/google/taimen" name="device/google/taimen" groups="device,taimen" /> + <project path="device/google/vrservices" name="device/google/vrservices" groups="pdk" clone-depth="1" /> + <project path="device/google/wahoo" name="device/google/wahoo" groups="device,wahoo" /> + <project path="device/google/wahoo-kernel" name="device/google/wahoo-kernel" groups="device,wahoo" clone-depth="1" /> + <project path="device/huawei/angler" name="device/huawei/angler" groups="device,angler,broadcom_pdk" /> + <project path="device/huawei/angler-kernel" name="device/huawei/angler-kernel" groups="device,angler,broadcom_pdk" clone-depth="1" /> + <project path="device/lge/bullhead" name="device/lge/bullhead" groups="device,bullhead" /> + <project path="device/lge/bullhead-kernel" name="device/lge/bullhead-kernel" groups="device,bullhead" clone-depth="1" /> + <project path="device/linaro/bootloader/arm-trusted-firmware" name="device/linaro/bootloader/arm-trusted-firmware" /> + <project path="device/linaro/bootloader/edk2" name="device/linaro/bootloader/edk2" /> + <project path="device/linaro/bootloader/OpenPlatformPkg" name="device/linaro/bootloader/OpenPlatformPkg" /> + <project path="device/linaro/hikey" name="device/linaro/hikey" groups="device,hikey,pdk" /> + <project path="device/linaro/hikey-kernel" name="device/linaro/hikey-kernel" groups="device,hikey,pdk" clone-depth="1" /> + <project path="device/sample" name="device/sample" groups="pdk" /> + <project path="external/aac" name="platform/external/aac" groups="pdk" /> + <project path="external/abi-compliance-checker" name="platform/external/abi-compliance-checker" groups="pdk" /> + <project path="external/abi-dumper" name="platform/external/abi-dumper" groups="pdk" /> + <project path="external/adt-infra" name="platform/external/adt-infra" groups="adt-infra,notdefault,pdk-fs" /> + <project path="external/android-clat" name="platform/external/android-clat" groups="pdk" /> + <project path="external/androidplot" name="platform/external/androidplot" groups="pdk" /> + <project path="external/annotation-tools" name="platform/external/annotation-tools" groups="pdk" /> + <project path="external/ant-glob" name="platform/external/ant-glob" groups="pdk" /> + <project path="external/antlr" name="platform/external/antlr" groups="pdk" /> + <project path="external/apache-commons-math" name="platform/external/apache-commons-math" groups="pdk" /> + <project path="external/apache-harmony" name="platform/external/apache-harmony" groups="pdk" /> + <project path="external/apache-http" name="platform/external/apache-http" groups="pdk" /> + <project path="external/apache-xml" name="platform/external/apache-xml" groups="pdk" /> + <project path="external/archive-patcher" name="platform/external/archive-patcher" groups="pdk" /> + <project path="external/arm-neon-tests" name="platform/external/arm-neon-tests" groups="vendor" /> + <project path="external/autotest" name="platform/external/autotest" groups="pdk-fs" /> + <project path="external/avb" name="platform/external/avb" groups="pdk" /> + <project path="external/bart" name="platform/external/bart" groups="pdk" /> + <project path="external/blktrace" name="platform/external/blktrace" groups="pdk" /> + <project path="external/boringssl" name="platform/external/boringssl" groups="pdk" /> + <project path="external/bouncycastle" name="platform/external/bouncycastle" groups="pdk" /> + <project path="external/brotli" name="platform/external/brotli" groups="pdk" /> + <project path="external/bsdiff" name="platform/external/bsdiff" groups="pdk" /> + <project path="external/bzip2" name="platform/external/bzip2" groups="pdk" /> + <project path="external/caliper" name="platform/external/caliper" groups="pdk" /> + <project path="external/cblas" name="platform/external/cblas" groups="pdk" /> + <project path="external/chromium-libpac" name="platform/external/chromium-libpac" groups="pdk" /> + <project path="external/chromium-trace" name="platform/external/chromium-trace" groups="pdk" /> + <project path="external/chromium-webview" name="platform/external/chromium-webview" groups="pdk" clone-depth="1" /> + <project path="external/clang" name="platform/external/clang" groups="pdk" /> + <project path="external/cldr" name="platform/external/cldr" groups="pdk" /> + <project path="external/cmockery" name="platform/external/cmockery" groups="pdk" /> + <project path="external/cn-cbor" name="platform/external/cn-cbor" groups="pdk" /> + <project path="external/compiler-rt" name="platform/external/compiler-rt" groups="pdk" /> + <project path="external/conscrypt" name="platform/external/conscrypt" groups="pdk" /> + <project path="external/crcalc" name="platform/external/crcalc" groups="pdk" /> + <project path="external/cros/system_api" name="platform/external/cros/system_api" groups="pdk" /> + <project path="external/curl" name="platform/external/curl" groups="pdk" /> + <project path="external/dagger2" name="platform/external/dagger2" groups="pdk" /> + <project path="external/deqp" name="platform/external/deqp" groups="pdk-fs" /> + <project path="external/desugar" name="platform/external/desugar" groups="pdk" /> + <project path="external/devlib" name="platform/external/devlib" groups="pdk" /> + <project path="external/dexmaker" name="platform/external/dexmaker" groups="pdk" /> + <project path="external/dhcpcd-6.8.2" name="platform/external/dhcpcd-6.8.2" groups="pdk" /> + <project path="external/dlmalloc" name="platform/external/dlmalloc" groups="pdk" /> + <project path="external/dng_sdk" name="platform/external/dng_sdk" groups="pdk" /> + <project path="external/dnsmasq" name="platform/external/dnsmasq" groups="pdk" /> + <project path="external/doclava" name="platform/external/doclava" groups="pdk" /> + <project path="external/dokka" name="platform/external/dokka" groups="pdk" /> + <project path="external/drm_hwcomposer" name="platform/external/drm_hwcomposer" groups="drm_hwcomposer,pdk-fs" /> + <project path="external/droiddriver" name="platform/external/droiddriver" groups="pdk" /> + <project path="external/drrickorang" name="platform/external/drrickorang" groups="pdk" /> + <project path="external/dtc" name="platform/external/dtc" groups="pdk"/> + <project path="external/e2fsprogs" name="platform/external/e2fsprogs" groups="pdk" /> + <project path="external/easymock" name="platform/external/easymock" groups="pdk" /> + <project path="external/eigen" name="platform/external/eigen" groups="pdk" /> + <project path="external/elfutils" name="platform/external/elfutils" groups="pdk" /> + <project path="external/emma" name="platform/external/emma" groups="pdk" /> + <project path="external/error_prone" name="platform/external/error_prone" groups="pdk" /> + <project path="external/esd" name="platform/external/esd" groups="pdk" /> + <project path="external/expat" name="platform/external/expat" groups="pdk" /> + <project path="external/eyes-free" name="platform/external/eyes-free" groups="pdk" /> + <project path="external/f2fs-tools" name="platform/external/f2fs-tools" groups="pdk" /> + <project path="external/fdlibm" name="platform/external/fdlibm" groups="pdk" /> + <project path="external/fec" name="platform/external/fec" groups="pdk" /> + <project path="external/flac" name="platform/external/flac" groups="pdk" /> + <project path="external/flatbuffers" name="platform/external/flatbuffers" groups="pdk" /> + <project path="external/fonttools" name="platform/external/fonttools" groups="pdk" /> + <project path="external/freetype" name="platform/external/freetype" groups="pdk" /> + <project path="external/fsck_msdos" name="platform/external/fsck_msdos" groups="pdk" /> + <project path="external/gemmlowp" name="platform/external/gemmlowp" groups="pdk" /> + <project path="external/gflags" name="platform/external/gflags" groups="pdk" /> + <project path="external/giflib" name="platform/external/giflib" groups="pdk,qcom_msm8x26" /> + <project path="external/glide" name="platform/external/glide" groups="pdk" /> + <project path="external/golang-protobuf" name="platform/external/golang-protobuf" groups="pdk" /> + <project path="external/google-benchmark" name="platform/external/google-benchmark" groups="pdk" /> + <project path="external/google-breakpad" name="platform/external/google-breakpad" groups="pdk-fs" /> + <project path="external/google-fonts/carrois-gothic-sc" name="platform/external/google-fonts/carrois-gothic-sc" groups="pdk" /> + <project path="external/google-fonts/coming-soon" name="platform/external/google-fonts/coming-soon" groups="pdk" /> + <project path="external/google-fonts/cutive-mono" name="platform/external/google-fonts/cutive-mono" groups="pdk" /> + <project path="external/google-fonts/dancing-script" name="platform/external/google-fonts/dancing-script" groups="pdk" /> + <project path="external/google-styleguide" name="platform/external/google-styleguide" groups="pdk" /> + <project path="external/google-tv-pairing-protocol" name="platform/external/google-tv-pairing-protocol" groups="pdk" /> + <project path="external/googletest" name="platform/external/googletest" groups="pdk" /> + <project path="external/gptfdisk" name="platform/external/gptfdisk" groups="pdk" /> + <project path="external/guava" name="platform/external/guava" groups="pdk" /> + <project path="external/guice" name="platform/external/guice" groups="pdk" /> + <project path="external/hamcrest" name="platform/external/hamcrest" groups="pdk" /> + <project path="external/harfbuzz_ng" name="platform/external/harfbuzz_ng" groups="pdk,qcom_msm8x26" /> + <project path="external/hyphenation-patterns" name="platform/external/hyphenation-patterns" groups="pdk" /> + <project path="external/icu" name="platform/external/icu" groups="pdk" /> + <project path="external/ImageMagick" name="platform/external/ImageMagick" groups="pdk" /> + <project path="external/ims" name="platform/external/ims" groups="pdk" /> + <project path="external/iproute2" name="platform/external/iproute2" groups="pdk" /> + <project path="external/ipsec-tools" name="platform/external/ipsec-tools" groups="pdk" /> + <project path="external/iptables" name="platform/external/iptables" groups="pdk" /> + <project path="external/iputils" name="platform/external/iputils" groups="pdk" /> + <project path="external/iw" name="platform/external/iw" groups="pdk" /> + <project path="external/jacoco" name="platform/external/jacoco" groups="pdk" /> + <project path="external/jarjar" name="platform/external/jarjar" groups="pdk" /> + <project path="external/javaparser" name="platform/external/javaparser" groups="pdk" /> + <project path="external/javasqlite" name="platform/external/javasqlite" groups="pdk" /> + <project path="external/javassist" name="platform/external/javassist" groups="pdk" /> + <project path="external/jcommander" name="platform/external/jcommander" groups="pdk" /> + <project path="external/jdiff" name="platform/external/jdiff" groups="pdk" /> + <project path="external/jemalloc" name="platform/external/jemalloc" groups="pdk" /> + <project path="external/jline" name="platform/external/jline" groups="pdk,tradefed,pdk-fs" /> + <project path="external/jmdns" name="platform/external/jmdns" groups="pdk" /> + <project path="external/jsilver" name="platform/external/jsilver" groups="pdk" /> + <project path="external/jsmn" name="platform/external/jsmn" groups="pdk" /> + <project path="external/jsoncpp" name="platform/external/jsoncpp" groups="pdk" /> + <project path="external/jsr305" name="platform/external/jsr305" groups="pdk" /> + <project path="external/jsr330" name="platform/external/jsr330" groups="pdk" /> + <project path="external/junit" name="platform/external/junit" groups="pdk" /> + <project path="external/junit-params" name="platform/external/junit-params" groups="pdk" /> + <project path="external/kernel-headers" name="platform/external/kernel-headers" groups="pdk" /> + <project path="external/kmod" name="platform/external/kmod" groups="pdk" /> + <project path="external/kotlinc" name="platform/external/kotlinc" groups="pdk" /> + <project path="external/ksoap2" name="platform/external/ksoap2" groups="pdk" /> + <project path="external/libavc" name="platform/external/libavc" groups="pdk" /> + <project path="external/libbackup" name="platform/external/libbackup" groups="pdk" /> + <project path="external/libbrillo" name="platform/external/libbrillo" groups="pdk" /> + <project path="external/libcap" name="platform/external/libcap" groups="pdk" /> + <project path="external/libcap-ng" name="platform/external/libcap-ng" groups="pdk" /> + <project path="external/libchrome" name="platform/external/libchrome" groups="pdk" /> + <project path="external/libconstrainedcrypto" name="platform/external/libconstrainedcrypto" groups="pdk" /> + <project path="external/libcups" name="platform/external/libcups" groups="pdk-cw-fs,pdk-fs" /> + <project path="external/libcxx" name="platform/external/libcxx" groups="pdk" /> + <project path="external/libcxxabi" name="platform/external/libcxxabi" groups="pdk" /> + <project path="external/libdaemon" name="platform/external/libdaemon" groups="pdk" /> + <project path="external/libdivsufsort" name="platform/external/libdivsufsort" groups="pdk" /> + <project path="external/libdrm" name="platform/external/libdrm" groups="pdk" /> + <project path="external/libedit" name="platform/external/libedit" groups="pdk" /> + <project path="external/libese" name="platform/external/libese" groups="pdk" /> + <project path="external/libevent" name="platform/external/libevent" groups="pdk" /> + <project path="external/libexif" name="platform/external/libexif" groups="pdk" /> + <project path="external/libgsm" name="platform/external/libgsm" groups="pdk" /> + <project path="external/libhevc" name="platform/external/libhevc" groups="pdk" /> + <project path="external/libjpeg-turbo" name="platform/external/libjpeg-turbo" groups="pdk" /> + <project path="external/libldac" name="platform/external/libldac" groups="pdk" /> + <project path="external/libmicrohttpd" name="platform/external/libmicrohttpd" groups="pdk" /> + <project path="external/libmpeg2" name="platform/external/libmpeg2" groups="pdk" /> + <project path="external/libmtp" name="platform/external/libmtp" groups="pdk" /> + <project path="external/libnetfilter_conntrack" name="platform/external/libnetfilter_conntrack" groups="pdk" /> + <project path="external/libnfnetlink" name="platform/external/libnfnetlink" groups="pdk" /> + <project path="external/libnl" name="platform/external/libnl" groups="pdk" /> + <project path="external/libogg" name="platform/external/libogg" groups="pdk" /> + <project path="external/libopus" name="platform/external/libopus" groups="pdk" /> + <project path="external/libpcap" name="platform/external/libpcap" groups="pdk" /> + <project path="external/libphonenumber" name="platform/external/libphonenumber" groups="pdk" /> + <project path="external/libpng" name="platform/external/libpng" groups="pdk" /> + <project path="external/libtextclassifier" name="platform/external/libtextclassifier" groups="pdk" /> + <project path="external/libunwind" name="platform/external/libunwind" groups="pdk" /> + <project path="external/libunwind_llvm" name="platform/external/libunwind_llvm" groups="pdk" /> + <project path="external/libusb" name="platform/external/libusb" groups="pdk" /> + <project path="external/libusb-compat" name="platform/external/libusb-compat" groups="pdk" /> + <project path="external/libvncserver" name="platform/external/libvncserver" groups="pdk" /> + <project path="external/libvorbis" name="platform/external/libvorbis" groups="pdk" /> + <project path="external/libvpx" name="platform/external/libvpx" groups="pdk" /> + <project path="external/libvterm" name="platform/external/libvterm" groups="pdk" /> + <project path="external/libxcam" name="platform/external/libxcam" groups="pdk" /> + <project path="external/libxml2" name="platform/external/libxml2" groups="pdk,libxml2" /> + <project path="external/libyuv" name="platform/external/libyuv" groups="pdk,libyuv" /> + <project path="external/linux-kselftest" name="platform/external/linux-kselftest" groups="vts,pdk" /> + <project path="external/lisa" name="platform/external/lisa" groups="pdk" /> + <project path="external/llvm" name="platform/external/llvm" groups="pdk" /> + <project path="external/lmfit" name="platform/external/lmfit" groups="pdk" /> + <project path="external/ltp" name="platform/external/ltp" groups="vts,pdk" /> + <project path="external/lz4" name="platform/external/lz4" groups="pdk" /> + <project path="external/lzma" name="platform/external/lzma" groups="pdk" /> + <project path="external/markdown" name="platform/external/markdown" groups="pdk" /> + <project path="external/mdnsresponder" name="platform/external/mdnsresponder" groups="pdk" /> + <project path="external/mesa3d" name="platform/external/mesa3d" groups="pdk-cw-fs,pdk-fs" /> + <project path="external/Microsoft-GSL" name="platform/external/Microsoft-GSL" groups="pdk" /> + <project path="external/minijail" name="platform/external/minijail" groups="pdk" /> + <project path="external/mksh" name="platform/external/mksh" groups="pdk" /> + <project path="external/mmc-utils" name="platform/external/mmc-utils" groups="pdk" /> + <project path="external/mockftpserver" name="platform/external/mockftpserver" groups="pdk" /> + <project path="external/mockito" name="platform/external/mockito" groups="pdk" /> + <project path="external/mockwebserver" name="platform/external/mockwebserver" groups="pdk" /> + <project path="external/modp_b64" name="platform/external/modp_b64" groups="pdk" /> + <project path="external/mp4parser" name="platform/external/mp4parser" groups="pdk" /> + <project path="external/mtpd" name="platform/external/mtpd" groups="pdk" /> + <project path="external/nanohttpd" name="platform/external/nanohttpd" groups="pdk" /> + <project path="external/nanopb-c" name="platform/external/nanopb-c" groups="pdk" /> + <project path="external/naver-fonts" name="platform/external/naver-fonts" groups="pdk" /> + <project path="external/neven" name="platform/external/neven" groups="pdk" /> + <project path="external/nfacct" name="platform/external/nfacct" groups="pdk" /> + <project path="external/nist-pkits" name="platform/external/nist-pkits" groups="pdk" /> + <project path="external/nist-sip" name="platform/external/nist-sip" groups="pdk" /> + <project path="external/noto-fonts" name="platform/external/noto-fonts" groups="pdk" /> + <project path="external/oauth" name="platform/external/oauth" groups="pdk" /> + <project path="external/objenesis" name="platform/external/objenesis" groups="pdk" /> + <project path="external/oj-libjdwp" name="platform/external/oj-libjdwp" groups="pdk" /> + <project path="external/okhttp" name="platform/external/okhttp" groups="pdk" /> + <project path="external/one-true-awk" name="platform/external/one-true-awk" groups="pdk" /> + <project path="external/opencv" name="platform/external/opencv" groups="pdk-cw-fs,pdk-fs" /> + <project path="external/owasp/sanitizer" name="platform/external/owasp/sanitizer" groups="pdk" /> + <project path="external/parameter-framework" name="platform/external/parameter-framework" groups="pdk" /> + <project path="external/pcre" name="platform/external/pcre" groups="pdk" /> + <project path="external/pdfium" name="platform/external/pdfium" groups="pdk" /> + <project path="external/perf_data_converter" name="platform/external/perf_data_converter" groups="pdk" /> + <project path="external/perfetto" name="platform/external/perfetto" groups="pdk" /> + <project path="external/piex" name="platform/external/piex" groups="pdk" /> + <project path="external/ply" name="platform/external/ply" groups="pdk" /> + <project path="external/ppp" name="platform/external/ppp" groups="pdk" /> + <project path="external/proguard" name="platform/external/proguard" groups="pdk" /> + <project path="external/protobuf" name="platform/external/protobuf" groups="pdk" /> + <project path="external/puffin" name="platform/external/puffin" groups="pdk" /> + <project path="external/python/appdirs" name="platform/external/python/appdirs" groups="vts,pdk" /> + <project path="external/python/cachetools" name="platform/external/python/cachetools" groups="vts,pdk" /> + <project path="external/python/cpython2" name="platform/external/python/cpython2" groups="pdk" /> + <project path="external/python/cpython3" name="platform/external/python/cpython3" groups="pdk" /> + <project path="external/python/dateutil" name="platform/external/python/dateutil" groups="pdk" /> + <project path="external/python/dill" name="platform/external/python/dill" groups="vts,pdk" /> + <project path="external/python/enum" name="platform/external/python/enum" groups="vts,pdk" /> + <project path="external/python/enum34" name="platform/external/python/enum34" groups="vts,pdk" /> + <project path="external/python/future" name="platform/external/python/future" groups="vts,pdk" /> + <project path="external/python/futures" name="platform/external/python/futures" groups="vts,pdk" /> + <project path="external/python/gapic-google-cloud-pubsub-v1" name="platform/external/python/gapic-google-cloud-pubsub-v1" groups="vts,pdk" /> + <project path="external/python/google-api-python-client" name="platform/external/python/google-api-python-client" groups="vts,pdk" /> + <project path="external/python/google-auth" name="platform/external/python/google-auth" groups="vts,pdk" /> + <project path="external/python/google-auth-httplib2" name="platform/external/python/google-auth-httplib2" groups="vts,pdk" /> + <project path="external/python/google-cloud-core" name="platform/external/python/google-cloud-core" groups="vts,pdk" /> + <project path="external/python/google-cloud-pubsub" name="platform/external/python/google-cloud-pubsub" groups="vts,pdk" /> + <project path="external/python/google-gax" name="platform/external/python/google-gax" groups="vts,pdk" /> + <project path="external/python/googleapis" name="platform/external/python/googleapis" groups="vts,pdk" /> + <project path="external/python/grpc-google-iam-v1" name="platform/external/python/grpc-google-iam-v1" groups="vts,pdk" /> + <project path="external/python/grpcio" name="platform/external/python/grpcio" groups="vts,pdk" /> + <project path="external/python/httplib2" name="platform/external/python/httplib2" groups="vts,pdk" /> + <project path="external/python/matplotlib" name="platform/external/python/matplotlib" groups="vts,pdk" /> + <project path="external/python/numpy" name="platform/external/python/numpy" groups="vts,pdk" /> + <project path="external/python/oauth2client" name="platform/external/python/oauth2client" groups="vts,pdk" /> + <project path="external/python/olefile" name="platform/external/python/olefile" groups="vts,pdk" /> + <project path="external/python/packaging" name="platform/external/python/packaging" groups="vts,pdk" /> + <project path="external/python/parse" name="platform/external/python/parse" groups="vts,pdk" /> + <project path="external/python/Pillow" name="platform/external/python/Pillow" groups="vts,pdk" /> + <project path="external/python/ply" name="platform/external/python/ply" groups="vts,pdk" /> + <project path="external/python/proto-google-cloud-pubsub-v1" name="platform/external/python/proto-google-cloud-pubsub-v1" groups="vts,pdk" /> + <project path="external/python/protobuf" name="platform/external/python/protobuf" groups="vts,pdk" /> + <project path="external/python/pyasn1" name="platform/external/python/pyasn1" groups="vts,pdk" /> + <project path="external/python/pyasn1-modules" name="platform/external/python/pyasn1-modules" groups="vts,pdk" /> + <project path="external/python/pyparsing" name="platform/external/python/pyparsing" groups="vts,pdk" /> + <project path="external/python/requests" name="platform/external/python/requests" groups="vts,pdk" /> + <project path="external/python/rsa" name="platform/external/python/rsa" groups="vts,pdk" /> + <project path="external/python/scipy" name="platform/external/python/scipy" groups="vts,pdk" /> + <project path="external/python/setuptools" name="platform/external/python/setuptools" groups="vts,pdk" /> + <project path="external/python/six" name="platform/external/python/six" groups="vts,pdk" /> + <project path="external/python/uritemplates" name="platform/external/python/uritemplates" groups="vts,pdk" /> + <project path="external/replicaisland" name="platform/external/replicaisland" groups="pdk" /> + <project path="external/rmi4utils" name="platform/external/rmi4utils" groups="pdk" /> + <project path="external/robolectric" name="platform/external/robolectric" groups="pdk-cw-fs,pdk-fs" /> + <project path="external/roboto-fonts" name="platform/external/roboto-fonts" groups="pdk" /> + <project path="external/rootdev" name="platform/external/rootdev" groups="pdk" /> + <project path="external/safe-iop" name="platform/external/safe-iop" groups="pdk" /> + <project path="external/scapy" name="platform/external/scapy" groups="pdk-fs" /> + <project path="external/scrypt" name="platform/external/scrypt" groups="pdk" /> + <project path="external/seccomp-tests" name="platform/external/seccomp-tests" groups="pdk" /> + <project path="external/selinux" name="platform/external/selinux" groups="pdk" /> + <project path="external/sfntly" name="platform/external/sfntly" groups="pdk,qcom_msm8x26" /> + <project path="external/shaderc/spirv-headers" name="platform/external/shaderc/spirv-headers" groups="pdk" /> + <project path="external/shflags" name="platform/external/shflags" groups="pdk" /> + <project path="external/skia" name="platform/external/skia" groups="pdk,qcom_msm8x26" /> + <project path="external/sl4a" name="platform/external/sl4a" groups="pdk" /> + <project path="external/slf4j" name="platform/external/slf4j" groups="pdk" /> + <project path="external/smali" name="platform/external/smali" groups="pdk" /> + <project path="external/snakeyaml" name="platform/external/snakeyaml" groups="pdk" /> + <project path="external/sonic" name="platform/external/sonic" groups="pdk" /> + <project path="external/sonivox" name="platform/external/sonivox" groups="pdk" /> + <project path="external/speex" name="platform/external/speex" groups="pdk" /> + <project path="external/spirv-llvm" name="platform/external/spirv-llvm" groups="pdk" /> + <project path="external/sqlite" name="platform/external/sqlite" groups="pdk" /> + <project path="external/squashfs-tools" name="platform/external/squashfs-tools" groups="pdk" /> + <project path="external/strace" name="platform/external/strace" groups="pdk" /> + <project path="external/stressapptest" name="platform/external/stressapptest" groups="pdk" /> + <project path="external/subsampling-scale-image-view" name="platform/external/subsampling-scale-image-view" clone-depth="1" /> + <project path="external/swiftshader" name="platform/external/swiftshader" groups="pdk" /> + <project path="external/syslinux" name="platform/external/syslinux" groups="pdk" /> + <project path="external/tagsoup" name="platform/external/tagsoup" groups="pdk" /> + <project path="external/tcpdump" name="platform/external/tcpdump" groups="pdk" /> + <project path="external/tensorflow" name="platform/external/tensorflow" groups="pdk" /> + <project path="external/testng" name="platform/external/testng" groups="pdk" /> + <project path="external/tinyalsa" name="platform/external/tinyalsa" groups="pdk" /> + <project path="external/tinycompress" name="platform/external/tinycompress" groups="pdk" /> + <project path="external/tinyxml" name="platform/external/tinyxml" groups="pdk" /> + <project path="external/tinyxml2" name="platform/external/tinyxml2" groups="pdk" /> + <project path="external/toolchain-utils" name="platform/external/toolchain-utils" /> + <project path="external/toybox" name="platform/external/toybox" groups="pdk" /> + <project path="external/tpm2" name="platform/external/tpm2" groups="pdk" /> + <project path="external/trappy" name="platform/external/trappy" groups="pdk" /> + <project path="external/tremolo" name="platform/external/tremolo" groups="pdk" /> + <project path="external/turbine" name="platform/external/turbine" groups="pdk" /> + <project path="external/unicode" name="platform/external/unicode" groups="pdk" /> + <project path="external/universal-tween-engine" name="platform/external/universal-tween-engine" /> + <project path="external/v4l2_codec2" name="platform/external/v4l2_codec2" groups="pdk" /> + <project path="external/v8" name="platform/external/v8" groups="pdk" /> + <project path="external/valgrind" name="platform/external/valgrind" groups="pdk" /> + <project path="external/vboot_reference" name="platform/external/vboot_reference" groups="vboot,pdk-fs" /> + <project path="external/vixl" name="platform/external/vixl" groups="pdk" /> + <project path="external/vogar" name="platform/external/vogar" groups="pdk" /> + <project path="external/volley" name="platform/external/volley" groups="pdk" /> + <project path="external/vulkan-validation-layers" name="platform/external/vulkan-validation-layers" groups="pdk" /> + <project path="external/walt" name="platform/external/walt" groups="pdk" /> + <project path="external/webp" name="platform/external/webp" groups="pdk,qcom_msm8x26" /> + <project path="external/webrtc" name="platform/external/webrtc" groups="pdk" /> + <project path="external/webview_support_interfaces" name="platform/external/webview_support_interfaces" groups="pdk" /> + <project path="external/wpa_supplicant_8" name="platform/external/wpa_supplicant_8" groups="pdk" /> + <project path="external/wycheproof" name="platform/external/wycheproof" groups="pdk" /> + <project path="external/x264" name="platform/external/x264" groups="pdk" /> + <project path="external/xmlrpcpp" name="platform/external/xmlrpcpp" groups="pdk" /> + <project path="external/xmp_toolkit" name="platform/external/xmp_toolkit" groups="pdk" /> + <project path="external/xz-embedded" name="platform/external/xz-embedded" groups="pdk" /> + <project path="external/zlib" name="platform/external/zlib" groups="pdk" /> + <project path="external/zopfli" name="platform/external/zopfli" groups="pdk" /> + <project path="external/zxing" name="platform/external/zxing" groups="pdk" /> + <project path="frameworks/av" name="platform/frameworks/av" groups="pdk" /> + <project path="frameworks/base" name="platform/frameworks/base" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/compile/libbcc" name="platform/frameworks/compile/libbcc" groups="pdk" /> + <project path="frameworks/compile/mclinker" name="platform/frameworks/compile/mclinker" groups="pdk" /> + <project path="frameworks/compile/slang" name="platform/frameworks/compile/slang" groups="pdk" /> + <project path="frameworks/data-binding" name="platform/frameworks/data-binding" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/ex" name="platform/frameworks/ex" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/hardware/interfaces" name="platform/frameworks/hardware/interfaces" groups="pdk" /> + <project path="frameworks/layoutlib" name="platform/frameworks/layoutlib" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/minikin" name="platform/frameworks/minikin" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/ml" name="platform/frameworks/ml" groups="pdk" /> + <project path="frameworks/multidex" name="platform/frameworks/multidex" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/native" name="platform/frameworks/native" groups="pdk" /> + <project path="frameworks/opt/bitmap" name="platform/frameworks/opt/bitmap" groups="pdk-fs" /> + <project path="frameworks/opt/calendar" name="platform/frameworks/opt/calendar" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/car/services" name="platform/frameworks/opt/car/services" groups="pdk-fs" /> + <project path="frameworks/opt/chips" name="platform/frameworks/opt/chips" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/colorpicker" name="platform/frameworks/opt/colorpicker" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/datetimepicker" name="platform/frameworks/opt/datetimepicker" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/inputmethodcommon" name="platform/frameworks/opt/inputmethodcommon" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/net/ethernet" name="platform/frameworks/opt/net/ethernet" groups="pdk-fs" /> + <project path="frameworks/opt/net/ims" name="platform/frameworks/opt/net/ims" groups="frameworks_ims,pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/net/lowpan" name="platform/frameworks/opt/net/lowpan" groups="pdk-fs" /> + <project path="frameworks/opt/net/voip" name="platform/frameworks/opt/net/voip" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/net/wifi" name="platform/frameworks/opt/net/wifi" groups="pdk" /> + <project path="frameworks/opt/photoviewer" name="platform/frameworks/opt/photoviewer" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/setupwizard" name="platform/frameworks/opt/setupwizard" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/telephony" name="platform/frameworks/opt/telephony" groups="pdk" /> + <project path="frameworks/opt/timezonepicker" name="platform/frameworks/opt/timezonepicker" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/opt/vcard" name="platform/frameworks/opt/vcard" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/rs" name="platform/frameworks/rs" groups="pdk" /> + <project path="frameworks/support" name="platform/frameworks/support" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/webview" name="platform/frameworks/webview" groups="pdk-cw-fs,pdk-fs" /> + <project path="frameworks/wilhelm" name="platform/frameworks/wilhelm" groups="pdk-cw-fs,pdk-fs" /> + <project path="hardware/akm" name="platform/hardware/akm" groups="pdk" /> + <project path="hardware/broadcom/libbt" name="platform/hardware/broadcom/libbt" groups="pdk" /> + <project path="hardware/broadcom/wlan" name="platform/hardware/broadcom/wlan" groups="pdk,broadcom_wlan" /> + <project path="hardware/google/apf" name="platform/hardware/google/apf" groups="pdk" /> + <project path="hardware/google/easel" name="platform/hardware/google/easel" groups="pdk,easel" /> + <project path="hardware/google/interfaces" name="platform/hardware/google/interfaces" groups="pdk" /> + <project path="hardware/intel/audio_media" name="platform/hardware/intel/audio_media" groups="intel,pdk" /> + <project path="hardware/intel/bootstub" name="platform/hardware/intel/bootstub" groups="intel,pdk" /> + <project path="hardware/intel/common/libmix" name="platform/hardware/intel/common/libmix" groups="intel,pdk" /> + <project path="hardware/intel/common/libstagefrighthw" name="platform/hardware/intel/common/libstagefrighthw" groups="intel,pdk" /> + <project path="hardware/intel/common/libva" name="platform/hardware/intel/common/libva" groups="intel,pdk" /> + <project path="hardware/intel/common/libwsbm" name="platform/hardware/intel/common/libwsbm" groups="intel,pdk" /> + <project path="hardware/intel/common/omx-components" name="platform/hardware/intel/common/omx-components" groups="intel,pdk" /> + <project path="hardware/intel/common/utils" name="platform/hardware/intel/common/utils" groups="intel,pdk" /> + <project path="hardware/intel/common/wrs_omxil_core" name="platform/hardware/intel/common/wrs_omxil_core" groups="intel,pdk" /> + <project path="hardware/intel/img/hwcomposer" name="platform/hardware/intel/img/hwcomposer" groups="intel,pdk" /> + <project path="hardware/intel/img/psb_headers" name="platform/hardware/intel/img/psb_headers" groups="intel,pdk" /> + <project path="hardware/intel/img/psb_video" name="platform/hardware/intel/img/psb_video" groups="intel,pdk" /> + <project path="hardware/interfaces" name="platform/hardware/interfaces" groups="pdk" /> + <project path="hardware/invensense" name="platform/hardware/invensense" groups="invensense,pdk" /> + <project path="hardware/libhardware" name="platform/hardware/libhardware" groups="pdk" /> + <project path="hardware/libhardware_legacy" name="platform/hardware/libhardware_legacy" groups="pdk" /> + <project path="hardware/marvell/bt" name="platform/hardware/marvell/bt" groups="marvell_bt,pdk" /> + <project path="hardware/nxp/nfc" name="platform/hardware/nxp/nfc" groups="pdk" /> + <project path="hardware/nxp/secure_element" name="platform/hardware/nxp/secure_element" groups="pdk" /> + <project path="hardware/qcom/audio" name="platform/hardware/qcom/audio" groups="qcom,qcom_audio,pdk" /> + <project path="hardware/qcom/bootctrl" name="platform/hardware/qcom/bootctrl" groups="pdk" /> + <project path="hardware/qcom/bt" name="platform/hardware/qcom/bt" groups="qcom,pdk" /> + <project path="hardware/qcom/camera" name="platform/hardware/qcom/camera" groups="qcom_camera,pdk" /> + <project path="hardware/qcom/data/ipacfg-mgr" name="platform/hardware/qcom/data/ipacfg-mgr" groups="qcom,pdk" /> + <project path="hardware/qcom/display" name="platform/hardware/qcom/display" groups="pdk,qcom,qcom_display" /> + <project path="hardware/qcom/gps" name="platform/hardware/qcom/gps" groups="qcom,qcom_gps,pdk" /> + <project path="hardware/qcom/keymaster" name="platform/hardware/qcom/keymaster" groups="qcom,qcom_keymaster,pdk" /> + <project path="hardware/qcom/media" name="platform/hardware/qcom/media" groups="qcom,pdk" /> + <project path="hardware/qcom/msm8960" name="platform/hardware/qcom/msm8960" groups="qcom_msm8960,pdk" /> + <project path="hardware/qcom/msm8994" name="platform/hardware/qcom/msm8994" groups="qcom_msm8994,pdk" /> + <project path="hardware/qcom/msm8996" name="platform/hardware/qcom/msm8996" groups="qcom_msm8996,pdk" /> + <project path="hardware/qcom/msm8998" name="platform/hardware/qcom/msm8998" groups="qcom_msm8998,pdk" /> + <project path="hardware/qcom/msm8x09" name="platform/hardware/qcom/msm8x09" groups="qcom_msm8x09" /> + <project path="hardware/qcom/msm8x26" name="platform/hardware/qcom/msm8x26" groups="qcom_msm8x26,pdk" /> + <project path="hardware/qcom/msm8x27" name="platform/hardware/qcom/msm8x27" groups="qcom_msm8x27,pdk" /> + <project path="hardware/qcom/msm8x84" name="platform/hardware/qcom/msm8x84" groups="qcom_msm8x84,pdk" /> + <project path="hardware/qcom/neuralnetworks/hvxservice" name="platform/hardware/qcom/neuralnetworks/hvxservice" groups="wahoo" /> + <project path="hardware/qcom/power" name="platform/hardware/qcom/power" groups="qcom,pdk" /> + <project path="hardware/qcom/wlan" name="platform/hardware/qcom/wlan" groups="qcom_wlan,pdk" /> + <project path="hardware/ril" name="platform/hardware/ril" groups="pdk" /> + <project path="hardware/st/nfc" name="platform/hardware/st/nfc" groups="pdk" /> + <project path="kernel/configs" name="kernel/configs" groups="vts,pdk" /> + <project path="kernel/tests" name="kernel/tests" /> + <project path="libcore" name="platform/libcore" groups="pdk" /> + <project path="libnativehelper" name="platform/libnativehelper" groups="pdk" /> + <project path="packages/apps/BasicSmsReceiver" name="platform/packages/apps/BasicSmsReceiver" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/Bluetooth" name="platform/packages/apps/Bluetooth" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/Browser2" name="platform/packages/apps/Browser2" groups="pdk-fs" /> + <project path="packages/apps/Calendar" name="platform/packages/apps/Calendar" groups="pdk-fs" /> + <project path="packages/apps/Camera2" name="platform/packages/apps/Camera2" groups="pdk-fs" /> + <project path="packages/apps/Car/Dialer" name="platform/packages/apps/Car/Dialer" groups="pdk-fs" /> + <project path="packages/apps/Car/Hvac" name="platform/packages/apps/Car/Hvac" groups="pdk-fs" /> + <project path="packages/apps/Car/LatinIME" name="platform/packages/apps/Car/LatinIME" groups="pdk-fs" /> + <project path="packages/apps/Car/Launcher" name="platform/packages/apps/Car/Launcher" groups="pdk-fs" /> + <project path="packages/apps/Car/LensPicker" name="platform/packages/apps/Car/LensPicker" groups="pdk-fs" /> + <project path="packages/apps/Car/libs" name="platform/packages/apps/Car/libs" groups="pdk-fs" /> + <project path="packages/apps/Car/LocalMediaPlayer" name="platform/packages/apps/Car/LocalMediaPlayer" groups="pdk-fs" /> + <project path="packages/apps/Car/Media" name="platform/packages/apps/Car/Media" groups="pdk-fs" /> + <project path="packages/apps/Car/Messenger" name="platform/packages/apps/Car/Messenger" groups="pdk-fs" /> + <project path="packages/apps/Car/Overview" name="platform/packages/apps/Car/Overview" groups="pdk-fs" /> + <project path="packages/apps/Car/Radio" name="platform/packages/apps/Car/Radio" groups="pdk-fs" /> + <project path="packages/apps/Car/Settings" name="platform/packages/apps/Car/Settings" groups="pdk-fs" /> + <project path="packages/apps/Car/Stream" name="platform/packages/apps/Car/Stream" groups="pdk-fs" /> + <project path="packages/apps/Car/SystemUpdater" name="platform/packages/apps/Car/SystemUpdater" groups="pdk-fs" /> + <project path="packages/apps/CarrierConfig" name="platform/packages/apps/CarrierConfig" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/CellBroadcastReceiver" name="platform/packages/apps/CellBroadcastReceiver" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/CertInstaller" name="platform/packages/apps/CertInstaller" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/Contacts" name="platform/packages/apps/Contacts" groups="pdk-fs" /> + <project path="packages/apps/DeskClock" name="platform/packages/apps/DeskClock" groups="pdk-fs" /> + <project path="packages/apps/DevCamera" name="platform/packages/apps/DevCamera" groups="pdk" /> + <project path="packages/apps/Dialer" name="platform/packages/apps/Dialer" groups="pdk-fs" /> + <project path="packages/apps/DocumentsUI" name="platform/packages/apps/DocumentsUI" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/Email" name="platform/packages/apps/Email" groups="pdk-fs" /> + <project path="packages/apps/EmergencyInfo" name="platform/packages/apps/EmergencyInfo" groups="pdk-fs" /> + <project path="packages/apps/ExactCalculator" name="platform/packages/apps/ExactCalculator" groups="pdk-fs" /> + <project path="packages/apps/Gallery" name="platform/packages/apps/Gallery" groups="pdk-fs" /> + <project path="packages/apps/Gallery2" name="platform/packages/apps/Gallery2" groups="pdk-fs" /> + <project path="packages/apps/HTMLViewer" name="platform/packages/apps/HTMLViewer" groups="pdk-fs" /> + <project path="packages/apps/KeyChain" name="platform/packages/apps/KeyChain" groups="pdk-fs" /> + <project path="packages/apps/Launcher2" name="platform/packages/apps/Launcher2" groups="pdk-fs" /> + <project path="packages/apps/Launcher3" name="platform/packages/apps/Launcher3" groups="pdk-fs" /> + <project path="packages/apps/LegacyCamera" name="platform/packages/apps/LegacyCamera" groups="pdk-fs" /> + <project path="packages/apps/ManagedProvisioning" name="platform/packages/apps/ManagedProvisioning" groups="pdk-fs" /> + <project path="packages/apps/Messaging" name="platform/packages/apps/Messaging" groups="pdk-fs" /> + <project path="packages/apps/Music" name="platform/packages/apps/Music" groups="pdk-fs" /> + <project path="packages/apps/MusicFX" name="platform/packages/apps/MusicFX" groups="pdk-fs" /> + <project path="packages/apps/Nfc" name="platform/packages/apps/Nfc" groups="apps_nfc,pdk-fs" /> + <project path="packages/apps/OneTimeInitializer" name="platform/packages/apps/OneTimeInitializer" groups="pdk-fs" /> + <project path="packages/apps/PackageInstaller" name="platform/packages/apps/PackageInstaller" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/PhoneCommon" name="platform/packages/apps/PhoneCommon" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/apps/Protips" name="platform/packages/apps/Protips" groups="pdk-fs" /> + <project path="packages/apps/Provision" name="platform/packages/apps/Provision" groups="pdk-fs" /> + <project path="packages/apps/QuickSearchBox" name="platform/packages/apps/QuickSearchBox" groups="pdk-fs" /> + <project path="packages/apps/SafetyRegulatoryInfo" name="platform/packages/apps/SafetyRegulatoryInfo" groups="pdk-fs" /> + <project path="packages/apps/SecureElement" name="platform/packages/apps/SecureElement" groups="apps_se,pdk-fs" /> + <project path="packages/apps/Settings" name="platform/packages/apps/Settings" groups="pdk-fs" /> + <project path="packages/apps/SoundRecorder" name="platform/packages/apps/SoundRecorder" groups="pdk-fs" /> + <project path="packages/apps/SpareParts" name="platform/packages/apps/SpareParts" groups="pdk-fs" /> + <project path="packages/apps/Stk" name="platform/packages/apps/Stk" groups="apps_stk,pdk-fs" /> + <project path="packages/apps/StorageManager" name="platform/packages/apps/StorageManager" groups="pdk-fs" /> + <project path="packages/apps/Tag" name="platform/packages/apps/Tag" groups="pdk-fs" /> + <project path="packages/apps/Terminal" name="platform/packages/apps/Terminal" groups="pdk-fs" /> + <project path="packages/apps/Test/connectivity" name="platform/packages/apps/Test/connectivity" groups="pdk" /> + <project path="packages/apps/TimeZoneData" name="platform/packages/apps/TimeZoneData" groups="pdk" /> + <project path="packages/apps/TimeZoneUpdater" name="platform/packages/apps/TimeZoneUpdater" groups="pdk" /> + <project path="packages/apps/Traceur" name="platform/packages/apps/Traceur" groups="pdk-fs" /> + <project path="packages/apps/TvSettings" name="platform/packages/apps/TvSettings" groups="pdk-fs" /> + <project path="packages/apps/TV" name="platform/packages/apps/TV" /> + <project path="packages/apps/UnifiedEmail" name="platform/packages/apps/UnifiedEmail" groups="pdk-fs" /> + <project path="packages/apps/WallpaperPicker" name="platform/packages/apps/WallpaperPicker" groups="pdk-fs" /> + <project path="packages/experimental" name="platform/packages/experimental" /> + <project path="packages/inputmethods/LatinIME" name="platform/packages/inputmethods/LatinIME" groups="pdk-fs" /> + <project path="packages/inputmethods/OpenWnn" name="platform/packages/inputmethods/OpenWnn" groups="pdk-fs" /> + <project path="packages/providers/ApplicationsProvider" name="platform/packages/providers/ApplicationsProvider" groups="pdk-fs" /> + <project path="packages/providers/BlockedNumberProvider" name="platform/packages/providers/BlockedNumberProvider" groups="pdk-fs" /> + <project path="packages/providers/BookmarkProvider" name="platform/packages/providers/BookmarkProvider" groups="pdk-fs" /> + <project path="packages/providers/CalendarProvider" name="platform/packages/providers/CalendarProvider" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/providers/CallLogProvider" name="platform/packages/providers/CallLogProvider" groups="pdk-fs" /> + <project path="packages/providers/ContactsProvider" name="platform/packages/providers/ContactsProvider" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/providers/DownloadProvider" name="platform/packages/providers/DownloadProvider" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/providers/MediaProvider" name="platform/packages/providers/MediaProvider" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/providers/PartnerBookmarksProvider" name="platform/packages/providers/PartnerBookmarksProvider" groups="pdk-fs" /> + <project path="packages/providers/TelephonyProvider" name="platform/packages/providers/TelephonyProvider" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/providers/TvProvider" name="platform/packages/providers/TvProvider" groups="pdk-fs" /> + <project path="packages/providers/UserDictionaryProvider" name="platform/packages/providers/UserDictionaryProvider" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/screensavers/Basic" name="platform/packages/screensavers/Basic" groups="pdk-fs" /> + <project path="packages/screensavers/PhotoTable" name="platform/packages/screensavers/PhotoTable" groups="pdk-fs" /> + <project path="packages/screensavers/WebView" name="platform/packages/screensavers/WebView" groups="pdk-fs" /> + <project path="packages/services/BuiltInPrintService" name="platform/packages/services/BuiltInPrintService" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/services/Car" name="platform/packages/services/Car" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/services/Mms" name="platform/packages/services/Mms" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/services/NetworkRecommendation" name="platform/packages/services/NetworkRecommendation" groups="pdk-fs" /> + <project path="packages/services/Telecomm" name="platform/packages/services/Telecomm" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/services/Telephony" name="platform/packages/services/Telephony" groups="pdk-cw-fs,pdk-fs" /> + <project path="packages/wallpapers/LivePicker" name="platform/packages/wallpapers/LivePicker" groups="pdk-fs" /> + <project path="pdk" name="platform/pdk" groups="pdk" /> + <project path="platform_testing" name="platform/platform_testing" groups="pdk-fs,pdk-cw-fs,cts" /> + <project path="prebuilts/abi-dumps/ndk" name="platform/prebuilts/abi-dumps/ndk" groups="pdk-fs" clone-depth="1" /> + <project path="prebuilts/abi-dumps/vndk" name="platform/prebuilts/abi-dumps/vndk" groups="pdk-fs" clone-depth="1" /> + <project path="prebuilts/android-emulator" name="platform/prebuilts/android-emulator" groups="pdk-fs" clone-depth="1" /> + <project path="prebuilts/build-tools" name="platform/prebuilts/build-tools" groups="pdk" clone-depth="1" /> + <project path="prebuilts/checkstyle" name="platform/prebuilts/checkstyle" groups="pdk" clone-depth="1" /> + <project path="prebuilts/clang-tools" name="platform/prebuilts/clang-tools" groups="pdk" clone-depth="1" /> + <project path="prebuilts/clang/host/darwin-x86" name="platform/prebuilts/clang/host/darwin-x86" groups="pdk,darwin" clone-depth="1" /> + <project path="prebuilts/clang/host/linux-x86" name="platform/prebuilts/clang/host/linux-x86" groups="pdk" clone-depth="1" /> + <project path="prebuilts/deqp" name="platform/prebuilts/deqp" groups="pdk-fs" clone-depth="1" /> + <project path="prebuilts/devtools" name="platform/prebuilts/devtools" groups="pdk-fs" clone-depth="1" /> + <project path="prebuilts/gcc/darwin-x86/aarch64/aarch64-linux-android-4.9" name="platform/prebuilts/gcc/darwin-x86/aarch64/aarch64-linux-android-4.9" groups="pdk,darwin,arm" clone-depth="1" /> + <project path="prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9" name="platform/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.9" groups="pdk,darwin,arm" clone-depth="1" /> + <project path="prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" name="platform/prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" groups="pdk,darwin" clone-depth="1" /> + <project path="prebuilts/gcc/darwin-x86/mips/mips64el-linux-android-4.9" name="platform/prebuilts/gcc/darwin-x86/mips/mips64el-linux-android-4.9" groups="pdk,darwin,mips" clone-depth="1" /> + <project path="prebuilts/gcc/darwin-x86/x86/x86_64-linux-android-4.9" name="platform/prebuilts/gcc/darwin-x86/x86/x86_64-linux-android-4.9" groups="pdk,darwin,x86" clone-depth="1" /> + <project path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9" groups="pdk,linux,arm" clone-depth="1" /> + <project path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" groups="pdk,linux,arm" clone-depth="1" /> + <project path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8" groups="pdk,linux" clone-depth="1" /> + <project path="prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8" name="platform/prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8" groups="pdk-fs" clone-depth="1" /> + <project path="prebuilts/gcc/linux-x86/mips/mips64el-linux-android-4.9" name="platform/prebuilts/gcc/linux-x86/mips/mips64el-linux-android-4.9" groups="pdk,linux,mips" clone-depth="1" /> + <project path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" groups="pdk,linux,x86" clone-depth="1" /> + <project path="prebuilts/gdb/darwin-x86" name="platform/prebuilts/gdb/darwin-x86" groups="darwin" clone-depth="1" /> + <project path="prebuilts/gdb/linux-x86" name="platform/prebuilts/gdb/linux-x86" groups="linux" clone-depth="1" /> + <project path="prebuilts/go/darwin-x86" name="platform/prebuilts/go/darwin-x86" groups="darwin,pdk,tradefed" clone-depth="1" /> + <project path="prebuilts/go/linux-x86" name="platform/prebuilts/go/linux-x86" groups="linux,pdk,tradefed" clone-depth="1" /> + <project path="prebuilts/gradle-plugin" name="platform/prebuilts/gradle-plugin" groups="pdk-cw-fs,pdk-fs" clone-depth="1" /> + <project path="prebuilts/jdk/jdk8" name="platform/prebuilts/jdk/jdk8" groups="pdk" clone-depth="1" /> + <project path="prebuilts/jdk/jdk9" name="platform/prebuilts/jdk/jdk9" groups="pdk" clone-depth="1" /> + <project path="prebuilts/libs/libedit" name="platform/prebuilts/libs/libedit" groups="pdk-cw-fs,pdk-fs" clone-depth="1" /> + <project path="prebuilts/maven_repo/android" name="platform/prebuilts/maven_repo/android" groups="pdk-cw-fs,pdk-fs" clone-depth="1" /> + <project path="prebuilts/maven_repo/bumptech" name="platform/prebuilts/maven_repo/bumptech" groups="pdk-cw-fs,pdk-fs" clone-depth="1" /> + <project path="prebuilts/maven_repo/google-play-service-client-libraries-3p" name="platform/prebuilts/maven_repo/google-play-service-client-libraries-3p" clone-depth="1" /> + <project path="prebuilts/misc" name="platform/prebuilts/misc" groups="pdk" clone-depth="1" /> + <project path="prebuilts/ndk" name="platform/prebuilts/ndk" groups="pdk" clone-depth="1" /> + <project path="prebuilts/python/darwin-x86/2.7.5" name="platform/prebuilts/python/darwin-x86/2.7.5" groups="darwin,pdk,pdk-cw-fs,pdk-fs" clone-depth="1" /> + <project path="prebuilts/python/linux-x86/2.7.5" name="platform/prebuilts/python/linux-x86/2.7.5" groups="linux,pdk,pdk-cw-fs,pdk-fs" clone-depth="1" /> + <project path="prebuilts/qemu-kernel" name="platform/prebuilts/qemu-kernel" groups="pdk" clone-depth="1" /> + <project path="prebuilts/r8" name="platform/prebuilts/r8" groups="pdk" clone-depth="1" /> + <project path="prebuilts/sdk" name="platform/prebuilts/sdk" groups="pdk" clone-depth="1" /> + <project path="prebuilts/tools" name="platform/prebuilts/tools" groups="pdk,tools" clone-depth="1" /> + <project path="sdk" name="platform/sdk" groups="pdk-cw-fs,pdk-fs" /> + <project path="system/bt" name="platform/system/bt" groups="pdk" /> + <project path="system/ca-certificates" name="platform/system/ca-certificates" groups="pdk" /> + <project path="system/chre" name="platform/system/chre" groups="pdk" /> + <project path="system/connectivity/wificond" name="platform/system/connectivity/wificond" groups="pdk" /> + <project path="system/connectivity/wifilogd" name="platform/system/connectivity/wifilogd" groups="pdk" /> + <project path="system/core" name="platform/system/core" groups="pdk" /> + <project path="system/extras" name="platform/system/extras" groups="pdk" /> + <project path="system/gatekeeper" name="platform/system/gatekeeper" groups="pdk" /> + <project path="system/hardware/interfaces" name="platform/system/hardware/interfaces" groups="pdk" /> + <project path="system/hwservicemanager" name="platform/system/hwservicemanager" groups="pdk" /> + <project path="system/iot/attestation" name="platform/system/iot/attestation" groups="pdk" /> + <project path="system/keymaster" name="platform/system/keymaster" groups="pdk" /> + <project path="system/libfmq" name="platform/system/libfmq" groups="pdk" /> + <project path="system/libhidl" name="platform/system/libhidl" groups="pdk" /> + <project path="system/libhwbinder" name="platform/system/libhwbinder" groups="pdk" /> + <project path="system/libufdt" name="platform/system/libufdt" groups="pdk" /> + <project path="system/libvintf" name="platform/system/libvintf" groups="pdk" /> + <project path="system/media" name="platform/system/media" groups="pdk" /> + <project path="system/netd" name="platform/system/netd" groups="pdk" /> + <project path="system/nfc" name="platform/system/nfc" groups="pdk" /> + <project path="system/nvram" name="platform/system/nvram" groups="pdk" /> + <project path="system/security" name="platform/system/security" groups="pdk" /> + <project path="system/sepolicy" name="platform/system/sepolicy" groups="pdk" /> + <project path="system/timezone" name="platform/system/timezone" groups="pdk" /> + <project path="system/tools/aidl" name="platform/system/tools/aidl" groups="pdk-cw-fs,pdk-fs" /> + <project path="system/tools/bpt" name="platform/system/tools/bpt" groups="pdk" /> + <project path="system/tools/hidl" name="platform/system/tools/hidl" groups="pdk" /> + <project path="system/update_engine" name="platform/system/update_engine" groups="pdk" /> + <project path="system/vold" name="platform/system/vold" groups="pdk" /> + <project path="test/framework" name="platform/test/framework" groups="vts,pdk" /> + <project path="test/mlts/benchmark" name="platform/test/mlts/benchmark" groups="pdk" /> + <project path="test/mlts/models" name="platform/test/mlts/models" groups="pdk" /> + <project path="test/sts" name="platform/test/sts" groups="sts,pdk" /> + <project path="test/vti/alert" name="platform/test/vti/alert" groups="vts,pdk" /> + <project path="test/vti/dashboard" name="platform/test/vti/dashboard" groups="vts,pdk" /> + <project path="test/vti/fuzz_test_serving" name="platform/test/vti/fuzz_test_serving" groups="vts,pdk" /> + <project path="test/vti/test_serving" name="platform/test/vti/test_serving" groups="vts,pdk" /> + <project path="test/vts" name="platform/test/vts" groups="vts,pdk" /> + <project path="test/vts-testcase/fuzz" name="platform/test/vts-testcase/fuzz" groups="vts,pdk" /> + <project path="test/vts-testcase/hal" name="platform/test/vts-testcase/hal" groups="vts,pdk" /> + <project path="test/vts-testcase/hal-trace" name="platform/test/vts-testcase/hal-trace" groups="vts,pdk" /> + <project path="test/vts-testcase/kernel" name="platform/test/vts-testcase/kernel" groups="vts,pdk" /> + <project path="test/vts-testcase/nbu" name="platform/test/vts-testcase/nbu" groups="vts,pdk" /> + <project path="test/vts-testcase/performance" name="platform/test/vts-testcase/performance" groups="vts,pdk" /> + <project path="test/vts-testcase/security" name="platform/test/vts-testcase/security" groups="vts,pdk" /> + <project path="test/vts-testcase/vndk" name="platform/test/vts-testcase/vndk" groups="vts,pdk" /> + <project path="toolchain/benchmark" name="toolchain/benchmark" /> + <project path="toolchain/binutils" name="toolchain/binutils" groups="pdk" /> + <project path="toolchain/pgo-profiles" name="toolchain/pgo-profiles" groups="pdk" /> + <project path="tools/acloud" name="platform/tools/acloud" groups="tools,vts,pdk,tradefed" /> + <project path="tools/adt/idea" name="platform/tools/adt/idea" groups="notdefault,tools" /> + <project path="tools/apksig" name="platform/tools/apksig" groups="pdk,tradefed" /> + <project path="tools/apkzlib" name="platform/tools/apkzlib" groups="pdk,tradefed" /> + <project path="tools/base" name="platform/tools/base" groups="notdefault,tools" /> + <project path="tools/build" name="platform/tools/build" groups="notdefault,tools" /> + <project path="tools/dexter" name="platform/tools/dexter" groups="tools,pdk-fs" /> + <project path="tools/external/fat32lib" name="platform/tools/external/fat32lib" groups="tools" /> + <project path="tools/external/gradle" name="platform/tools/external/gradle" groups="tools" clone-depth="1" /> + <project path="tools/idea" name="platform/tools/idea" groups="notdefault,tools" /> + <project path="tools/loganalysis" name="platform/tools/loganalysis" groups="nopresubmit,pdk,tradefed" /> + <project path="tools/metalava" name="platform/tools/metalava" groups="tools" /> + <project path="tools/motodev" name="platform/tools/motodev" groups="notdefault,motodev" /> + <project path="tools/repohooks" name="platform/tools/repohooks" groups="adt-infra,cts,developers,motodev,pdk,tools,tradefed" /> + <project path="tools/security" name="platform/tools/security" groups="pdk,tools" /> + <project path="tools/studio/cloud" name="platform/tools/studio/cloud" groups="notdefault,tools" /> + <project path="tools/swt" name="platform/tools/swt" groups="notdefault,tools" /> + <project path="tools/test/connectivity" name="platform/tools/test/connectivity" groups="pdk" /> + <project path="tools/test/graphicsbenchmark" name="platform/tools/test/graphicsbenchmark" groups="pdk" /> + <project path="tools/tradefederation/core" name="platform/tools/tradefederation" groups="pdk,tradefed" /> + <project path="tools/tradefederation/contrib" name="platform/tools/tradefederation/contrib" groups="pdk,tradefed" /> + + <repo-hooks in-project="platform/tools/repohooks" enabled-list="pre-upload" /> + +</manifest> diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 38ce92a5dc7..3b741d51598 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -29,8 +29,10 @@ "merge_when_pipeline_succeeds": { "type": "boolean" }, "source_branch": { "type": "string" }, "source_project_id": { "type": "integer" }, + "source_project_full_path": { "type": ["string", "null"]}, "target_branch": { "type": "string" }, "target_project_id": { "type": "integer" }, + "target_project_full_path": { "type": ["string", "null"]}, "allow_collaboration": { "type": "boolean"}, "metrics": { "oneOf": [ diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index f76ed4bfda4..77410e0070c 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -21,27 +21,6 @@ describe IssuablesHelper do end end - describe '#group_dropdown_label' do - let(:group) { create(:group) } - let(:default) { 'default label' } - - it 'returns default group label when group_id is nil' do - expect(group_dropdown_label(nil, default)).to eq('default label') - end - - it 'returns "any group" when group_id is 0' do - expect(group_dropdown_label('0', default)).to eq('Any group') - end - - it 'returns group full path when a group was found for the provided id' do - expect(group_dropdown_label(group.id, default)).to eq(group.full_name) - end - - it 'returns default label when a group was not found for the provided id' do - expect(group_dropdown_label(9999, default)).to eq('default label') - end - end - describe '#issuable_labels_tooltip' do it 'returns label text with no labels' do expect(issuable_labels_tooltip([])).to eq("Labels") diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb index 460d3b6a7e4..343e140f5fb 100644 --- a/spec/helpers/namespaces_helper_spec.rb +++ b/spec/helpers/namespaces_helper_spec.rb @@ -28,6 +28,16 @@ describe NamespacesHelper do expect(options).not_to include(admin_group.name) expect(options).to include(user_group.name) + expect(options).to include(user.name) + end + + it 'returns only groups if groups_only option is true' do + allow(helper).to receive(:current_user).and_return(user) + + options = helper.namespaces_options(nil, groups_only: true) + + expect(options).not_to include(user.name) + expect(options).to include(user_group.name) end context 'when nested groups are available', :nested_groups do diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 5a2e4b34069..a64f8a11ef2 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -92,11 +92,10 @@ describe SubmoduleHelper do context 'in-repository submodule' do let(:group) { create(:group, name: "Master Project", path: "master-project") } let(:project) { create(:project, group: group) } - before do - self.instance_variable_set(:@project, project) - end it 'in-repository' do + allow(repo).to receive(:project).and_return(project) + stub_url('./') expect(submodule_links(submodule_item)).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"]) end @@ -167,32 +166,28 @@ describe SubmoduleHelper do let(:project) { create(:project, group: group) } let(:commit_id) { sample_commit[:id] } - before do - self.instance_variable_set(:@project, project) - end - it 'one level down' do - result = relative_self_links('../test.git', commit_id) + result = relative_self_links('../test.git', commit_id, project) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'with trailing whitespace' do - result = relative_self_links('../test.git ', commit_id) + result = relative_self_links('../test.git ', commit_id, project) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'two levels down' do - result = relative_self_links('../../test.git', commit_id) + result = relative_self_links('../../test.git', commit_id, project) expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) end it 'one level down with namespace and repo' do - result = relative_self_links('../foobar/test.git', commit_id) + result = relative_self_links('../foobar/test.git', commit_id, project) expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"]) end it 'two levels down with namespace and repo' do - result = relative_self_links('../foobar/baz/test.git', commit_id) + result = relative_self_links('../foobar/baz/test.git', commit_id, project) expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"]) end @@ -201,7 +196,7 @@ describe SubmoduleHelper do let(:project) { create(:project, namespace: user.namespace) } it 'one level down with personal project' do - result = relative_self_links('../test.git', commit_id) + result = relative_self_links('../test.git', commit_id, project) expect(result).to eq(["/#{user.username}/test", "/#{user.username}/test/tree/#{commit_id}"]) end end diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js index 7a34126eef7..0b933dda431 100644 --- a/spec/javascripts/environments/environment_item_spec.js +++ b/spec/javascripts/environments/environment_item_spec.js @@ -104,7 +104,7 @@ describe('Environment item', () => { }, ], }, - 'stop_action?': true, + has_stop_action: true, environment_path: 'root/ci-folders/environments/31', created_at: '2016-11-07T11:11:16.525Z', updated_at: '2016-11-10T15:55:58.778Z', diff --git a/spec/javascripts/environments/mock_data.js b/spec/javascripts/environments/mock_data.js index 8a1e26935d9..7bb57f938b8 100644 --- a/spec/javascripts/environments/mock_data.js +++ b/spec/javascripts/environments/mock_data.js @@ -7,7 +7,7 @@ export const environmentsList = [ external_url: null, environment_type: null, last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/7', stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', @@ -22,7 +22,7 @@ export const environmentsList = [ external_url: null, environment_type: 'build', last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/12', stop_path: '/root/review-app/environments/12/stop', created_at: '2017-02-01T19:42:18.400Z', @@ -41,7 +41,7 @@ export const serverData = [ external_url: null, environment_type: null, last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/7', stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', @@ -58,7 +58,7 @@ export const serverData = [ external_url: null, environment_type: 'build', last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/12', stop_path: '/root/review-app/environments/12/stop', created_at: '2017-02-01T19:42:18.400Z', @@ -77,7 +77,7 @@ export const environment = { external_url: null, environment_type: null, last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/7', stop_path: '/root/review-app/environments/7/stop', created_at: '2017-01-31T10:53:46.894Z', @@ -95,7 +95,7 @@ export const folder = { external_url: null, environment_type: 'build', last_deployment: null, - 'stop_action?': false, + has_stop_action: false, environment_path: '/root/review-app/environments/12', stop_path: '/root/review-app/environments/12/stop', created_at: '2017-02-01T19:42:18.400Z', diff --git a/spec/javascripts/frequent_items/store/actions_spec.js b/spec/javascripts/frequent_items/store/actions_spec.js index 0cdd033d38f..0a8525e77d6 100644 --- a/spec/javascripts/frequent_items/store/actions_spec.js +++ b/spec/javascripts/frequent_items/store/actions_spec.js @@ -205,7 +205,7 @@ describe('Frequent Items Dropdown Store Actions', () => { actions.setSearchQuery, { query: 'test' }, mockedState, - [{ type: types.SET_SEARCH_QUERY }], + [{ type: types.SET_SEARCH_QUERY, payload: { query: 'test' } }], [{ type: 'fetchSearchedItems', payload: { query: 'test' } }], done, ); @@ -216,7 +216,7 @@ describe('Frequent Items Dropdown Store Actions', () => { actions.setSearchQuery, null, mockedState, - [{ type: types.SET_SEARCH_QUERY }], + [{ type: types.SET_SEARCH_QUERY, payload: null }], [{ type: 'fetchFrequentItems' }], done, ); diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js index d6ab0aeeed7..4ca7015184e 100644 --- a/spec/javascripts/helpers/vuex_action_helper.js +++ b/spec/javascripts/helpers/vuex_action_helper.js @@ -1,71 +1,103 @@ +const noop = () => {}; + /** - * helper for testing action with expected mutations inspired in + * Helper for testing action with expected mutations inspired in * https://vuex.vuejs.org/en/testing.html * + * @param {Function} action to be tested + * @param {Object} payload will be provided to the action + * @param {Object} state will be provided to the action + * @param {Array} [expectedMutations=[]] mutations expected to be committed + * @param {Array} [expectedActions=[]] actions expected to be dispatched + * @param {Function} [done=noop] to be executed after the tests + * @return {Promise} + * * @example * testAction( * actions.actionName, // action - * { }, // mocked response - * state, // state + * { }, // mocked payload + * state, //state + * // expected mutations * [ * { type: types.MUTATION} - * { type: types.MUTATION_1, payload: {}} - * ], // mutations + * { type: types.MUTATION_1, payload: jasmine.any(Number)} + * ], + * // expected actions * [ - * { type: 'actionName', payload: {}}, - * { type: 'actionName1', payload: {}} - * ] //actions + * { type: 'actionName', payload: {param: 'foobar'}}, + * { type: 'actionName1'} + * ] * done, * ); + * + * @example + * testAction( + * actions.actionName, // action + * { }, // mocked payload + * state, //state + * [ { type: types.MUTATION} ], // expected mutations + * [], // expected actions + * ).then(done) + * .catch(done.fail); */ -export default (action, payload, state, expectedMutations, expectedActions, done) => { - let mutationsCount = 0; - let actionsCount = 0; +export default ( + action, + payload, + state, + expectedMutations = [], + expectedActions = [], + done = noop, +) => { + const mutations = []; + const actions = []; // mock commit const commit = (type, mutationPayload) => { - const mutation = expectedMutations[mutationsCount]; - - expect(mutation.type).toEqual(type); + const mutation = { type }; - if (mutation.payload) { - expect(mutation.payload).toEqual(mutationPayload); + if (typeof mutationPayload !== 'undefined') { + mutation.payload = mutationPayload; } - mutationsCount += 1; - if (mutationsCount >= expectedMutations.length) { - done(); - } + mutations.push(mutation); }; // mock dispatch const dispatch = (type, actionPayload) => { - const actionExpected = expectedActions[actionsCount]; - - expect(actionExpected.type).toEqual(type); + const dispatchedAction = { type }; - if (actionExpected.payload) { - expect(actionExpected.payload).toEqual(actionPayload); + if (typeof actionPayload !== 'undefined') { + dispatchedAction.payload = actionPayload; } - actionsCount += 1; - if (actionsCount >= expectedActions.length) { - done(); - } + actions.push(dispatchedAction); }; - // call the action with mocked store and arguments - action({ commit, state, dispatch, rootState: state }, payload); - - // check if no mutations should have been dispatched - if (expectedMutations.length === 0) { - expect(mutationsCount).toEqual(0); + const validateResults = () => { + expect({ + mutations, + actions, + }).toEqual({ + mutations: expectedMutations, + actions: expectedActions, + }); done(); - } + }; - // check if no mutations should have been dispatched - if (expectedActions.length === 0) { - expect(actionsCount).toEqual(0); - done(); - } + return new Promise((resolve, reject) => { + try { + const result = action({ commit, state, dispatch, rootState: state }, payload); + resolve(result); + } catch (e) { + reject(e); + } + }) + .catch(error => { + validateResults(); + throw error; + }) + .then(data => { + validateResults(); + return data; + }); }; diff --git a/spec/javascripts/helpers/vuex_action_helper_spec.js b/spec/javascripts/helpers/vuex_action_helper_spec.js new file mode 100644 index 00000000000..8d6ad6750c0 --- /dev/null +++ b/spec/javascripts/helpers/vuex_action_helper_spec.js @@ -0,0 +1,141 @@ +import MockAdapter from 'axios-mock-adapter'; +import { TEST_HOST } from 'spec/test_constants'; +import axios from '~/lib/utils/axios_utils'; +import testAction from './vuex_action_helper'; + +describe('VueX test helper (testAction)', () => { + let originalExpect; + let assertion; + let mock; + const noop = () => {}; + + beforeAll(() => { + mock = new MockAdapter(axios); + /* + In order to test the helper properly, we need to overwrite the jasmine `expect` helper. + We test that the testAction helper properly passes the dispatched actions/committed mutations + to the jasmine helper. + */ + originalExpect = expect; + assertion = null; + global.expect = actual => ({ + toEqual: () => { + originalExpect(actual).toEqual(assertion); + }, + }); + }); + + afterAll(() => { + mock.restore(); + global.expect = originalExpect; + }); + + it('should properly pass on state and payload', () => { + const exampleState = { FOO: 12, BAR: 3 }; + const examplePayload = { BAZ: 73, BIZ: 55 }; + + const action = ({ state }, payload) => { + originalExpect(state).toEqual(exampleState); + originalExpect(payload).toEqual(examplePayload); + }; + + assertion = { mutations: [], actions: [] }; + + testAction(action, examplePayload, exampleState); + }); + + describe('should work with synchronous actions', () => { + it('committing mutation', () => { + const action = ({ commit }) => { + commit('MUTATION'); + }; + + assertion = { mutations: [{ type: 'MUTATION' }], actions: [] }; + + testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + }); + + it('dispatching action', () => { + const action = ({ dispatch }) => { + dispatch('ACTION'); + }; + + assertion = { actions: [{ type: 'ACTION' }], mutations: [] }; + + testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + }); + + it('work with jasmine done once finished', done => { + assertion = { mutations: [], actions: [] }; + + testAction(noop, null, {}, assertion.mutations, assertion.actions, done); + }); + + it('provide promise interface', done => { + assertion = { mutations: [], actions: [] }; + + testAction(noop, null, {}, assertion.mutations, assertion.actions) + .then(done) + .catch(done.fail); + }); + }); + + describe('should work with promise based actions (fetch action)', () => { + let lastError; + const data = { FOO: 'BAR' }; + + const promiseAction = ({ commit, dispatch }) => { + dispatch('ACTION'); + + return axios + .get(TEST_HOST) + .catch(error => { + commit('ERROR'); + lastError = error; + throw error; + }) + .then(() => { + commit('SUCCESS'); + return data; + }); + }; + + beforeEach(() => { + lastError = null; + }); + + it('work with jasmine done once finished', done => { + mock.onGet(TEST_HOST).replyOnce(200, 42); + + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + + testAction(promiseAction, null, {}, assertion.mutations, assertion.actions, done); + }); + + it('return original data of successful promise while checking actions/mutations', done => { + mock.onGet(TEST_HOST).replyOnce(200, 42); + + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + + testAction(promiseAction, null, {}, assertion.mutations, assertion.actions) + .then(res => { + originalExpect(res).toEqual(data); + done(); + }) + .catch(done.fail); + }); + + it('return original error of rejected promise while checking actions/mutations', done => { + mock.onGet(TEST_HOST).replyOnce(500, ''); + + assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] }; + + testAction(promiseAction, null, {}, assertion.mutations, assertion.actions) + .then(done.fail) + .catch(error => { + originalExpect(error).toBe(lastError); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/components/ide_status_bar_spec.js b/spec/javascripts/ide/components/ide_status_bar_spec.js index 770dca9cb0f..9895682f388 100644 --- a/spec/javascripts/ide/components/ide_status_bar_spec.js +++ b/spec/javascripts/ide/components/ide_status_bar_spec.js @@ -13,6 +13,7 @@ describe('ideStatusBar', () => { store.state.currentProjectId = 'abcproject'; store.state.projects.abcproject = projectData; + store.state.currentBranchId = 'master'; vm = createComponentWithStore(Component, store).$mount(); }); @@ -60,4 +61,29 @@ describe('ideStatusBar', () => { expect(vm.getCommitPath('abc123de')).toBe('/commit/abc123de'); }); }); + + describe('pipeline status', () => { + it('opens right sidebar on clicking icon', done => { + spyOn(vm, 'setRightPane'); + Vue.set(vm.$store.state.pipelines, 'latestPipeline', { + details: { + status: { + text: 'success', + details_path: 'test', + icon: 'success', + }, + }, + }); + + vm + .$nextTick() + .then(() => { + vm.$el.querySelector('.ide-status-pipeline button').click(); + + expect(vm.setRightPane).toHaveBeenCalledWith('pipelines-list'); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/ide/components/new_dropdown/button_spec.js b/spec/javascripts/ide/components/new_dropdown/button_spec.js new file mode 100644 index 00000000000..ef083d06ba7 --- /dev/null +++ b/spec/javascripts/ide/components/new_dropdown/button_spec.js @@ -0,0 +1,49 @@ +import Vue from 'vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import Button from '~/ide/components/new_dropdown/button.vue'; + +describe('IDE new entry dropdown button component', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(Button); + }); + + beforeEach(() => { + vm = mountComponent(Component, { + label: 'Testing', + icon: 'doc-new', + }); + + spyOn(vm, '$emit'); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders button with label', () => { + expect(vm.$el.textContent).toContain('Testing'); + }); + + it('renders icon', () => { + expect(vm.$el.querySelector('.ic-doc-new')).not.toBe(null); + }); + + it('emits click event', () => { + vm.$el.click(); + + expect(vm.$emit).toHaveBeenCalledWith('click'); + }); + + it('hides label if showLabel is false', done => { + vm.showLabel = false; + + vm.$nextTick(() => { + expect(vm.$el.textContent).not.toContain('Testing'); + + done(); + }); + }); +}); diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js index 7b637f37eba..4d704b80209 100644 --- a/spec/javascripts/ide/components/new_dropdown/index_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js @@ -13,6 +13,7 @@ describe('new dropdown component', () => { vm = createComponentWithStore(component, store, { branch: 'master', path: '', + mouseOver: false, }); vm.$store.state.currentProjectId = 'abcproject'; @@ -21,6 +22,8 @@ describe('new dropdown component', () => { tree: [], }; + spyOn(vm, 'openNewEntryModal'); + vm.$mount(); }); @@ -31,50 +34,23 @@ describe('new dropdown component', () => { }); it('renders new file, upload and new directory links', () => { - expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); - expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file'); - expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory'); + const buttons = vm.$el.querySelectorAll('.dropdown-menu button'); + expect(buttons[0].textContent.trim()).toBe('New file'); + expect(buttons[1].textContent.trim()).toBe('Upload file'); + expect(buttons[2].textContent.trim()).toBe('New directory'); }); describe('createNewItem', () => { it('sets modalType to blob when new file is clicked', () => { - vm.$el.querySelectorAll('a')[0].click(); + vm.$el.querySelectorAll('.dropdown-menu button')[0].click(); - expect(vm.modalType).toBe('blob'); + expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'blob', path: '' }); }); it('sets modalType to tree when new directory is clicked', () => { - vm.$el.querySelectorAll('a')[2].click(); - - expect(vm.modalType).toBe('tree'); - }); - - it('opens modal when link is clicked', done => { - vm.$el.querySelectorAll('a')[0].click(); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.modal')).not.toBeNull(); - - done(); - }); - }); - }); - - describe('hideModal', () => { - beforeAll(done => { - vm.openModal = true; - Vue.nextTick(done); - }); - - it('closes modal after toggling', done => { - vm.hideModal(); + vm.$el.querySelectorAll('.dropdown-menu button')[2].click(); - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.modal')).toBeNull(); - }) - .then(done) - .catch(done.fail); + expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'tree', path: '' }); }); }); diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js index f362ed4db65..6dcc5880677 100644 --- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js @@ -1,6 +1,7 @@ import Vue from 'vue'; +import { createStore } from '~/ide/stores'; import modal from '~/ide/components/new_dropdown/modal.vue'; -import createComponent from 'spec/helpers/vue_mount_component_helper'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; describe('new file modal component', () => { const Component = Vue.extend(modal); @@ -13,13 +14,15 @@ describe('new file modal component', () => { ['tree', 'blob'].forEach(type => { describe(type, () => { beforeEach(() => { - vm = createComponent(Component, { + const store = createStore(); + store.state.newEntryModal = { type, - branchId: 'master', path: '', - }); + }; + + vm = createComponentWithStore(Component, store).$mount(); - vm.entryName = 'testing'; + vm.name = 'testing'; }); it(`sets modal title as ${type}`, () => { @@ -40,12 +43,11 @@ describe('new file modal component', () => { describe('createEntryInStore', () => { it('$emits create', () => { - spyOn(vm, '$emit'); + spyOn(vm, 'createTempEntry'); vm.createEntryInStore(); - expect(vm.$emit).toHaveBeenCalledWith('create', { - branchId: 'master', + expect(vm.createTempEntry).toHaveBeenCalledWith({ name: 'testing', type, }); @@ -53,22 +55,4 @@ describe('new file modal component', () => { }); }); }); - - it('focuses field on mount', () => { - document.body.innerHTML += '<div class="js-test"></div>'; - - vm = createComponent( - Component, - { - type: 'tree', - branchId: 'master', - path: '', - }, - '.js-test', - ); - - expect(document.activeElement).toBe(vm.$refs.fieldName); - - vm.$el.remove(); - }); }); diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js index 2bc5d701601..9c76500cfe5 100644 --- a/spec/javascripts/ide/components/new_dropdown/upload_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js @@ -9,7 +9,6 @@ describe('new dropdown upload', () => { const Component = Vue.extend(upload); vm = createComponent(Component, { - branchId: 'master', path: '', }); @@ -65,7 +64,6 @@ describe('new dropdown upload', () => { expect(vm.$emit).toHaveBeenCalledWith('create', { name: file.name, - branchId: 'master', type: 'blob', content: target.result, base64: false, @@ -77,7 +75,6 @@ describe('new dropdown upload', () => { expect(vm.$emit).toHaveBeenCalledWith('create', { name: file.name, - branchId: 'master', type: 'blob', content: binaryTarget.result.split('base64,')[1], base64: true, diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index 80bf664d491..4bbda4c8e80 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -9,6 +9,9 @@ export const projectData = { master: { treeId: 'abcproject/master', can_push: true, + commit: { + id: '123', + }, }, }, mergeRequests: {}, diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index 58d3ffc6d94..f570c0b16bd 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -601,10 +601,7 @@ describe('IDE store file actions', () => { actions.unstageChange, 'path', store.state, - [ - { type: types.UNSTAGE_CHANGE, payload: 'path' }, - { type: types.SET_LAST_COMMIT_MSG, payload: '' }, - ], + [{ type: types.UNSTAGE_CHANGE, payload: 'path' }], [], done, ); diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index ca79edafb7e..6a85968e199 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -73,6 +73,7 @@ describe('IDE store project actions', () => { branchId: store.state.currentBranchId, }, store.state, + // mutations [ { type: 'SET_BRANCH_COMMIT', @@ -82,17 +83,9 @@ describe('IDE store project actions', () => { commit: { id: '123' }, }, }, - ], // mutations - [ - { - type: 'getLastCommitPipeline', - payload: { - projectId: 'abc/def', - projectIdNumber: store.state.projects['abc/def'].id, - branchId: 'master', - }, - }, - ], // action + ], + // action + [], done, ); }); diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js index 6860e6cdb91..9f098eded08 100644 --- a/spec/javascripts/ide/stores/actions/tree_spec.js +++ b/spec/javascripts/ide/stores/actions/tree_spec.js @@ -192,11 +192,8 @@ describe('Multi-file store tree actions', () => { showTreeEntry, 'grandparent/parent/child.txt', store.state, - [ - { type: types.SET_TREE_OPEN, payload: 'grandparent/parent' }, - { type: types.SET_TREE_OPEN, payload: 'grandparent' }, - ], - [{ type: 'showTreeEntry' }], + [{ type: types.SET_TREE_OPEN, payload: 'grandparent/parent' }], + [{ type: 'showTreeEntry', payload: 'grandparent/parent' }], done, ); }); 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 d21f33eaf6d..d063f1ea860 100644 --- a/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/merge_requests/actions_spec.js @@ -122,21 +122,6 @@ describe('IDE merge requests actions', () => { }); }); - it('dispatches request', done => { - testAction( - fetchMergeRequests, - { type: 'created' }, - mockedState, - [], - [ - { type: 'requestMergeRequests' }, - { type: 'resetMergeRequests' }, - { type: 'receiveMergeRequestsSuccess' }, - ], - done, - ); - }); - it('dispatches success with received data', done => { testAction( fetchMergeRequests, @@ -144,8 +129,8 @@ describe('IDE merge requests actions', () => { mockedState, [], [ - { type: 'requestMergeRequests' }, - { type: 'resetMergeRequests' }, + { type: 'requestMergeRequests', payload: 'created' }, + { type: 'resetMergeRequests', payload: 'created' }, { type: 'receiveMergeRequestsSuccess', payload: { type: 'created', data: mergeRequests }, @@ -168,9 +153,9 @@ describe('IDE merge requests actions', () => { mockedState, [], [ - { type: 'requestMergeRequests' }, - { type: 'resetMergeRequests' }, - { type: 'receiveMergeRequestsError' }, + { type: 'requestMergeRequests', payload: 'created' }, + { type: 'resetMergeRequests', payload: 'created' }, + { type: 'receiveMergeRequestsError', payload: { type: 'created', search: '' } }, ], done, ); diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js index 836ba72b5d8..91edb388791 100644 --- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -315,7 +315,7 @@ describe('IDE pipelines actions', () => { 'job', mockedState, [{ type: types.SET_DETAIL_JOB, payload: 'job' }], - [{ type: 'setRightPane' }], + [{ type: 'setRightPane', payload: 'jobs-detail' }], done, ); }); @@ -325,7 +325,7 @@ describe('IDE pipelines actions', () => { setDetailJob, null, mockedState, - [{ type: types.SET_DETAIL_JOB }], + [{ type: types.SET_DETAIL_JOB, payload: null }], [{ type: 'setRightPane', payload: rightSidebarViews.pipelines }], done, ); @@ -336,7 +336,7 @@ describe('IDE pipelines actions', () => { setDetailJob, 'job', mockedState, - [{ type: types.SET_DETAIL_JOB }], + [{ type: types.SET_DETAIL_JOB, payload: 'job' }], [{ type: 'setRightPane', payload: rightSidebarViews.jobsDetail }], done, ); diff --git a/spec/javascripts/issuable_time_tracker_spec.js b/spec/javascripts/issuable_time_tracker_spec.js deleted file mode 100644 index 5add150f874..00000000000 --- a/spec/javascripts/issuable_time_tracker_spec.js +++ /dev/null @@ -1,201 +0,0 @@ -/* eslint-disable no-unused-vars, func-call-spacing, no-spaced-func, semi, quotes, space-infix-ops, max-len */ - -import $ from 'jquery'; -import Vue from 'vue'; - -import timeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; - -function initTimeTrackingComponent(opts) { - setFixtures(` - <div> - <div id="mock-container"></div> - </div> - `); - - this.initialData = { - time_estimate: opts.timeEstimate, - time_spent: opts.timeSpent, - human_time_estimate: opts.timeEstimateHumanReadable, - human_time_spent: opts.timeSpentHumanReadable, - rootPath: '/', - }; - - const TimeTrackingComponent = Vue.extend(timeTracker); - this.timeTracker = new TimeTrackingComponent({ - el: '#mock-container', - propsData: this.initialData, - }); -} - -describe('Issuable Time Tracker', function() { - describe('Initialization', function() { - beforeEach(function() { - initTimeTrackingComponent.call(this, { timeEstimate: 100000, timeSpent: 5000, timeEstimateHumanReadable: '2h 46m', timeSpentHumanReadable: '1h 23m' }); - }); - - it('should return something defined', function() { - expect(this.timeTracker).toBeDefined(); - }); - - it ('should correctly set timeEstimate', function(done) { - Vue.nextTick(() => { - expect(this.timeTracker.timeEstimate).toBe(this.initialData.time_estimate); - done(); - }); - }); - it ('should correctly set time_spent', function(done) { - Vue.nextTick(() => { - expect(this.timeTracker.timeSpent).toBe(this.initialData.time_spent); - done(); - }); - }); - }); - - describe('Content Display', function() { - describe('Panes', function() { - describe('Comparison pane', function() { - beforeEach(function() { - initTimeTrackingComponent.call(this, { timeEstimate: 100000, timeSpent: 5000, timeEstimateHumanReadable: '', timeSpentHumanReadable: '' }); - }); - - it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', function(done) { - Vue.nextTick(() => { - const $comparisonPane = this.timeTracker.$el.querySelector('.time-tracking-comparison-pane'); - expect(this.timeTracker.showComparisonState).toBe(true); - done(); - }); - }); - - describe('Remaining meter', function() { - it('should display the remaining meter with the correct width', function(done) { - Vue.nextTick(() => { - const meterWidth = this.timeTracker.$el.querySelector('.time-tracking-comparison-pane .meter-fill').style.width; - const correctWidth = '5%'; - - expect(meterWidth).toBe(correctWidth); - done(); - }) - }); - - it('should display the remaining meter with the correct background color when within estimate', function(done) { - Vue.nextTick(() => { - const styledMeter = $(this.timeTracker.$el).find('.time-tracking-comparison-pane .within_estimate .meter-fill'); - expect(styledMeter.length).toBe(1); - done() - }); - }); - - it('should display the remaining meter with the correct background color when over estimate', function(done) { - this.timeTracker.time_estimate = 100000; - this.timeTracker.time_spent = 20000000; - Vue.nextTick(() => { - const styledMeter = $(this.timeTracker.$el).find('.time-tracking-comparison-pane .over_estimate .meter-fill'); - expect(styledMeter.length).toBe(1); - done(); - }); - }); - }); - }); - - describe("Estimate only pane", function() { - beforeEach(function() { - initTimeTrackingComponent.call(this, { timeEstimate: 100000, timeSpent: 0, timeEstimateHumanReadable: '2h 46m', timeSpentHumanReadable: '' }); - }); - - it('should display the human readable version of time estimated', function(done) { - Vue.nextTick(() => { - const estimateText = this.timeTracker.$el.querySelector('.time-tracking-estimate-only-pane').innerText; - const correctText = 'Estimated: 2h 46m'; - - expect(estimateText).toBe(correctText); - done(); - }); - }); - }); - - describe('Spent only pane', function() { - beforeEach(function() { - initTimeTrackingComponent.call(this, { timeEstimate: 0, timeSpent: 5000, timeEstimateHumanReadable: '2h 46m', timeSpentHumanReadable: '1h 23m' }); - }); - - it('should display the human readable version of time spent', function(done) { - Vue.nextTick(() => { - const spentText = this.timeTracker.$el.querySelector('.time-tracking-spend-only-pane').innerText; - const correctText = 'Spent: 1h 23m'; - - expect(spentText).toBe(correctText); - done(); - }); - }); - }); - - describe('No time tracking pane', function() { - beforeEach(function() { - initTimeTrackingComponent.call(this, { timeEstimate: 0, timeSpent: 0, timeEstimateHumanReadable: '', timeSpentHumanReadable: '' }); - }); - - it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', function(done) { - Vue.nextTick(() => { - const $noTrackingPane = this.timeTracker.$el.querySelector('.time-tracking-no-tracking-pane'); - const noTrackingText =$noTrackingPane.innerText; - const correctText = 'No estimate or time spent'; - - expect(this.timeTracker.showNoTimeTrackingState).toBe(true); - expect($noTrackingPane).toBeVisible(); - expect(noTrackingText).toBe(correctText); - done(); - }); - }); - }); - - describe("Help pane", function() { - beforeEach(function() { - initTimeTrackingComponent.call(this, { timeEstimate: 0, timeSpent: 0 }); - }); - - it('should not show the "Help" pane by default', function(done) { - Vue.nextTick(() => { - const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state'); - - expect(this.timeTracker.showHelpState).toBe(false); - expect($helpPane).toBeNull(); - done(); - }); - }); - - it('should show the "Help" pane when help button is clicked', function(done) { - Vue.nextTick(() => { - $(this.timeTracker.$el).find('.help-button').click(); - - setTimeout(() => { - const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state'); - expect(this.timeTracker.showHelpState).toBe(true); - expect($helpPane).toBeVisible(); - done(); - }, 10); - }); - }); - - it('should not show the "Help" pane when help button is clicked and then closed', function(done) { - Vue.nextTick(() => { - $(this.timeTracker.$el).find('.help-button').click(); - - setTimeout(() => { - - $(this.timeTracker.$el).find('.close-help-button').click(); - - setTimeout(() => { - const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state'); - - expect(this.timeTracker.showHelpState).toBe(false); - expect($helpPane).toBeNull(); - - done(); - }, 1000); - }, 1000); - }); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js index 9ab9ab1c9f4..7926db44429 100644 --- a/spec/javascripts/performance_bar/components/performance_bar_app_spec.js +++ b/spec/javascripts/performance_bar/components/performance_bar_app_spec.js @@ -1,39 +1,15 @@ import Vue from 'vue'; -import axios from '~/lib/utils/axios_utils'; import performanceBarApp from '~/performance_bar/components/performance_bar_app.vue'; -import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; import PerformanceBarStore from '~/performance_bar/stores/performance_bar_store'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import MockAdapter from 'axios-mock-adapter'; -describe('performance bar', () => { - let mock; +describe('performance bar app', () => { let vm; beforeEach(() => { const store = new PerformanceBarStore(); - mock = new MockAdapter(axios); - - mock.onGet('/-/peek/results').reply( - 200, - { - data: { - gc: { - invokes: 0, - invoke_time: '0.00', - use_size: 0, - total_size: 0, - total_object: 0, - gc_time: '0.00', - }, - host: { hostname: 'web-01' }, - }, - }, - {}, - ); - vm = mountComponent(Vue.extend(performanceBarApp), { store, env: 'development', @@ -45,44 +21,9 @@ describe('performance bar', () => { afterEach(() => { vm.$destroy(); - mock.restore(); }); it('sets the class to match the environment', () => { expect(vm.$el.getAttribute('class')).toContain('development'); }); - - describe('loadRequestDetails', () => { - beforeEach(() => { - spyOn(vm.store, 'addRequest').and.callThrough(); - }); - - it('does nothing if the request cannot be tracked', () => { - spyOn(vm.store, 'canTrackRequest').and.callFake(() => false); - - vm.loadRequestDetails('123', 'https://gitlab.com/'); - - expect(vm.store.addRequest).not.toHaveBeenCalled(); - }); - - it('adds the request immediately', () => { - vm.loadRequestDetails('123', 'https://gitlab.com/'); - - expect(vm.store.addRequest).toHaveBeenCalledWith( - '123', - 'https://gitlab.com/', - ); - }); - - it('makes an HTTP request for the request details', () => { - spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough(); - - vm.loadRequestDetails('456', 'https://gitlab.com/'); - - expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith( - '/-/peek/results', - '456', - ); - }); - }); }); diff --git a/spec/javascripts/performance_bar/index_spec.js b/spec/javascripts/performance_bar/index_spec.js new file mode 100644 index 00000000000..1784bd64adb --- /dev/null +++ b/spec/javascripts/performance_bar/index_spec.js @@ -0,0 +1,83 @@ +import axios from '~/lib/utils/axios_utils'; +import performanceBar from '~/performance_bar'; +import PerformanceBarService from '~/performance_bar/services/performance_bar_service'; + +import MockAdapter from 'axios-mock-adapter'; + +describe('performance bar wrapper', () => { + let mock; + let vm; + + beforeEach(() => { + const peekWrapper = document.createElement('div'); + + peekWrapper.setAttribute('id', 'js-peek'); + peekWrapper.setAttribute('data-env', 'development'); + peekWrapper.setAttribute('data-request-id', '123'); + peekWrapper.setAttribute('data-peek-url', '/-/peek/results'); + peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true'); + + document.body.appendChild(peekWrapper); + + mock = new MockAdapter(axios); + + mock.onGet('/-/peek/results').reply( + 200, + { + data: { + gc: { + invokes: 0, + invoke_time: '0.00', + use_size: 0, + total_size: 0, + total_object: 0, + gc_time: '0.00', + }, + host: { hostname: 'web-01' }, + }, + }, + {}, + ); + + vm = performanceBar({ container: '#js-peek' }); + }); + + afterEach(() => { + vm.$destroy(); + mock.restore(); + }); + + describe('loadRequestDetails', () => { + beforeEach(() => { + spyOn(vm.store, 'addRequest').and.callThrough(); + }); + + it('does nothing if the request cannot be tracked', () => { + spyOn(vm.store, 'canTrackRequest').and.callFake(() => false); + + vm.loadRequestDetails('123', 'https://gitlab.com/'); + + expect(vm.store.addRequest).not.toHaveBeenCalled(); + }); + + it('adds the request immediately', () => { + vm.loadRequestDetails('123', 'https://gitlab.com/'); + + expect(vm.store.addRequest).toHaveBeenCalledWith( + '123', + 'https://gitlab.com/', + ); + }); + + it('makes an HTTP request for the request details', () => { + spyOn(PerformanceBarService, 'fetchRequestDetails').and.callThrough(); + + vm.loadRequestDetails('456', 'https://gitlab.com/'); + + expect(PerformanceBarService.fetchRequestDetails).toHaveBeenCalledWith( + '/-/peek/results', + '456', + ); + }); + }); +}); diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js new file mode 100644 index 00000000000..b58de607ece --- /dev/null +++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js @@ -0,0 +1,243 @@ +import $ from 'jquery'; +import Vue from 'vue'; + +import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; + +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Issuable Time Tracker', () => { + let initialData; + let vm; + + const initTimeTrackingComponent = opts => { + setFixtures(` + <div> + <div id="mock-container"></div> + </div> + `); + + initialData = { + time_estimate: opts.timeEstimate, + time_spent: opts.timeSpent, + human_time_estimate: opts.timeEstimateHumanReadable, + human_time_spent: opts.timeSpentHumanReadable, + rootPath: '/', + }; + + const TimeTrackingComponent = Vue.extend({ + ...TimeTracker, + components: { + ...TimeTracker.components, + transition: { + // disable animations + template: '<div><slot></slot></div>', + }, + }, + }); + vm = mountComponent(TimeTrackingComponent, initialData, '#mock-container'); + }; + + afterEach(() => { + vm.$destroy(); + }); + + describe('Initialization', () => { + beforeEach(() => { + initTimeTrackingComponent({ + timeEstimate: 100000, + timeSpent: 5000, + timeEstimateHumanReadable: '2h 46m', + timeSpentHumanReadable: '1h 23m', + }); + }); + + it('should return something defined', () => { + expect(vm).toBeDefined(); + }); + + it('should correctly set timeEstimate', done => { + Vue.nextTick(() => { + expect(vm.timeEstimate).toBe(initialData.time_estimate); + done(); + }); + }); + + it('should correctly set time_spent', done => { + Vue.nextTick(() => { + expect(vm.timeSpent).toBe(initialData.time_spent); + done(); + }); + }); + }); + + describe('Content Display', () => { + describe('Panes', () => { + describe('Comparison pane', () => { + beforeEach(() => { + initTimeTrackingComponent({ + timeEstimate: 100000, + timeSpent: 5000, + timeEstimateHumanReadable: '', + timeSpentHumanReadable: '', + }); + }); + + it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', done => { + Vue.nextTick(() => { + expect(vm.showComparisonState).toBe(true); + const $comparisonPane = vm.$el.querySelector('.time-tracking-comparison-pane'); + expect($comparisonPane).toBeVisible(); + done(); + }); + }); + + describe('Remaining meter', () => { + it('should display the remaining meter with the correct width', done => { + Vue.nextTick(() => { + const meterWidth = vm.$el.querySelector('.time-tracking-comparison-pane .meter-fill') + .style.width; + const correctWidth = '5%'; + + expect(meterWidth).toBe(correctWidth); + done(); + }); + }); + + it('should display the remaining meter with the correct background color when within estimate', done => { + Vue.nextTick(() => { + const styledMeter = $(vm.$el).find( + '.time-tracking-comparison-pane .within_estimate .meter-fill', + ); + expect(styledMeter.length).toBe(1); + done(); + }); + }); + + it('should display the remaining meter with the correct background color when over estimate', done => { + vm.time_estimate = 100000; + vm.time_spent = 20000000; + Vue.nextTick(() => { + const styledMeter = $(vm.$el).find( + '.time-tracking-comparison-pane .over_estimate .meter-fill', + ); + expect(styledMeter.length).toBe(1); + done(); + }); + }); + }); + }); + + describe('Estimate only pane', () => { + beforeEach(() => { + initTimeTrackingComponent({ + timeEstimate: 100000, + timeSpent: 0, + timeEstimateHumanReadable: '2h 46m', + timeSpentHumanReadable: '', + }); + }); + + it('should display the human readable version of time estimated', done => { + Vue.nextTick(() => { + const estimateText = vm.$el.querySelector('.time-tracking-estimate-only-pane') + .innerText; + const correctText = 'Estimated: 2h 46m'; + + expect(estimateText).toBe(correctText); + done(); + }); + }); + }); + + describe('Spent only pane', () => { + beforeEach(() => { + initTimeTrackingComponent({ + timeEstimate: 0, + timeSpent: 5000, + timeEstimateHumanReadable: '2h 46m', + timeSpentHumanReadable: '1h 23m', + }); + }); + + it('should display the human readable version of time spent', done => { + Vue.nextTick(() => { + const spentText = vm.$el.querySelector('.time-tracking-spend-only-pane').innerText; + const correctText = 'Spent: 1h 23m'; + + expect(spentText).toBe(correctText); + done(); + }); + }); + }); + + describe('No time tracking pane', () => { + beforeEach(() => { + initTimeTrackingComponent({ + timeEstimate: 0, + timeSpent: 0, + timeEstimateHumanReadable: '', + timeSpentHumanReadable: '', + }); + }); + + it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', done => { + Vue.nextTick(() => { + const $noTrackingPane = vm.$el.querySelector('.time-tracking-no-tracking-pane'); + const noTrackingText = $noTrackingPane.innerText; + const correctText = 'No estimate or time spent'; + + expect(vm.showNoTimeTrackingState).toBe(true); + expect($noTrackingPane).toBeVisible(); + expect(noTrackingText).toBe(correctText); + done(); + }); + }); + }); + + describe('Help pane', () => { + const helpButton = () => vm.$el.querySelector('.help-button'); + const closeHelpButton = () => vm.$el.querySelector('.close-help-button'); + const helpPane = () => vm.$el.querySelector('.time-tracking-help-state'); + + beforeEach(done => { + initTimeTrackingComponent({ timeEstimate: 0, timeSpent: 0 }); + + Vue.nextTick() + .then(done) + .catch(done.fail); + }); + + it('should not show the "Help" pane by default', () => { + expect(vm.showHelpState).toBe(false); + expect(helpPane()).toBeNull(); + }); + + it('should show the "Help" pane when help button is clicked', done => { + helpButton().click(); + + Vue.nextTick() + .then(() => { + expect(vm.showHelpState).toBe(true); + expect(helpPane()).toBeVisible(); + }) + .then(done) + .catch(done.fail); + }); + + it('should not show the "Help" pane when help button is clicked and then closed', done => { + helpButton().click(); + + Vue.nextTick() + .then(() => closeHelpButton().click()) + .then(() => Vue.nextTick()) + .then(() => { + expect(vm.showHelpState).toBe(false); + expect(helpPane()).toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + }); +}); diff --git a/spec/javascripts/sidebar/todo_spec.js b/spec/javascripts/sidebar/todo_spec.js deleted file mode 100644 index a929b804a29..00000000000 --- a/spec/javascripts/sidebar/todo_spec.js +++ /dev/null @@ -1,158 +0,0 @@ -import Vue from 'vue'; - -import SidebarTodos from '~/sidebar/components/todo_toggle/todo.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -const createComponent = ({ - issuableId = 1, - issuableType = 'epic', - isTodo, - isActionActive, - collapsed, -}) => { - const Component = Vue.extend(SidebarTodos); - - return mountComponent(Component, { - issuableId, - issuableType, - isTodo, - isActionActive, - collapsed, - }); -}; - -describe('SidebarTodo', () => { - let vm; - - beforeEach(() => { - vm = createComponent({}); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('computed', () => { - describe('buttonClasses', () => { - it('returns todo button classes for when `collapsed` prop is `false`', () => { - expect(vm.buttonClasses).toBe('btn btn-default btn-todo issuable-header-btn float-right'); - }); - - it('returns todo button classes for when `collapsed` prop is `true`', done => { - vm.collapsed = true; - Vue.nextTick() - .then(() => { - expect(vm.buttonClasses).toBe('btn-blank btn-todo sidebar-collapsed-icon dont-change-state'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('buttonLabel', () => { - it('returns todo button text for marking todo as done when `isTodo` prop is `true`', () => { - expect(vm.buttonLabel).toBe('Mark todo as done'); - }); - - it('returns todo button text for add todo when `isTodo` prop is `false`', done => { - vm.isTodo = false; - Vue.nextTick() - .then(() => { - expect(vm.buttonLabel).toBe('Add todo'); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('collapsedButtonIconClasses', () => { - it('returns collapsed button icon class when `isTodo` prop is `true`', () => { - expect(vm.collapsedButtonIconClasses).toBe('todo-undone'); - }); - - it('returns empty string when `isTodo` prop is `false`', done => { - vm.isTodo = false; - Vue.nextTick() - .then(() => { - expect(vm.collapsedButtonIconClasses).toBe(''); - }) - .then(done) - .catch(done.fail); - }); - }); - - describe('collapsedButtonIcon', () => { - it('returns button icon name when `isTodo` prop is `true`', () => { - expect(vm.collapsedButtonIcon).toBe('todo-done'); - }); - - it('returns button icon name when `isTodo` prop is `false`', done => { - vm.isTodo = false; - Vue.nextTick() - .then(() => { - expect(vm.collapsedButtonIcon).toBe('todo-add'); - }) - .then(done) - .catch(done.fail); - }); - }); - }); - - describe('methods', () => { - describe('handleButtonClick', () => { - it('emits `toggleTodo` event on component', () => { - spyOn(vm, '$emit'); - vm.handleButtonClick(); - expect(vm.$emit).toHaveBeenCalledWith('toggleTodo'); - }); - }); - }); - - describe('template', () => { - it('renders component container element', () => { - const dataAttributes = { - issuableId: '1', - issuableType: 'epic', - originalTitle: 'Mark todo as done', - placement: 'left', - container: 'body', - boundary: 'viewport', - }; - expect(vm.$el.nodeName).toBe('BUTTON'); - - const elDataAttrs = vm.$el.dataset; - Object.keys(elDataAttrs).forEach((attr) => { - expect(elDataAttrs[attr]).toBe(dataAttributes[attr]); - }); - }); - - it('renders button label element when `collapsed` prop is `false`', () => { - const buttonLabelEl = vm.$el.querySelector('span.issuable-todo-inner'); - expect(buttonLabelEl).not.toBeNull(); - expect(buttonLabelEl.innerText.trim()).toBe('Mark todo as done'); - }); - - it('renders button icon when `collapsed` prop is `true`', done => { - vm.collapsed = true; - Vue.nextTick() - .then(() => { - const buttonIconEl = vm.$el.querySelector('svg'); - expect(buttonIconEl).not.toBeNull(); - expect(buttonIconEl.querySelector('use').getAttribute('xlink:href')).toContain('todo-done'); - }) - .then(done) - .catch(done.fail); - }); - - it('renders loading icon when `isActionActive` prop is true', done => { - vm.isActionActive = true; - Vue.nextTick() - .then(() => { - const loadingEl = vm.$el.querySelector('span.loading-container'); - expect(loadingEl).not.toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index 0eff98bcc9d..bc00fdfd73c 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -6,6 +6,7 @@ import '~/commons'; import Vue from 'vue'; import VueResource from 'vue-resource'; import Translate from '~/vue_shared/translate'; +import jasmineDiff from 'jasmine-diff'; import { getDefaultAdapter } from '~/lib/utils/axios_utils'; import { FIXTURES_PATH, TEST_HOST } from './test_constants'; @@ -35,7 +36,15 @@ Vue.use(Translate); jasmine.getFixtures().fixturesPath = FIXTURES_PATH; jasmine.getJSONFixtures().fixturesPath = FIXTURES_PATH; -beforeAll(() => jasmine.addMatchers(customMatchers)); +beforeAll(() => { + jasmine.addMatchers( + jasmineDiff(jasmine, { + colors: true, + inline: true, + }), + ); + jasmine.addMatchers(customMatchers); +}); // globalize common libraries window.$ = $; @@ -166,13 +175,13 @@ if (process.env.BABEL_ENV === 'coverage') { ]; describe('Uncovered files', function() { - const sourceFiles = require.context('~', true, /\.js$/); + const sourceFiles = require.context('~', true, /\.(js|vue)$/); $.holdReady(true); sourceFiles.keys().forEach(function(path) { // ignore if there is a matching spec file - if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) { + if (testsContext.keys().indexOf(`${path.replace(/\.(js|vue)$/, '')}_spec`) > -1) { return; } diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js index 61b7bd2c226..8ac2f26979b 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js @@ -119,6 +119,7 @@ describe('MRWidgetHeader', () => { beforeEach(() => { vm = mountComponent(Component, { mr: { + iid: 1, divergedCommitsCount: 12, sourceBranch: 'mr-widget-refactor', sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>', @@ -130,6 +131,8 @@ describe('MRWidgetHeader', () => { emailPatchesPath: '/mr/email-patches', plainDiffPath: '/mr/plainDiffPath', statusPath: 'abc', + sourceProjectFullPath: 'root/gitlab-ce', + targetProjectFullPath: 'gitlab-org/gitlab-ce', }, }); }); @@ -146,16 +149,40 @@ describe('MRWidgetHeader', () => { const button = vm.$el.querySelector('.js-web-ide'); expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); + expect(button.getAttribute('href')).toEqual( + '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce', + ); }); - it('renders web ide button with relative URL', () => { + it('renders web ide button with blank query string if target & source project branch', done => { + vm.mr.targetProjectFullPath = 'root/gitlab-ce'; + + vm.$nextTick(() => { + const button = vm.$el.querySelector('.js-web-ide'); + + expect(button.textContent.trim()).toEqual('Open in Web IDE'); + expect(button.getAttribute('href')).toEqual( + '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=', + ); + + done(); + }); + }); + + it('renders web ide button with relative URL', done => { gon.relative_url_root = '/gitlab'; + vm.mr.iid = 2; - const button = vm.$el.querySelector('.js-web-ide'); + vm.$nextTick(() => { + const button = vm.$el.querySelector('.js-web-ide'); - expect(button.textContent.trim()).toEqual('Open in Web IDE'); - expect(button.getAttribute('href')).toEqual('/-/ide/projectabc'); + expect(button.textContent.trim()).toEqual('Open in Web IDE'); + expect(button.getAttribute('href')).toEqual( + '/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce', + ); + + done(); + }); }); it('renders download dropdown with links', () => { diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 9d2a15ff009..c0b5a7d4455 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -29,8 +29,10 @@ export default { source_branch: 'daaaa', source_branch_link: 'daaaa', source_project_id: 19, + source_project_full_path: '/group1/project1', target_branch: 'master', target_project_id: 19, + target_project_full_path: '/group2/project2', metrics: { merged_by: { name: 'Administrator', diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js index 73a69df019e..65d8ed39ade 100644 --- a/spec/javascripts/vue_shared/components/memory_graph_spec.js +++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js @@ -113,7 +113,7 @@ describe('MemoryGraph', () => { const circleEl = el.querySelector('circle'); expect(circleEl).toBeDefined(); expect(circleEl.getAttribute('r')).toBe('1.5'); - expect(circleEl.getAttribute('tranform')).toBe('translate(0 -1)'); + expect(circleEl.getAttribute('transform')).toBe('translate(0 -1)'); expect(circleEl.getAttribute('cx')).toBe(`${dotX}`); expect(circleEl.getAttribute('cy')).toBe(`${dotY}`); done(); diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 3a0688b7cbb..1f35d1e4880 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -379,6 +379,20 @@ describe Gitlab::ClosingIssueExtractor do .to match_array([issue, other_issue, third_issue]) end + it 'allows non-comma-separated issue numbers in single line message' do + message = "Closes #{reference} #{reference2} #{reference3}" + + expect(subject.closed_by_message(message)) + .to match_array([issue, other_issue, third_issue]) + end + + it 'allows mixed comma-separated and non-comma-separated issue numbers in single line message' do + message = "Closes #{reference}, #{reference2} and #{reference3}" + + expect(subject.closed_by_message(message)) + .to match_array([issue, other_issue, third_issue]) + end + it 'fetches issues in multi-line message' do message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}" diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index ec1a684cfbc..a8c5627e678 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -2,6 +2,11 @@ require "spec_helper" describe Gitlab::Git::Branch, seed_helper: true do let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:rugged) do + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repository.rugged + end + end subject { repository.branches } @@ -124,6 +129,7 @@ describe Gitlab::Git::Branch, seed_helper: true do it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } def create_commit - repository.create_commit(params.merge(committer: committer.merge(time: Time.now))) + params[:message].delete!("\r") + Rugged::Commit.create(rugged, params.merge(committer: committer.merge(time: Time.now))) end end diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 3bb0b5be15b..11ab376ab8f 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -27,6 +27,7 @@ EOT too_large: false } + # TODO use a Gitaly diff object instead @rugged_diff = Gitlab::GitalyClient::StorageSettings.allow_disk_access do repository.rugged.diff("5937ac0a7beb003549fc5fd26fc247adbce4a52e^", "5937ac0a7beb003549fc5fd26fc247adbce4a52e", paths: [".gitmodules"]).patches.first @@ -266,8 +267,12 @@ EOT describe '#submodule?' do before do - commit = repository.lookup('5937ac0a7beb003549fc5fd26fc247adbce4a52e') - @diffs = commit.parents[0].diff(commit).patches + # TODO use a Gitaly diff object instead + rugged_commit = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repository.rugged.rev_parse('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + end + + @diffs = rugged_commit.parents[0].diff(rugged_commit).patches end it { expect(described_class.new(@diffs[0]).submodule?).to eq(false) } diff --git a/spec/lib/gitlab/git/index_spec.rb b/spec/lib/gitlab/git/index_spec.rb index 16e6bd35449..e51b875be11 100644 --- a/spec/lib/gitlab/git/index_spec.rb +++ b/spec/lib/gitlab/git/index_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Git::Index, seed_helper: true do let(:index) { described_class.new(repository) } before do - index.read_tree(repository.lookup('master').tree) + index.read_tree(lookup('master').tree) end around do |example| @@ -30,7 +30,7 @@ describe Gitlab::Git::Index, seed_helper: true do entry = index.get(options[:file_path]) expect(entry).not_to be_nil - expect(repository.lookup(entry[:oid]).content).to eq(options[:content]) + expect(lookup(entry[:oid]).content).to eq(options[:content]) end end @@ -54,7 +54,7 @@ describe Gitlab::Git::Index, seed_helper: true do index.create(options) entry = index.get(options[:file_path]) - expect(repository.lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content])) + expect(lookup(entry[:oid]).content).to eq(Base64.decode64(options[:content])) end end @@ -68,7 +68,7 @@ describe Gitlab::Git::Index, seed_helper: true do index.create(options) entry = index.get(options[:file_path]) - expect(repository.lookup(entry[:oid]).content).to eq("Hello,\nWorld") + expect(lookup(entry[:oid]).content).to eq("Hello,\nWorld") end end end @@ -135,7 +135,7 @@ describe Gitlab::Git::Index, seed_helper: true do entry = index.get(options[:file_path]) - expect(repository.lookup(entry[:oid]).content).to eq(options[:content]) + expect(lookup(entry[:oid]).content).to eq(options[:content]) end it 'preserves file mode' do @@ -190,7 +190,7 @@ describe Gitlab::Git::Index, seed_helper: true do entry = index.get(options[:file_path]) expect(entry).not_to be_nil - expect(repository.lookup(entry[:oid]).content).to eq(options[:content]) + expect(lookup(entry[:oid]).content).to eq(options[:content]) end it 'preserves file mode' do @@ -232,4 +232,8 @@ describe Gitlab::Git::Index, seed_helper: true do end end end + + def lookup(revision) + repository.rugged.rev_parse(revision) + end end diff --git a/spec/lib/gitlab/git/popen_spec.rb b/spec/lib/gitlab/git/popen_spec.rb index b033ede9062..074e66d2a5d 100644 --- a/spec/lib/gitlab/git/popen_spec.rb +++ b/spec/lib/gitlab/git/popen_spec.rb @@ -2,6 +2,9 @@ require 'spec_helper' describe 'Gitlab::Git::Popen' do let(:path) { Rails.root.join('tmp').to_s } + let(:test_string) { 'The quick brown fox jumped over the lazy dog' } + # The pipe buffer is typically 64K. This string is about 440K. + let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] } let(:klass) do Class.new(Object) do @@ -70,6 +73,15 @@ describe 'Gitlab::Git::Popen' do end end end + + context 'with a process that writes a lot of data to stderr' do + it 'returns zero' do + output, status = klass.new.popen(spew_command, path) + + expect(output).to include(test_string) + expect(status).to eq(0) + end + end end context 'popen_with_timeout' do @@ -85,6 +97,17 @@ describe 'Gitlab::Git::Popen' do it { expect(output).to include('tests') } end + context 'multi-line string' do + let(:test_string) { "this is 1 line\n2nd line\n3rd line\n" } + let(:result) { klass.new.popen_with_timeout(['echo', test_string], timeout, path) } + let(:output) { result.first } + let(:status) { result.last } + + it { expect(status).to be_zero } + # echo adds its own line + it { expect(output).to eq(test_string + "\n") } + end + context 'non-zero status' do let(:result) { klass.new.popen_with_timeout(%w(cat NOTHING), timeout, path) } let(:output) { result.first } @@ -110,6 +133,13 @@ describe 'Gitlab::Git::Popen' do it "handles processes that do not shutdown correctly" do expect { klass.new.popen_with_timeout(['bash', '-c', "trap -- '' SIGTERM; sleep 1000"], timeout, path) }.to raise_error(Timeout::Error) end + + it 'handles process that writes a lot of data to stderr' do + output, status = klass.new.popen_with_timeout(spew_command, timeout, path) + + expect(output).to include(test_string) + expect(status).to eq(0) + end end context 'timeout period' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 4f8e6f29255..6480f6c407d 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -321,90 +321,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context '#submodules' do - around do |example| - # TODO #submodules will be removed, has been migrated to gitaly - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - example.run - end - end - - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } - - context 'where repo has submodules' do - let(:submodules) { repository.send(:submodules, 'master') } - let(:submodule) { submodules.first } - - it { expect(submodules).to be_kind_of Hash } - it { expect(submodules.empty?).to be_falsey } - - it 'should have valid data' do - expect(submodule).to eq([ - "six", { - "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", - "name" => "six", - "url" => "git://github.com/randx/six.git" - } - ]) - end - - it 'should handle nested submodules correctly' do - nested = submodules['nested/six'] - expect(nested['name']).to eq('nested/six') - expect(nested['url']).to eq('git://github.com/randx/six.git') - expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') - end - - it 'should handle deeply nested submodules correctly' do - nested = submodules['deeper/nested/six'] - expect(nested['name']).to eq('deeper/nested/six') - expect(nested['url']).to eq('git://github.com/randx/six.git') - expect(nested['id']).to eq('24fb71c79fcabc63dfd8832b12ee3bf2bf06b196') - end - - it 'should not have an entry for an invalid submodule' do - expect(submodules).not_to have_key('invalid/path') - end - - it 'should not have an entry for an uncommited submodule dir' do - submodules = repository.send(:submodules, 'fix-existing-submodule-dir') - expect(submodules).not_to have_key('submodule-existing-dir') - end - - it 'should handle tags correctly' do - submodules = repository.send(:submodules, 'v1.2.1') - - expect(submodules.first).to eq([ - "six", { - "id" => "409f37c4f05865e4fb208c771485f211a22c4c2d", - "name" => "six", - "url" => "git://github.com/randx/six.git" - } - ]) - end - - it 'should not break on invalid syntax' do - allow(repository).to receive(:blob_content).and_return(<<-GITMODULES.strip_heredoc) - [submodule "six"] - path = six - url = git://github.com/randx/six.git - - [submodule] - foo = bar - GITMODULES - - expect(submodules).to have_key('six') - end - end - - context 'where repo doesn\'t have submodules' do - let(:submodules) { repository.send(:submodules, '6d39438') } - it 'should return an empty hash' do - expect(submodules).to be_empty - end - end - end - describe '#commit_count' do shared_examples 'simple commit counting' do it { expect(repository.commit_count("master")).to eq(25) } @@ -611,21 +527,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - describe "#remove_remote" do - before(:all) do - @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') - @repo.remove_remote("expendable") - end - - it "should remove the remote" do - expect(@repo.rugged.remotes).not_to include("expendable") - end - - after(:all) do - ensure_seeds - end - end - describe "#remote_update" do before(:all) do @repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') @@ -633,7 +534,9 @@ describe Gitlab::Git::Repository, seed_helper: true do end it "should add the remote" do - expect(@repo.rugged.remotes["expendable"].url).to( + rugged = Gitlab::GitalyClient::StorageSettings.allow_disk_access { @repo.rugged } + + expect(rugged.remotes["expendable"].url).to( eq(TEST_NORMAL_REPO_PATH) ) end @@ -1157,6 +1060,13 @@ describe Gitlab::Git::Repository, seed_helper: true do @repo.rugged.config['core.autocrlf'] = true end + around do |example| + # OK because autocrlf is only used in gitaly-ruby + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + example.run + end + end + it 'return the value of the autocrlf option' do expect(@repo.autocrlf).to be(true) end @@ -1172,6 +1082,13 @@ describe Gitlab::Git::Repository, seed_helper: true do @repo.rugged.config['core.autocrlf'] = false end + around do |example| + # OK because autocrlf= is only used in gitaly-ruby + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + example.run + end + end + it 'should set the autocrlf option to the provided option' do @repo.autocrlf = :input @@ -1186,50 +1103,17 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe '#find_branch' do - shared_examples 'finding a branch' do - it 'should return a Branch for master' do - branch = repository.find_branch('master') + it 'should return a Branch for master' do + branch = repository.find_branch('master') - expect(branch).to be_a_kind_of(Gitlab::Git::Branch) - expect(branch.name).to eq('master') - end - - it 'should handle non-existent branch' do - branch = repository.find_branch('this-is-garbage') - - expect(branch).to eq(nil) - end + expect(branch).to be_a_kind_of(Gitlab::Git::Branch) + expect(branch.name).to eq('master') end - context 'when Gitaly find_branch feature is enabled' do - it_behaves_like 'finding a branch' - end - - context 'when Gitaly find_branch feature is disabled', :skip_gitaly_mock do - it_behaves_like 'finding a branch' - - context 'force_reload is true' do - it 'should reload Rugged::Repository' do - expect(Rugged::Repository).to receive(:new).twice.and_call_original - - repository.find_branch('master') - branch = repository.find_branch('master', force_reload: true) - - expect(branch).to be_a_kind_of(Gitlab::Git::Branch) - expect(branch.name).to eq('master') - end - end - - context 'force_reload is false' do - it 'should not reload Rugged::Repository' do - expect(Rugged::Repository).to receive(:new).once.and_call_original + it 'should handle non-existent branch' do + branch = repository.find_branch('this-is-garbage') - branch = repository.find_branch('master', force_reload: false) - - expect(branch).to be_a_kind_of(Gitlab::Git::Branch) - expect(branch.name).to eq('master') - end - end + expect(branch).to eq(nil) end end @@ -2042,54 +1926,61 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:repository) do Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') end + let(:rugged) do + Gitlab::GitalyClient::StorageSettings.allow_disk_access { repository.rugged } + end let(:remote_name) { 'my-remote' } + let(:url) { 'http://my-repo.git' } after do ensure_seeds end describe '#add_remote' do - let(:url) { 'http://my-repo.git' } let(:mirror_refmap) { '+refs/*:refs/*' } - it 'creates a new remote via Gitaly' do - expect_any_instance_of(Gitlab::GitalyClient::RemoteService) - .to receive(:add_remote).with(remote_name, url, mirror_refmap) + shared_examples 'add_remote' do + it 'added the remote' do + begin + rugged.remotes.delete(remote_name) + rescue Rugged::ConfigError + end - repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) + repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) + + expect(rugged.remotes[remote_name]).not_to be_nil + expect(rugged.config["remote.#{remote_name}.mirror"]).to eq('true') + expect(rugged.config["remote.#{remote_name}.prune"]).to eq('true') + expect(rugged.config["remote.#{remote_name}.fetch"]).to eq(mirror_refmap) + end end - context 'with Gitaly disabled', :skip_gitaly_mock do - it 'creates a new remote via Rugged' do - expect_any_instance_of(Rugged::RemoteCollection).to receive(:create) - .with(remote_name, url) - expect_any_instance_of(Rugged::Config).to receive(:[]=) - .with("remote.#{remote_name}.mirror", true) - expect_any_instance_of(Rugged::Config).to receive(:[]=) - .with("remote.#{remote_name}.prune", true) - expect_any_instance_of(Rugged::Config).to receive(:[]=) - .with("remote.#{remote_name}.fetch", mirror_refmap) + context 'using Gitaly' do + it_behaves_like 'add_remote' + end - repository.add_remote(remote_name, url, mirror_refmap: mirror_refmap) - end + context 'with Gitaly disabled', :disable_gitaly do + it_behaves_like 'add_remote' end end describe '#remove_remote' do - it 'removes the remote via Gitaly' do - expect_any_instance_of(Gitlab::GitalyClient::RemoteService) - .to receive(:remove_remote).with(remote_name) + shared_examples 'remove_remote' do + it 'removes the remote' do + rugged.remotes.create(remote_name, url) - repository.remove_remote(remote_name) + repository.remove_remote(remote_name) + + expect(rugged.remotes[remote_name]).to be_nil + end end - context 'with Gitaly disabled', :skip_gitaly_mock do - it 'removes the remote via Rugged' do - expect_any_instance_of(Rugged::RemoteCollection).to receive(:delete) - .with(remote_name) + context 'using Gitaly' do + it_behaves_like 'remove_remote' + end - repository.remove_remote(remote_name) - end + context 'with Gitaly disabled', :disable_gitaly do + it_behaves_like 'remove_remote' end end end @@ -2281,20 +2172,25 @@ describe Gitlab::Git::Repository, seed_helper: true do let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') } it 'cleans up the files' do - repository.with_worktree(worktree_path, 'master', env: ENV) do - FileUtils.touch(worktree_path, mtime: Time.now - 8.hours) - # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object, - # but the HEAD must be 40 characters long or git will ignore it. - File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA) + create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master] + raise 'preparation failed' unless system(*create_worktree, err: '/dev/null') - # git 2.16 fails with "fatal: bad object HEAD" - expect { repository.rev_list(including: :all) }.to raise_error(Gitlab::Git::Repository::GitError) + FileUtils.touch(worktree_path, mtime: Time.now - 8.hours) + # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object, + # but the HEAD must be 40 characters long or git will ignore it. + File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA) - repository.clean_stale_repository_files + # git 2.16 fails with "fatal: bad object HEAD" + expect(rev_list_all).to be false - expect { repository.rev_list(including: :all) }.not_to raise_error - expect(File.exist?(worktree_path)).to be_falsey - end + repository.clean_stale_repository_files + + expect(rev_list_all).to be true + expect(File.exist?(worktree_path)).to be_falsey + end + + def rev_list_all + system(*%W[git -C #{repository_path} rev-list --all], out: '/dev/null', err: '/dev/null') end it 'increments a counter upon an error' do diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index b752c3e8341..d9d405e1ccc 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -32,65 +32,4 @@ describe Gitlab::Git::RevList do expect(rev_list.new_refs).to eq(%w[sha1 sha2]) end end - - context '#new_objects' do - it 'fetches list of newly pushed objects using rev-list' do - stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") - - expect { |b| rev_list.new_objects(&b) }.to yield_with_args(%w[sha1 sha2]) - end - - it 'can skip pathless objects' do - stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2 path/to/file") - - expect { |b| rev_list.new_objects(require_path: true, &b) }.to yield_with_args(%w[sha2]) - end - - it 'can handle non utf-8 paths' do - non_utf_char = [0x89].pack("c*").force_encoding("UTF-8") - stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha2 πå†h/†ø/ƒîlé#{non_utf_char}\nsha1") - - rev_list.new_objects(require_path: true) do |object_ids| - expect(object_ids.force).to eq(%w[sha2]) - end - end - - it 'can yield a lazy enumerator' do - stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") - - rev_list.new_objects do |object_ids| - expect(object_ids).to be_a Enumerator::Lazy - end - end - - it 'returns the result of the block when given' do - stub_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") - - objects = rev_list.new_objects do |object_ids| - object_ids.first - end - - expect(objects).to eq 'sha1' - end - - it 'can accept list of references to exclude' do - stub_popen_rev_list('newrev', '--not', 'master', '--objects', output: "sha1\nsha2") - - expect { |b| rev_list.new_objects(not_in: ['master'], &b) }.to yield_with_args(%w[sha1 sha2]) - end - - it 'handles empty list of references to exclude as listing all known objects' do - stub_popen_rev_list('newrev', '--objects', output: "sha1\nsha2") - - expect { |b| rev_list.new_objects(not_in: [], &b) }.to yield_with_args(%w[sha1 sha2]) - end - end - - context '#all_objects' do - it 'fetches list of all pushed objects using rev-list' do - stub_popen_rev_list('--all', '--objects', output: "sha1\nsha2") - - expect { |b| rev_list.all_objects(&b) }.to yield_with_args(%w[sha1 sha2]) - end - end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 832db2bd906..dbd64c4bec0 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -734,26 +734,18 @@ describe Gitlab::GitAccess do merge_into_protected_branch: "0b4bc9a #{merge_into_protected_branch} refs/heads/feature" } end - def stub_git_hooks - # Running the `pre-receive` hook is expensive, and not necessary for this test. - allow_any_instance_of(Gitlab::Git::HooksService).to receive(:execute) do |service, &block| - block.call(service) - end - end - def merge_into_protected_branch @protected_branch_merge_commit ||= begin Gitlab::GitalyClient::StorageSettings.allow_disk_access do - stub_git_hooks project.repository.add_branch(user, unprotected_branch, 'feature') - target_branch = project.repository.lookup('feature') + rugged = project.repository.rugged + target_branch = rugged.rev_parse('feature') source_branch = project.repository.create_file( user, 'filename', 'This is the file content', message: 'This is a good commit message', branch_name: unprotected_branch) - rugged = project.repository.rugged author = { email: "email@example.com", time: Time.now, name: "Example Git User" } merge_index = rugged.merge_commits(target_branch, source_branch) diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 2223f163177..90e6d653d34 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -9,6 +9,7 @@ describe Gitlab::ImportExport::AvatarSaver do before do FileUtils.mkdir_p("#{shared.export_path}/avatar/") allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + stub_feature_flags(import_export_object_storage: false) end after do diff --git a/spec/lib/gitlab/import_export/uploads_manager_spec.rb b/spec/lib/gitlab/import_export/uploads_manager_spec.rb new file mode 100644 index 00000000000..9c3870a0af8 --- /dev/null +++ b/spec/lib/gitlab/import_export/uploads_manager_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::UploadsManager do + let(:shared) { project.import_export_shared } + let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } + let(:project) { create(:project) } + let(:exported_file_path) { "#{shared.export_path}/uploads/#{upload.secret}/#{File.basename(upload.path)}" } + + subject(:manager) { described_class.new(project: project, shared: shared) } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + FileUtils.mkdir_p(shared.export_path) + end + + after do + FileUtils.rm_rf(shared.export_path) + end + + describe '#save' do + context 'when the project has uploads locally stored' do + let(:upload) { create(:upload, :issuable_upload, :with_file, model: project) } + + before do + project.uploads << upload + end + + it 'does not cause errors' do + manager.save + + expect(shared.errors).to be_empty + end + + it 'copies the file in the correct location when there is an upload' do + manager.save + + expect(File).to exist(exported_file_path) + end + end + + context 'using object storage' do + let!(:upload) { create(:upload, :issuable_upload, :object_storage, model: project) } + + before do + stub_feature_flags(import_export_object_storage: true) + stub_uploads_object_storage(FileUploader) + end + + it 'saves the file' do + fake_uri = double + + expect(fake_uri).to receive(:open).and_return(StringIO.new('File content')) + expect(URI).to receive(:parse).and_return(fake_uri) + + manager.save + + expect(File.read(exported_file_path)).to eq('File content') + end + end + + describe '#restore' do + context 'using object storage' do + before do + stub_feature_flags(import_export_object_storage: true) + stub_uploads_object_storage(FileUploader) + + FileUtils.mkdir_p(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038')) + FileUtils.touch(File.join(shared.export_path, 'uploads/72a497a02fe3ee09edae2ed06d390038', "dummy.txt")) + end + + it 'restores the file' do + manager.restore + + expect(project.uploads.size).to eq(1) + expect(project.uploads.first.build_uploader.filename).to eq('dummy.txt') + end + end + end + end +end diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 095687fa89d..c716edd9397 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::ImportExport::UploadsSaver do let(:shared) { project.import_export_shared } before do + stub_feature_flags(import_export_object_storage: false) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) end @@ -30,7 +31,7 @@ describe Gitlab::ImportExport::UploadsSaver do it 'copies the uploads to the export path' do saver.save - uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) } + uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) } expect(uploads).to include('banana_sample.gif') end @@ -52,7 +53,7 @@ describe Gitlab::ImportExport::UploadsSaver do it 'copies the uploads to the export path' do saver.save - uploads = Dir.glob(File.join(saver.uploads_export_path, '**/*')).map { |file| File.basename(file) } + uploads = Dir.glob(File.join(shared.export_path, 'uploads/**/*')).map { |file| File.basename(file) } expect(uploads).to include('banana_sample.gif') end diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb index 10341486512..25827423914 100644 --- a/spec/lib/gitlab/import_sources_spec.rb +++ b/spec/lib/gitlab/import_sources_spec.rb @@ -12,7 +12,8 @@ describe Gitlab::ImportSources do 'FogBugz' => 'fogbugz', 'Repo by URL' => 'git', 'GitLab export' => 'gitlab_project', - 'Gitea' => 'gitea' + 'Gitea' => 'gitea', + 'Manifest file' => 'manifest' } expect(described_class.options).to eq(expected) @@ -31,6 +32,7 @@ describe Gitlab::ImportSources do git gitlab_project gitea + manifest ) expect(described_class.values).to eq(expected) @@ -63,7 +65,8 @@ describe Gitlab::ImportSources do 'fogbugz' => Gitlab::FogbugzImport::Importer, 'git' => nil, 'gitlab_project' => Gitlab::ImportExport::Importer, - 'gitea' => Gitlab::LegacyGithubImport::Importer + 'gitea' => Gitlab::LegacyGithubImport::Importer, + 'manifest' => nil } import_sources.each do |name, klass| @@ -82,7 +85,8 @@ describe Gitlab::ImportSources do 'fogbugz' => 'FogBugz', 'git' => 'Repo by URL', 'gitlab_project' => 'GitLab export', - 'gitea' => 'Gitea' + 'gitea' => 'Gitea', + 'manifest' => 'Manifest file' } import_sources.each do |name, title| diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb new file mode 100644 index 00000000000..ab305fb2316 --- /dev/null +++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::ManifestImport::Manifest, :postgresql do + let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) } + let(:manifest) { described_class.new(file) } + + describe '#valid?' do + context 'valid file' do + it { expect(manifest.valid?).to be true } + end + + context 'missing or invalid attributes' do + let(:file) { Tempfile.new('foo') } + + before do + content = <<~EOS + <manifest> + <remote review="invalid-url" /> + <project name="platform/build"/> + </manifest> + EOS + + file.write(content) + file.rewind + end + + it { expect(manifest.valid?).to be false } + + describe 'errors' do + before do + manifest.valid? + end + + it { expect(manifest.errors).to include('Make sure a <remote> tag is present and is valid.') } + it { expect(manifest.errors).to include('Make sure every <project> tag has name and path attributes.') } + end + end + end + + describe '#projects' do + it { expect(manifest.projects.size).to eq(660) } + it { expect(manifest.projects[0][:name]).to eq('platform/build') } + it { expect(manifest.projects[0][:path]).to eq('build/make') } + it { expect(manifest.projects[0][:url]).to eq('https://android-review.googlesource.com/platform/build') } + end +end diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb new file mode 100644 index 00000000000..1d01d437535 --- /dev/null +++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Gitlab::ManifestImport::ProjectCreator, :postgresql do + let(:group) { create(:group) } + let(:user) { create(:user) } + let(:repository) do + { + path: 'device/common', + url: 'https://android-review.googlesource.com/device/common' + } + end + + before do + group.add_owner(user) + end + + subject { described_class.new(repository, group, user) } + + describe '#execute' do + it { expect(subject.execute).to be_a(Project) } + it { expect { subject.execute }.to change { Project.count }.by(1) } + it { expect { subject.execute }.to change { Group.count }.by(1) } + + it 'creates project with valid full path and import url' do + subject.execute + + project = Project.last + + expect(project.full_path).to eq(File.join(group.path, 'device/common')) + expect(project.import_url).to eq('https://android-review.googlesource.com/device/common') + end + end +end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 1dbead16d5b..c1b84e9f077 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -55,6 +55,19 @@ describe Gitlab::Popen do end end + context 'with a process that writes a lot of data to stderr' do + let(:test_string) { 'The quick brown fox jumped over the lazy dog' } + # The pipe buffer is typically 64K. This string is about 440K. + let(:spew_command) { ['bash', '-c', "for i in {1..10000}; do echo '#{test_string}' 1>&2; done"] } + + it 'returns zero' do + output, status = @klass.new.popen(spew_command, path) + + expect(output).to include(test_string) + expect(status).to eq(0) + end + end + context 'without a directory argument' do before do @output, @status = @klass.new.popen(%w(ls)) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3c96fe76829..ee923374480 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1613,6 +1613,7 @@ describe Ci::Build do { key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true }, { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, + { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true }, { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true }, { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true }, { key: 'CI_BUILD_REF', value: build.sha, public: true }, diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index e01906f4b6c..b335e0fbeb3 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -157,22 +157,4 @@ describe Deployment do end end end - - describe '#stop_action?' do - subject { deployment.stop_action? } - - context 'when no other actions' do - let(:deployment) { build(:deployment) } - - it { is_expected.to be_falsey } - end - - context 'when matching action is defined' do - let(:build) { create(:ci_build) } - let(:deployment) { FactoryBot.build(:deployment, deployable: build, on_stop: 'close_app') } - let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } - - it { is_expected.to be_truthy } - end - end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 4bded9efe91..c65e0b81451 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -170,8 +170,8 @@ describe Environment do end end - describe '#stop_action?' do - subject { environment.stop_action? } + describe '#stop_action_available?' do + subject { environment.stop_action_available? } context 'when no other actions' do it { is_expected.to be_falsey } @@ -179,8 +179,17 @@ describe Environment do context 'when matching action is defined' do let(:build) { create(:ci_build) } - let!(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') } - let!(:close_action) { create(:ci_build, :manual, pipeline: build.pipeline, name: 'close_app') } + + let!(:deployment) do + create(:deployment, environment: environment, + deployable: build, + on_stop: 'close_app') + end + + let!(:close_action) do + create(:ci_build, :manual, pipeline: build.pipeline, + name: 'close_app') + end context 'when environment is available' do before do diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index d7c5f26ab67..77c475b9f52 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -93,4 +93,10 @@ RSpec.describe NotificationSetting do end end end + + context 'email events' do + it 'includes EXCLUDED_WATCHER_EVENTS in EMAIL_EVENTS' do + expect(described_class::EMAIL_EVENTS).to include(*described_class::EXCLUDED_WATCHER_EVENTS) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c01c7bc47b5..d200e5f2e42 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -149,23 +149,25 @@ describe Project do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } it { is_expected.to validate_length_of(:name).is_at_most(255) } - it { is_expected.to validate_presence_of(:path) } it { is_expected.to validate_length_of(:path).is_at_most(255) } - it { is_expected.to validate_length_of(:description).is_at_most(2000) } - it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) } it { is_expected.to allow_value('').for(:ci_config_path) } it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) } it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) } - it { is_expected.to validate_presence_of(:creator) } - it { is_expected.to validate_presence_of(:namespace) } - it { is_expected.to validate_presence_of(:repository_storage) } + it 'validates build timeout constraints' do + is_expected.to validate_numericality_of(:build_timeout) + .only_integer + .is_greater_than_or_equal_to(10.minutes) + .is_less_than(1.month) + .with_message('needs to be beetween 10 minutes and 1 month') + end + it 'does not allow new projects beyond user limits' do project2 = build(:project) allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index a3c20b3b3c1..a544940800a 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,3 +1,4 @@ +# coding: utf-8 require "spec_helper" describe ProjectWiki do @@ -10,7 +11,6 @@ describe ProjectWiki do subject { project_wiki } - it { is_expected.to delegate_method(:empty?).to :pages } it { is_expected.to delegate_method(:repository_storage).to :project } it { is_expected.to delegate_method(:hashed_storage?).to :project } @@ -92,11 +92,19 @@ describe ProjectWiki do context "when the wiki has pages" do before do project_wiki.create_page("index", "This is an awesome new Gollum Wiki") + project_wiki.create_page("another-page", "This is another page") end describe '#empty?' do subject { super().empty? } it { is_expected.to be_falsey } + + # Re-enable this when https://gitlab.com/gitlab-org/gitaly/issues/1204 is fixed + xit 'only instantiates a Wiki page once' do + expect(WikiPage).to receive(:new).once.and_call_original + + subject + end end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index caf5d829d21..02d31098cfd 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -151,7 +151,9 @@ describe Repository do it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) } after do - repository.rugged.tags.delete(annotated_tag_name) + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + repository.rugged.tags.delete(annotated_tag_name) + end end end end @@ -1020,24 +1022,6 @@ describe Repository do end end - describe '#find_branch' do - context 'fresh_repo is true' do - it 'delegates the call to raw_repository' do - expect(repository.raw_repository).to receive(:find_branch).with('master', true) - - repository.find_branch('master', fresh_repo: true) - end - end - - context 'fresh_repo is false' do - it 'delegates the call to raw_repository' do - expect(repository.raw_repository).to receive(:find_branch).with('master', false) - - repository.find_branch('master', fresh_repo: false) - end - end - end - describe '#update_branch_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev @@ -2231,8 +2215,11 @@ describe Repository do create_remote_branch('joe', 'remote_branch', masterrev) repository.add_branch(user, 'local_branch', masterrev.id) - expect(repository.remote_branches('joe').any? { |branch| branch.name == 'local_branch' }).to eq(false) - expect(repository.remote_branches('joe').any? { |branch| branch.name == 'remote_branch' }).to eq(true) + # TODO: move this test to gitaly https://gitlab.com/gitlab-org/gitaly/issues/1243 + Gitlab::GitalyClient::StorageSettings.allow_disk_access do + expect(repository.remote_branches('joe').any? { |branch| branch.name == 'local_branch' }).to eq(false) + expect(repository.remote_branches('joe').any? { |branch| branch.name == 'remote_branch' }).to eq(true) + end end end diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index f29abcf536e..bd498269798 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -7,7 +7,6 @@ describe Todo do it { is_expected.to belong_to(:author).class_name("User") } it { is_expected.to belong_to(:note) } it { is_expected.to belong_to(:project) } - it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:target).touch(true) } it { is_expected.to belong_to(:user) } end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 113703fac38..246947e58a8 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -514,6 +514,38 @@ describe API::Commits do expect(response).to have_gitlab_http_status(400) end end + + context 'when committing into a fork as a maintainer' do + include_context 'merge request allowing collaboration' + + let(:project_id) { forked_project.id } + + def push_params(branch_name) + { + branch: branch_name, + commit_message: 'Hello world', + actions: [ + { + action: 'create', + file_path: 'foo/bar/baz.txt', + content: 'puts 8' + } + ] + } + end + + it 'allows pushing to the source branch of the merge request' do + post api(url, user), push_params('feature') + + expect(response).to have_gitlab_http_status(:created) + end + + it 'denies pushing to another branch' do + post api(url, user), push_params('other-branch') + + expect(response).to have_gitlab_http_status(:forbidden) + end + end end describe 'GET /projects/:id/repository/commits/:sha/refs' do @@ -1065,11 +1097,29 @@ describe API::Commits do it 'returns 400 if you are not allowed to push to the target branch' do post api(route, current_user), branch: 'feature' - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']).to eq('You are not allowed to push into this branch') + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to match(/You are not allowed to push into this branch/) end end end + + context 'when cherry picking to a fork as a maintainer' do + include_context 'merge request allowing collaboration' + + let(:project_id) { forked_project.id } + + it 'allows access from a maintainer that to the source branch' do + post api(route, user), branch: 'feature' + + expect(response).to have_gitlab_http_status(:created) + end + + it 'denies cherry picking to another branch' do + post api(route, user), branch: 'master' + + expect(response).to have_gitlab_http_status(:forbidden) + end + end end describe 'POST /projects/:id/repository/commits/:sha/comments' do diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index d2072198d83..0ba2539a717 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -11,6 +11,21 @@ describe MergeRequestWidgetEntity do described_class.new(resource, request: request).as_json end + describe 'source_project_full_path' do + it 'includes the full path of the source project' do + expect(subject[:source_project_full_path]).to be_present + end + + context 'when the source project is missing' do + it 'returns `nil` for the source project' do + resource.allow_broken = true + resource.update!(source_project: nil) + + expect(subject[:source_project_full_path]).to be_nil + end + end + end + describe 'pipeline' do let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) } diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb new file mode 100644 index 00000000000..05424d08b9d --- /dev/null +++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb @@ -0,0 +1,15 @@ +shared_context 'merge request allowing collaboration' do + include ProjectForksHelper + + let(:canonical) { create(:project, :public, :repository) } + let(:forked_project) { fork_project(canonical, nil, repository: true) } + + before do + canonical.add_maintainer(user) + create(:merge_request, + target_project: canonical, + source_project: forked_project, + source_branch: 'feature', + allow_collaboration: true) + end +end diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb deleted file mode 100644 index bafd9bac8d0..00000000000 --- a/spec/support/shared_examples/controllers/todos_shared_examples.rb +++ /dev/null @@ -1,43 +0,0 @@ -shared_examples 'todos actions' do - context 'when authorized' do - before do - sign_in(user) - parent.add_developer(user) - end - - it 'creates todo' do - expect do - post_create - end.to change { user.todos.count }.by(1) - - expect(response).to have_gitlab_http_status(200) - end - - it 'returns todo path and pending count' do - post_create - - expect(response).to have_gitlab_http_status(200) - expect(json_response['count']).to eq 1 - expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}}) - end - end - - context 'when not authorized for project/group' do - it 'does not create todo for resource that user has no access to' do - sign_in(user) - expect do - post_create - end.to change { user.todos.count }.by(0) - - expect(response).to have_gitlab_http_status(404) - end - - it 'does not create todo when user is not logged in' do - expect do - post_create - end.to change { user.todos.count }.by(0) - - expect(response).to have_gitlab_http_status(parent.is_a?(Group) ? 401 : 302) - end - end -end diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb index 79b2196660c..1b563021244 100644 --- a/spec/support/shared_examples/requests/api/notes.rb +++ b/spec/support/shared_examples/requests/api/notes.rb @@ -121,6 +121,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name| expect(json_response['body']).to eq('hi!') expect(json_response['author']['username']).to eq(user.username) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) + expect(Time.parse(json_response['updated_at'])).to be_like_time(creation_time) end end diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index 7ba28b4fc1f..3efe512a59c 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -124,6 +124,15 @@ describe FileUploader do end end + describe '.extract_dynamic_path' do + it 'works with hashed storage' do + path = 'export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/72a497a02fe3ee09edae2ed06d390038/dummy.txt' + + expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt') + expect(described_class.extract_dynamic_path(path)[:secret]).to eq('72a497a02fe3ee09edae2ed06d390038') + end + end + describe '#secret' do it 'generates a secret if none is provided' do expect(described_class).to receive(:generate_secret).and_return('secret') diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index d5808e21271..30e67e67e0e 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -209,12 +209,7 @@ describe GitGarbageCollectWorker do tree: old_commit.tree, parents: [old_commit] ) - Gitlab::Git::OperationService.new(nil, project.repository.raw_repository).send( - :update_ref, - "refs/heads/#{SecureRandom.hex(6)}", - new_commit_sha, - Gitlab::Git::BLANK_SHA - ) + rugged.references.create("refs/heads/#{SecureRandom.hex(6)}", new_commit_sha) end def packs(project) diff --git a/yarn.lock b/yarn.lock index d844ae4f8e9..9d0eae5f509 100644 --- a/yarn.lock +++ b/yarn.lock @@ -104,139 +104,140 @@ source-map "^0.5.6" vue-template-es2015-compiler "^1.6.0" -"@webassemblyjs/ast@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.10.tgz#7f1e81149ca4e103c9e7cc321ea0dcb83a392512" +"@webassemblyjs/ast@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25" dependencies: - "@webassemblyjs/helper-module-context" "1.5.10" - "@webassemblyjs/helper-wasm-bytecode" "1.5.10" - "@webassemblyjs/wast-parser" "1.5.10" + "@webassemblyjs/helper-module-context" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/wast-parser" "1.5.13" debug "^3.1.0" mamacro "^0.0.3" -"@webassemblyjs/floating-point-hex-parser@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.10.tgz#ae48705fd58927df62023f114520b8215330ff86" +"@webassemblyjs/floating-point-hex-parser@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz#29ce0baa97411f70e8cce68ce9c0f9d819a4e298" -"@webassemblyjs/helper-api-error@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.10.tgz#0baf9453ce2fd8db58f0fdb4fb2852557c71d5a7" +"@webassemblyjs/helper-api-error@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz#e49b051d67ee19a56e29b9aa8bd949b5b4442a59" -"@webassemblyjs/helper-buffer@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.10.tgz#abee4284161e9cd6ba7619785ca277bfcb8052ce" +"@webassemblyjs/helper-buffer@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz#873bb0a1b46449231137c1262ddfd05695195a1e" dependencies: debug "^3.1.0" -"@webassemblyjs/helper-code-frame@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.10.tgz#4e23c05431665f16322104580af7c06253d4b4e0" +"@webassemblyjs/helper-code-frame@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz#1bd2181b6a0be14e004f0fe9f5a660d265362b58" dependencies: - "@webassemblyjs/wast-printer" "1.5.10" + "@webassemblyjs/wast-printer" "1.5.13" -"@webassemblyjs/helper-fsm@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.10.tgz#490bab613ea255a9272b764826d3cc9d15170676" +"@webassemblyjs/helper-fsm@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz#cdf3d9d33005d543a5c5e5adaabf679ffa8db924" -"@webassemblyjs/helper-module-context@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.10.tgz#6fca93585228bf33e6da076d0a1373db1fdd6580" +"@webassemblyjs/helper-module-context@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz#dc29ddfb51ed657655286f94a5d72d8a489147c5" dependencies: + debug "^3.1.0" mamacro "^0.0.3" -"@webassemblyjs/helper-wasm-bytecode@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.10.tgz#90f6da93c7a186bfb2f587de442982ff533c4b44" +"@webassemblyjs/helper-wasm-bytecode@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz#03245817f0a762382e61733146f5773def15a747" -"@webassemblyjs/helper-wasm-section@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.10.tgz#d64292a19f7f357c49719461065efdf7ec975d66" +"@webassemblyjs/helper-wasm-section@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz#efc76f44a10d3073b584b43c38a179df173d5c7d" dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/helper-buffer" "1.5.10" - "@webassemblyjs/helper-wasm-bytecode" "1.5.10" - "@webassemblyjs/wasm-gen" "1.5.10" + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-buffer" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/wasm-gen" "1.5.13" debug "^3.1.0" -"@webassemblyjs/ieee754@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.10.tgz#257cad440dd6c8a339402d31e035ba2e38e9c245" +"@webassemblyjs/ieee754@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz#573e97c8c12e4eebb316ca5fde0203ddd90b0364" dependencies: ieee754 "^1.1.11" -"@webassemblyjs/leb128@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.10.tgz#a8e4fe5f4b16daadb241fcc44d9735e9f27b05a3" +"@webassemblyjs/leb128@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.13.tgz#ab52ebab9cec283c1c1897ac1da833a04a3f4cee" dependencies: - leb "^0.3.0" + long "4.0.0" -"@webassemblyjs/utf8@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.10.tgz#0b3b6bc86b7619c5dc7b2789db6665aa35689983" +"@webassemblyjs/utf8@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.13.tgz#6b53d2cd861cf94fa99c1f12779dde692fbc2469" -"@webassemblyjs/wasm-edit@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.10.tgz#0fe80f19e57f669eab1caa8c1faf9690b259d5b9" +"@webassemblyjs/wasm-edit@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz#c9cef5664c245cf11b3b3a73110c9155831724a8" dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/helper-buffer" "1.5.10" - "@webassemblyjs/helper-wasm-bytecode" "1.5.10" - "@webassemblyjs/helper-wasm-section" "1.5.10" - "@webassemblyjs/wasm-gen" "1.5.10" - "@webassemblyjs/wasm-opt" "1.5.10" - "@webassemblyjs/wasm-parser" "1.5.10" - "@webassemblyjs/wast-printer" "1.5.10" + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-buffer" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/helper-wasm-section" "1.5.13" + "@webassemblyjs/wasm-gen" "1.5.13" + "@webassemblyjs/wasm-opt" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" + "@webassemblyjs/wast-printer" "1.5.13" debug "^3.1.0" -"@webassemblyjs/wasm-gen@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.10.tgz#8b29ddd3651259408ae5d5c816a011fb3f3f3584" +"@webassemblyjs/wasm-gen@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz#8e6ea113c4b432fa66540189e79b16d7a140700e" dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/helper-wasm-bytecode" "1.5.10" - "@webassemblyjs/ieee754" "1.5.10" - "@webassemblyjs/leb128" "1.5.10" - "@webassemblyjs/utf8" "1.5.10" + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/ieee754" "1.5.13" + "@webassemblyjs/leb128" "1.5.13" + "@webassemblyjs/utf8" "1.5.13" -"@webassemblyjs/wasm-opt@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.10.tgz#569e45ab1b2bf0a7706cdf6d1b51d1188e9e4c7b" +"@webassemblyjs/wasm-opt@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz#147aad7717a7ee4211c36b21a5f4c30dddf33138" dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/helper-buffer" "1.5.10" - "@webassemblyjs/wasm-gen" "1.5.10" - "@webassemblyjs/wasm-parser" "1.5.10" + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-buffer" "1.5.13" + "@webassemblyjs/wasm-gen" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" debug "^3.1.0" -"@webassemblyjs/wasm-parser@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.10.tgz#3e1017e49f833f46b840db7cf9d194d4f00037ff" - dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/helper-api-error" "1.5.10" - "@webassemblyjs/helper-wasm-bytecode" "1.5.10" - "@webassemblyjs/ieee754" "1.5.10" - "@webassemblyjs/leb128" "1.5.10" - "@webassemblyjs/wasm-parser" "1.5.10" - -"@webassemblyjs/wast-parser@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.10.tgz#1a3235926483c985a00ee8ebca856ffda9544934" - dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/floating-point-hex-parser" "1.5.10" - "@webassemblyjs/helper-api-error" "1.5.10" - "@webassemblyjs/helper-code-frame" "1.5.10" - "@webassemblyjs/helper-fsm" "1.5.10" +"@webassemblyjs/wasm-parser@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz#6f46516c5bb23904fbdf58009233c2dd8a54c72f" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-api-error" "1.5.13" + "@webassemblyjs/helper-wasm-bytecode" "1.5.13" + "@webassemblyjs/ieee754" "1.5.13" + "@webassemblyjs/leb128" "1.5.13" + "@webassemblyjs/utf8" "1.5.13" + +"@webassemblyjs/wast-parser@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz#5727a705d397ae6a3ae99d7f5460acf2ec646eea" + dependencies: + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/floating-point-hex-parser" "1.5.13" + "@webassemblyjs/helper-api-error" "1.5.13" + "@webassemblyjs/helper-code-frame" "1.5.13" + "@webassemblyjs/helper-fsm" "1.5.13" long "^3.2.0" mamacro "^0.0.3" -"@webassemblyjs/wast-printer@1.5.10": - version "1.5.10" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.10.tgz#adb38831ba45efd0a5c7971b666e179b64f68bba" +"@webassemblyjs/wast-printer@1.5.13": + version "1.5.13" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz#bb34d528c14b4f579e7ec11e793ec50ad7cd7c95" dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/wast-parser" "1.5.10" + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/wast-parser" "1.5.13" long "^3.2.0" abbrev@1: @@ -274,6 +275,10 @@ 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" +acorn@^5.6.2: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" + addressparser@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" @@ -282,12 +287,11 @@ after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" -agent-base@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7" +agent-base@4, agent-base@^4.1.0, agent-base@^4.2.0, agent-base@~4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" dependencies: - extend "~3.0.0" - semver "~5.0.1" + es6-promisify "^5.0.0" ajv-keywords@^2.1.0: version "2.1.1" @@ -322,10 +326,6 @@ align-text@^0.1.1, align-text@^0.1.3: longest "^1.0.1" repeat-string "^1.5.2" -alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -372,13 +372,6 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -409,17 +402,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - dependencies: - arr-flatten "^1.0.1" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" -arr-flatten@^1.0.1, arr-flatten@^1.1.0: +arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" @@ -532,11 +519,11 @@ async@^2.0.0, async@^2.1.4: dependencies: lodash "^4.14.0" -async@~2.1.2: - version "2.1.5" - resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" +async@~2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: - lodash "^4.14.0" + lodash "^4.17.10" asynckit@^0.4.0: version "0.4.0" @@ -546,17 +533,6 @@ atob@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" -autoprefixer@^6.3.1: - version "6.7.7" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" - dependencies: - browserslist "^1.7.6" - caniuse-db "^1.0.30000634" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^5.2.16" - postcss-value-parser "^3.2.3" - autosize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/autosize/-/autosize-4.0.0.tgz#7a0599b1ba84d73bd7589b0d9da3870152c69237" @@ -766,9 +742,9 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-loader@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.4.tgz#e3463938bd4e6d55d1c174c5485d406a188ed015" +babel-loader@^7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" dependencies: find-cache-dir "^1.0.0" loader-utils "^1.0.2" @@ -1202,10 +1178,6 @@ backo2@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" -balanced-match@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -1374,14 +1346,6 @@ braces@^0.1.2: dependencies: expand-range "^0.1.0" -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - braces@^2.3.0, braces@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb" @@ -1455,13 +1419,6 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: - version "1.7.7" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" - dependencies: - caniuse-db "^1.0.30000639" - electron-to-chromium "^1.2.7" - buffer-from@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" @@ -1600,19 +1557,6 @@ camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" -caniuse-api@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" - dependencies: - browserslist "^1.3.6" - caniuse-db "^1.0.30000529" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000649" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000649.tgz#1ee1754a6df235450c8b7cd15e0ebf507221a86a" - capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" @@ -1654,6 +1598,10 @@ chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" +chardet@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" + "charenc@>= 0.0.1": version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -1666,24 +1614,27 @@ check-types@^7.3.0: version "7.3.0" resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.3.0.tgz#468f571a4435c24248f5fd0cb0e8d87c3c341e7d" -chokidar@^1.4.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" +chokidar@^2.0.0, chokidar@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" dependencies: - anymatch "^1.3.0" + anymatch "^2.0.0" async-each "^1.0.0" - glob-parent "^2.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" inherits "^2.0.1" is-binary-path "^1.0.0" - is-glob "^2.0.0" + is-glob "^4.0.0" + normalize-path "^2.1.1" path-is-absolute "^1.0.0" readdirp "^2.0.0" + upath "^1.0.0" optionalDependencies: fsevents "^1.0.0" -chokidar@^2.0.0, chokidar@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" +chokidar@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" dependencies: anymatch "^2.0.0" async-each "^1.0.0" @@ -1692,20 +1643,23 @@ chokidar@^2.0.0, chokidar@^2.0.2: inherits "^2.0.1" is-binary-path "^1.0.0" is-glob "^4.0.0" + lodash.debounce "^4.0.8" normalize-path "^2.1.1" path-is-absolute "^1.0.0" readdirp "^2.0.0" - upath "^1.0.0" + upath "^1.0.5" optionalDependencies: - fsevents "^1.0.0" + fsevents "^1.2.2" chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" -chrome-trace-event@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-0.1.2.tgz#90f36885d5345a50621332f0717b595883d5d982" +chrome-trace-event@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" + dependencies: + tslib "^1.9.0" cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -1718,15 +1672,9 @@ circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" -circular-json@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.1.tgz#b8942a09e535863dc21b04417a91971e1d9cd91f" - -clap@^1.0.9: - version "1.1.3" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b" - dependencies: - chalk "^1.1.3" +circular-json@^0.5.4: + version "0.5.5" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.5.tgz#64182ef359042d37cd8e767fc9de878b1e9447d3" class-utils@^0.3.5: version "0.3.6" @@ -1785,24 +1733,10 @@ clone-response@1.0.2: dependencies: mimic-response "^1.0.0" -clone@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" -co@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda" - -coa@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.1.tgz#7f959346cfc8719e3f7233cd6852854a7c67d8a3" - dependencies: - q "^1.1.2" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1814,39 +1748,17 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.3.0, color-convert@^1.9.0: +color-convert@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" dependencies: color-name "^1.1.1" -color-name@^1.0.0, color-name@^1.1.1: +color-name@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" -color-string@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" - dependencies: - color-name "^1.0.0" - -color@^0.11.0: - version "0.11.4" - resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" - dependencies: - clone "^1.0.2" - color-convert "^1.3.0" - color-string "^0.3.0" - -colormin@^1.0.5: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" - dependencies: - color "^0.11.0" - css-color-names "0.0.4" - has "^1.0.1" - -colors@^1.1.0, colors@~1.1.2: +colors@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -2115,22 +2027,16 @@ crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" -css-color-names@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - -css-loader@^0.28.11: - version "0.28.11" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7" +css-loader@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.0.tgz#9f46aaa5ca41dbe31860e3b62b8e23c42916bf56" dependencies: babel-code-frame "^6.26.0" css-selector-tokenizer "^0.7.0" - cssnano "^3.10.0" icss-utils "^2.1.0" loader-utils "^1.0.2" lodash.camelcase "^4.3.0" - object-assign "^4.1.1" - postcss "^5.0.6" + postcss "^6.0.23" postcss-modules-extract-imports "^1.2.0" postcss-modules-local-by-default "^1.2.0" postcss-modules-scope "^1.1.0" @@ -2150,50 +2056,6 @@ cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" -cssnano@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" - dependencies: - autoprefixer "^6.3.1" - decamelize "^1.1.2" - defined "^1.0.0" - has "^1.0.1" - object-assign "^4.0.1" - postcss "^5.0.14" - postcss-calc "^5.2.0" - postcss-colormin "^2.1.8" - postcss-convert-values "^2.3.4" - postcss-discard-comments "^2.0.4" - postcss-discard-duplicates "^2.0.1" - postcss-discard-empty "^2.0.1" - postcss-discard-overridden "^0.1.1" - postcss-discard-unused "^2.2.1" - postcss-filter-plugins "^2.0.0" - postcss-merge-idents "^2.1.5" - postcss-merge-longhand "^2.0.1" - postcss-merge-rules "^2.0.3" - postcss-minify-font-values "^1.0.2" - postcss-minify-gradients "^1.0.1" - postcss-minify-params "^1.0.4" - postcss-minify-selectors "^2.0.4" - postcss-normalize-charset "^1.1.0" - postcss-normalize-url "^3.0.7" - postcss-ordered-values "^2.1.0" - postcss-reduce-idents "^2.2.2" - postcss-reduce-initial "^1.0.0" - postcss-reduce-transforms "^1.0.3" - postcss-svgo "^2.1.1" - postcss-unique-selectors "^2.0.2" - postcss-value-parser "^3.2.3" - postcss-zindex "^2.0.1" - -csso@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" - dependencies: - clap "^1.0.9" - source-map "^0.5.3" - currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -2368,18 +2230,12 @@ debug@2.6.8: dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: +debug@3.1.0, debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: ms "2.0.0" -debug@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2442,11 +2298,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - -degenerator@~1.0.2: +degenerator@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" dependencies: @@ -2493,6 +2345,10 @@ depd@1.1.1, depd@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -2522,7 +2378,7 @@ di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" -diff@^3.4.0: +diff@^3.2.0, diff@^3.4.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -2654,10 +2510,6 @@ ejs@^2.5.7: version "2.5.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5" -electron-to-chromium@^1.2.7: - version "1.3.3" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.3.tgz#651eb63fe89f39db70ffc8dbd5d9b66958bc6a0e" - elliptic@^6.0.0: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -2735,6 +2587,14 @@ enhanced-resolve@^4.0.0: memory-fs "^0.4.0" tapable "^1.0.0" +enhanced-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + tapable "^1.0.0" + enhanced-resolve@~0.9.0: version "0.9.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" @@ -2787,10 +2647,20 @@ es-to-primitive@^1.1.1: is-date-object "^1.0.1" is-symbol "^1.0.1" +es6-promise@^4.0.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" + es6-promise@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.0.2.tgz#010d5858423a5f118979665f46486a95c6ee2bb6" +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + dependencies: + es6-promise "^4.0.3" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -2964,7 +2834,7 @@ espree@^3.5.2: acorn "^5.5.0" acorn-jsx "^3.0.0" -esprima@2.7.x, esprima@^2.6.0, esprima@^2.7.1: +esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -3061,12 +2931,6 @@ expand-braces@^0.1.1: array-unique "^0.2.1" braces "^0.1.2" -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - dependencies: - is-posix-bracket "^0.1.0" - expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -3086,12 +2950,6 @@ expand-range@^0.1.0: is-number "^0.1.1" repeat-string "^0.2.2" -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - dependencies: - fill-range "^2.1.0" - exports-loader@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-0.7.0.tgz#84881c784dea6036b8e1cd1dac3da9b6409e21a5" @@ -3151,7 +3009,7 @@ extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^2.0.4, external-editor@^2.1.0: +external-editor@^2.0.4: version "2.2.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" dependencies: @@ -3159,11 +3017,13 @@ external-editor@^2.0.4, external-editor@^2.1.0: iconv-lite "^0.4.17" tmp "^0.0.33" -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" +external-editor@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6" dependencies: - is-extglob "^1.0.0" + chardet "^0.5.0" + iconv-lite "^0.4.22" + tmp "^0.0.33" extglob@^2.0.4: version "2.0.4" @@ -3238,10 +3098,6 @@ file-uri-to-path@1: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - fileset@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" @@ -3253,16 +3109,6 @@ filesize@^3.5.11: version "3.6.0" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.0.tgz#22d079615624bb6fd3c04026120628a41b3f4efa" -fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -3318,10 +3164,6 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" -flatten@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" - flush-write-stream@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" @@ -3341,16 +3183,10 @@ follow-redirects@^1.2.5: dependencies: debug "^3.1.0" -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - dependencies: - for-in "^1.0.1" - foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -3367,15 +3203,7 @@ form-data@~2.0.0: combined-stream "^1.0.5" mime-types "^2.1.11" -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -form-data@~2.3.1: +form-data@~2.3.0, form-data@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: @@ -3433,7 +3261,7 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.0.0: +fsevents@^1.0.0, fsevents@^1.2.2: version "1.2.4" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: @@ -3494,9 +3322,9 @@ get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" -get-uri@2: - version "2.0.1" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.1.tgz#dbdcacacd8c608a38316869368117697a1631c59" +get-uri@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.2.tgz#5c795e71326f6ca1286f2fc82575cd2bab2af578" dependencies: data-uri-to-buffer "1" debug "2" @@ -3515,19 +3343,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -3850,10 +3665,6 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -html-comment-regex@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" - html-entities@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" @@ -3886,13 +3697,21 @@ http-errors@1.6.2, http-errors@~1.6.1, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" -http-proxy-agent@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz#cc1ce38e453bf984a0f7702d2dd59c73d081284a" +http-errors@1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" dependencies: - agent-base "2" - debug "2" - extend "3" + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + dependencies: + agent-base "4" + debug "3.1.0" http-proxy-middleware@~0.18.0: version "0.18.0" @@ -3941,13 +3760,12 @@ https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" -https-proxy-agent@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6" +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" dependencies: - agent-base "2" - debug "2" - extend "3" + agent-base "^4.1.0" + debug "^3.1.0" iconv-lite@0.4.15: version "0.4.15" @@ -3957,7 +3775,7 @@ iconv-lite@0.4.19, iconv-lite@^0.4.17: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -iconv-lite@^0.4.4: +iconv-lite@0.4.23, iconv-lite@^0.4.22, iconv-lite@^0.4.4: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: @@ -4035,9 +3853,9 @@ indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" -inflection@~1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.10.0.tgz#5bffcb1197ad3e81050f8e17e21668087ee9eb2f" +inflection@~1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" inflection@~1.3.0: version "1.3.8" @@ -4081,20 +3899,20 @@ inquirer@^3.0.6: strip-ansi "^4.0.0" through "^2.3.6" -inquirer@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-5.2.0.tgz#db350c2b73daca77ff1243962e9f22f099685726" +inquirer@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.0.0.tgz#e8c20303ddc15bbfc2c12a6213710ccd9e1413d8" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.0" cli-cursor "^2.1.0" cli-width "^2.0.0" - external-editor "^2.1.0" + external-editor "^3.0.0" figures "^2.0.0" lodash "^4.3.0" mute-stream "0.0.7" run-async "^2.2.0" - rxjs "^5.5.2" + rxjs "^6.1.0" string-width "^2.1.0" strip-ansi "^4.0.0" through "^2.3.6" @@ -4126,10 +3944,6 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ip@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.0.1.tgz#c7e356cdea225ae71b36d70f2e71a92ba4e42590" - ip@^1.1.0, ip@^1.1.2, ip@^1.1.4, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -4138,10 +3952,6 @@ ipaddr.js@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -4210,16 +4020,6 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - dependencies: - is-primitive "^2.0.0" - is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -4230,10 +4030,6 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4254,12 +4050,6 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - dependencies: - is-extglob "^1.0.0" - is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -4301,12 +4091,6 @@ is-number@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806" -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - dependencies: - kind-of "^3.0.2" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -4357,14 +4141,6 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" @@ -4397,12 +4173,6 @@ is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" -is-svg@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" - dependencies: - html-comment-regex "^1.1.0" - is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -4554,6 +4324,12 @@ jasmine-core@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.9.0.tgz#bfbb56defcd30789adec5a3fbba8504233289c72" +jasmine-diff@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/jasmine-diff/-/jasmine-diff-0.1.3.tgz#93ccc2dcc41028c5ddd4606558074839f2deeaa8" + dependencies: + diff "^3.2.0" + jasmine-jquery@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jasmine-jquery/-/jasmine-jquery-2.1.1.tgz#d4095e646944a26763235769ab018d9f30f0d47b" @@ -4576,10 +4352,6 @@ jquery.waitforimages@^2.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" -js-base64@^2.1.9: - version "2.1.9" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" - js-cookie@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.1.3.tgz#48071625217ac9ecfab8c343a13d42ec09ff0526" @@ -4595,13 +4367,6 @@ js-yaml@3.x, js-yaml@^3.7.0, js-yaml@^3.9.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@~3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" - dependencies: - argparse "^1.0.7" - esprima "^2.6.0" - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -4691,9 +4456,9 @@ karma-coverage-istanbul-reporter@^1.4.2: istanbul-api "^1.1.14" minimatch "^3.0.4" -karma-jasmine@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.1.tgz#6fe840e75a11600c9d91e84b33c458e1c46a3529" +karma-jasmine@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-1.1.2.tgz#394f2b25ffb4a644b9ada6f22d443e2fd08886c3" karma-mocha-reporter@^2.2.5: version "2.2.5" @@ -4709,24 +4474,24 @@ karma-sourcemap-loader@^0.3.7: dependencies: graceful-fs "^4.1.2" -karma-webpack@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-3.0.0.tgz#bf009c5b73c667c11c015717e9e520f581317c44" +karma-webpack@^4.0.0-beta.0: + version "4.0.0-beta.0" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.0-beta.0.tgz#2b386df6c364f588f896ffbdae57c2e51513d1ba" dependencies: async "^2.0.0" babel-runtime "^6.0.0" loader-utils "^1.0.0" lodash "^4.0.0" source-map "^0.5.6" - webpack-dev-middleware "^2.0.6" + webpack-dev-middleware "^3.0.1" -karma@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/karma/-/karma-2.0.2.tgz#4d2db9402850a66551fa784b0164fb0824ed8c4b" +karma@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/karma/-/karma-2.0.4.tgz#b399785f57e9bab1d3c4384db33fef4dec8ae349" dependencies: bluebird "^3.3.0" body-parser "^1.16.1" - chokidar "^1.4.1" + chokidar "^2.0.3" colors "^1.1.0" combine-lists "^1.0.0" connect "^3.6.0" @@ -4739,7 +4504,7 @@ karma@^2.0.2: http-proxy "^1.13.0" isbinaryfile "^3.0.0" lodash "^4.17.4" - log4js "^2.3.9" + log4js "^2.5.3" mime "^1.3.4" minimatch "^3.0.2" optimist "^0.6.1" @@ -4810,10 +4575,6 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -leb@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/leb/-/leb-0.3.0.tgz#32bee9fad168328d6aea8522d833f4180eed1da3" - levn@^0.3.0, levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -4889,6 +4650,10 @@ lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" @@ -4897,10 +4662,6 @@ lodash.kebabcase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - lodash.mergewith@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" @@ -4909,10 +4670,6 @@ lodash.snakecase@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - lodash.upperfirst@4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" @@ -4921,7 +4678,7 @@ lodash@4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: +lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -4931,21 +4688,21 @@ log-symbols@^2.1.0: dependencies: chalk "^2.0.1" -log4js@^2.3.9: - version "2.5.3" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-2.5.3.tgz#38bb7bde5e9c1c181bd75e8bc128c5cd0409caf1" +log4js@^2.5.3: + version "2.11.0" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-2.11.0.tgz#bf3902eff65c6923d9ce9cfbd2db54160e34005a" dependencies: - circular-json "^0.5.1" + circular-json "^0.5.4" date-format "^1.2.0" debug "^3.1.0" - semver "^5.3.0" - streamroller "^0.7.0" + semver "^5.5.0" + streamroller "0.7.0" optionalDependencies: amqplib "^0.5.2" axios "^0.15.3" hipchat-notifier "^1.1.0" loggly "^1.1.0" - mailgun-js "^0.7.0" + mailgun-js "^0.18.0" nodemailer "^2.5.0" redis "^2.7.1" slack-node "~0.2.0" @@ -4966,6 +4723,10 @@ loglevelnext@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/loglevelnext/-/loglevelnext-1.0.3.tgz#0f69277e73bbbf2cd61b94d82313216bf87ac66e" +long@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + long@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" @@ -5002,14 +4763,6 @@ lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@~2.6.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" - -macaddress@^0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" - mailcomposer@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/mailcomposer/-/mailcomposer-4.0.1.tgz#0e1c44b2a07cf740ee17dc149ba009f19cadfeb4" @@ -5017,18 +4770,18 @@ mailcomposer@4.0.1: buildmail "4.0.1" libmime "3.0.0" -mailgun-js@^0.7.0: - version "0.7.15" - resolved "https://registry.yarnpkg.com/mailgun-js/-/mailgun-js-0.7.15.tgz#ee366a20dac64c3c15c03d6c1b3e0ed795252abb" +mailgun-js@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/mailgun-js/-/mailgun-js-0.18.1.tgz#ee39aa18d7bb598a5ce9ede84afb681defc8a6b0" dependencies: - async "~2.1.2" - debug "~2.2.0" - form-data "~2.1.1" - inflection "~1.10.0" + async "~2.6.0" + debug "~3.1.0" + form-data "~2.3.0" + inflection "~1.12.0" is-stream "^1.1.0" path-proxy "~1.0.0" - proxy-agent "~2.0.0" - q "~1.4.0" + promisify-call "^2.0.2" + proxy-agent "~3.0.0" tsscmp "~1.0.0" make-dir@^1.0.0: @@ -5067,10 +4820,6 @@ match-at@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/match-at/-/match-at-0.1.1.tgz#25d040d291777704d5e6556bbb79230ec2de0540" -math-expression-evaluator@^1.2.14: - version "1.2.16" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9" - md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -5128,24 +4877,6 @@ methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -micromatch@^2.1.5: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -5262,7 +4993,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -5295,10 +5026,6 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -ms@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5359,7 +5086,7 @@ neo-async@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" -netmask@~1.0.4: +netmask@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" @@ -5467,9 +5194,9 @@ nodemailer@^2.5.0: nodemailer-smtp-transport "2.7.2" socks "1.1.9" -nodemon@^1.17.3: - version "1.17.3" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.17.3.tgz#3b0bbc2ee05ccb43b1aef15ba05c63c7bc9b8530" +nodemon@^1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.2.tgz#36b89c790da70c4f270e2cc0718723131bc04abb" dependencies: chokidar "^2.0.2" debug "^3.1.0" @@ -5510,16 +5237,12 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: remove-trailing-separator "^1.0.1" -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - normalize-url@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" @@ -5528,15 +5251,6 @@ normalize-url@2.0.1: query-string "^5.0.1" sort-keys "^2.0.0" -normalize-url@^1.4.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" - dependencies: - object-assign "^4.0.1" - prepend-http "^1.0.0" - query-string "^4.1.0" - sort-keys "^1.0.0" - npm-bundled@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" @@ -5567,10 +5281,6 @@ null-check@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" @@ -5579,7 +5289,7 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -5605,13 +5315,6 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" @@ -5743,29 +5446,28 @@ p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" -pac-proxy-agent@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz#34a385dfdf61d2f0ecace08858c745d3e791fd4d" +pac-proxy-agent@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz#90d9f6730ab0f4d2607dcdcd4d3d641aa26c3896" dependencies: - agent-base "2" - debug "2" - extend "3" - get-uri "2" - http-proxy-agent "1" - https-proxy-agent "1" - pac-resolver "~2.0.0" - raw-body "2" - socks-proxy-agent "2" - -pac-resolver@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-2.0.0.tgz#99b88d2f193fbdeefc1c9a529c1f3260ab5277cd" + agent-base "^4.2.0" + debug "^3.1.0" + get-uri "^2.0.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + pac-resolver "^3.0.0" + raw-body "^2.2.0" + socks-proxy-agent "^3.0.0" + +pac-resolver@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26" dependencies: - co "~3.0.6" - degenerator "~1.0.2" - ip "1.0.1" - netmask "~1.0.4" - thunkify "~2.1.1" + co "^4.6.0" + degenerator "^1.0.4" + ip "^1.1.5" + netmask "^1.0.6" + thunkify "^2.1.2" package-json@^4.0.0: version "4.0.1" @@ -5798,15 +5500,6 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -5967,128 +5660,6 @@ posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" -postcss-calc@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" - dependencies: - postcss "^5.0.2" - postcss-message-helpers "^2.0.0" - reduce-css-calc "^1.2.6" - -postcss-colormin@^2.1.8: - version "2.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" - dependencies: - colormin "^1.0.5" - postcss "^5.0.13" - postcss-value-parser "^3.2.3" - -postcss-convert-values@^2.3.4: - version "2.6.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" - dependencies: - postcss "^5.0.11" - postcss-value-parser "^3.1.2" - -postcss-discard-comments@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" - dependencies: - postcss "^5.0.14" - -postcss-discard-duplicates@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" - dependencies: - postcss "^5.0.4" - -postcss-discard-empty@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" - dependencies: - postcss "^5.0.14" - -postcss-discard-overridden@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" - dependencies: - postcss "^5.0.16" - -postcss-discard-unused@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" - dependencies: - postcss "^5.0.14" - uniqs "^2.0.0" - -postcss-filter-plugins@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" - dependencies: - postcss "^5.0.4" - uniqid "^4.0.0" - -postcss-merge-idents@^2.1.5: - version "2.1.7" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" - dependencies: - has "^1.0.1" - postcss "^5.0.10" - postcss-value-parser "^3.1.1" - -postcss-merge-longhand@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" - dependencies: - postcss "^5.0.4" - -postcss-merge-rules@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" - dependencies: - browserslist "^1.5.2" - caniuse-api "^1.5.2" - postcss "^5.0.4" - postcss-selector-parser "^2.2.2" - vendors "^1.0.0" - -postcss-message-helpers@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" - -postcss-minify-font-values@^1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" - dependencies: - object-assign "^4.0.1" - postcss "^5.0.4" - postcss-value-parser "^3.0.2" - -postcss-minify-gradients@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" - dependencies: - postcss "^5.0.12" - postcss-value-parser "^3.3.0" - -postcss-minify-params@^1.0.4: - version "1.2.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" - dependencies: - alphanum-sort "^1.0.1" - postcss "^5.0.2" - postcss-value-parser "^3.0.2" - uniqs "^2.0.0" - -postcss-minify-selectors@^2.0.4: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" - dependencies: - alphanum-sort "^1.0.2" - has "^1.0.1" - postcss "^5.0.14" - postcss-selector-parser "^2.0.0" - postcss-modules-extract-imports@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" @@ -6116,57 +5687,6 @@ postcss-modules-values@^1.3.0: icss-replace-symbols "^1.1.0" postcss "^6.0.1" -postcss-normalize-charset@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" - dependencies: - postcss "^5.0.5" - -postcss-normalize-url@^3.0.7: - version "3.0.8" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" - dependencies: - is-absolute-url "^2.0.0" - normalize-url "^1.4.0" - postcss "^5.0.14" - postcss-value-parser "^3.2.3" - -postcss-ordered-values@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" - dependencies: - postcss "^5.0.4" - postcss-value-parser "^3.0.1" - -postcss-reduce-idents@^2.2.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" - dependencies: - postcss "^5.0.4" - postcss-value-parser "^3.0.2" - -postcss-reduce-initial@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" - dependencies: - postcss "^5.0.4" - -postcss-reduce-transforms@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" - dependencies: - has "^1.0.1" - postcss "^5.0.8" - postcss-value-parser "^3.0.1" - -postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - postcss-selector-parser@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" @@ -6175,44 +5695,10 @@ postcss-selector-parser@^3.1.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-svgo@^2.1.1: - version "2.1.6" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" - dependencies: - is-svg "^2.0.0" - postcss "^5.0.14" - postcss-value-parser "^3.2.3" - svgo "^0.7.0" - -postcss-unique-selectors@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" - dependencies: - alphanum-sort "^1.0.1" - postcss "^5.0.4" - uniqs "^2.0.0" - -postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: +postcss-value-parser@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" -postcss-zindex@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" - dependencies: - has "^1.0.1" - postcss "^5.0.4" - uniqs "^2.0.0" - -postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: - version "5.2.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.16.tgz#732b3100000f9ff8379a48a53839ed097376ad57" - dependencies: - chalk "^1.1.3" - js-base64 "^2.1.9" - source-map "^0.5.6" - supports-color "^3.2.3" - postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20: version "6.0.22" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" @@ -6221,11 +5707,19 @@ postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.20: source-map "^0.6.1" supports-color "^5.4.0" +postcss@^6.0.23: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" -prepend-http@^1.0.0, prepend-http@^1.0.1: +prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -6233,10 +5727,6 @@ prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - prettier@1.12.1, prettier@^1.11.1: version "1.12.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" @@ -6271,6 +5761,12 @@ promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" +promisify-call@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/promisify-call/-/promisify-call-2.0.4.tgz#d48c2d45652ccccd52801ddecbd533a6d4bd5fba" + dependencies: + with-callback "^1.0.2" + proxy-addr@~2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" @@ -6278,18 +5774,22 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.6.0" -proxy-agent@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499" +proxy-agent@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.0.1.tgz#4fb7b61b1476d0fe8e3a3384d90e2460bbded3f9" dependencies: - agent-base "2" - debug "2" - extend "3" - http-proxy-agent "1" - https-proxy-agent "1" - lru-cache "~2.6.5" - pac-proxy-agent "1" - socks-proxy-agent "2" + agent-base "^4.2.0" + debug "^3.1.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + lru-cache "^4.1.2" + pac-proxy-agent "^2.0.1" + proxy-from-env "^1.0.0" + socks-proxy-agent "^4.0.1" + +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" prr@~0.0.0: version "0.0.0" @@ -6348,14 +5848,6 @@ punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -q@^1.1.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" - -q@~1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" - qjobs@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" @@ -6368,13 +5860,6 @@ qs@~6.2.0: version "6.2.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" -query-string@^4.1.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.2.tgz#ec0fd765f58a50031a3968c2431386f8947a5cdd" - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - query-string@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" @@ -6399,13 +5884,6 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" @@ -6433,7 +5911,7 @@ raven-js@^3.22.1: version "3.22.1" resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.22.1.tgz#1117f00dfefaa427ef6e1a7d50bbb1fb998a24da" -raw-body@2, raw-body@2.3.2: +raw-body@2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" dependencies: @@ -6442,6 +5920,15 @@ raw-body@2, raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" +raw-body@^2.2.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" + dependencies: + bytes "3.0.0" + http-errors "1.6.3" + iconv-lite "0.4.23" + unpipe "1.0.0" + raw-loader@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" @@ -6561,20 +6048,6 @@ redis@^2.7.1: redis-commands "^1.2.0" redis-parser "^2.6.0" -reduce-css-calc@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" - dependencies: - balanced-match "^0.4.2" - math-expression-evaluator "^1.2.14" - reduce-function-call "^1.0.1" - -reduce-function-call@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" - dependencies: - balanced-match "^0.4.2" - regenerate@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" @@ -6591,12 +6064,6 @@ regenerator-transform@^0.10.0: babel-types "^6.19.0" private "^0.1.6" -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - dependencies: - is-equal-shallow "^0.1.3" - regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -6832,11 +6299,11 @@ rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" -rxjs@^5.5.2: - version "5.5.10" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045" +rxjs@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" dependencies: - symbol-observable "1.0.1" + tslib "^1.9.0" safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" @@ -6872,10 +6339,6 @@ sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -sax@~1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" - schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" @@ -6911,10 +6374,6 @@ semver-diff@^2.0.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" -semver@~5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" - send@0.16.1: version "0.16.1" resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" @@ -7050,6 +6509,10 @@ smart-buffer@^1.0.13, smart-buffer@^1.0.4: version "1.1.15" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" +smart-buffer@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3" + smtp-connection@2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/smtp-connection/-/smtp-connection-2.12.0.tgz#d76ef9127cb23c2259edb1e8349c2e8d5e2d74c1" @@ -7155,13 +6618,19 @@ sockjs@0.3.19: faye-websocket "^0.10.0" uuid "^3.0.1" -socks-proxy-agent@2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz#86ebb07193258637870e13b7bd99f26c663df3d3" +socks-proxy-agent@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz#2eae7cf8e2a82d34565761539a7f9718c5617659" dependencies: - agent-base "2" - extend "3" - socks "~1.1.5" + agent-base "^4.1.0" + socks "^1.1.10" + +socks-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz#5936bf8b707a993079c6f37db2091821bffa6473" + dependencies: + agent-base "~4.2.0" + socks "~2.2.0" socks@1.1.9: version "1.1.9" @@ -7170,18 +6639,19 @@ socks@1.1.9: ip "^1.1.2" smart-buffer "^1.0.4" -socks@~1.1.5: +socks@^1.1.10: version "1.1.10" resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" dependencies: ip "^1.1.4" smart-buffer "^1.0.13" -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" +socks@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.1.tgz#68ad678b3642fbc5d99c64c165bc561eab0215f9" dependencies: - is-plain-obj "^1.0.0" + ip "^1.1.5" + smart-buffer "^4.0.1" sort-keys@^2.0.0: version "2.0.0" @@ -7340,6 +6810,10 @@ static-extend@^0.1.1: version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -7382,7 +6856,7 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -streamroller@^0.7.0: +streamroller@0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b" dependencies: @@ -7477,7 +6951,7 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: +supports-color@^3.1.0, supports-color@^3.1.2: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: @@ -7493,22 +6967,6 @@ svg4everybody@2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d" -svgo@^0.7.0: - version "0.7.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" - dependencies: - coa "~1.0.1" - colors "~1.1.2" - csso "~2.3.1" - js-yaml "~3.7.0" - mkdirp "~0.5.1" - sax "~1.2.1" - whet.extend "~0.9.9" - -symbol-observable@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" - table@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" @@ -7583,7 +7041,7 @@ through@2, through@^2.3.6, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -thunkify@~2.1.1: +thunkify@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" @@ -7691,6 +7149,10 @@ tryit@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" +tslib@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + tsscmp@~1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.5.tgz#7dc4a33af71581ab4337da91d85ca5427ebd9a97" @@ -7794,16 +7256,6 @@ uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" -uniqid@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" - dependencies: - macaddress "^0.2.8" - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - unique-filename@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3" @@ -7841,6 +7293,10 @@ upath@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.5.tgz#02cab9ecebe95bbec6d5fc2566325725ab6d1a73" +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + update-notifier@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" @@ -7859,10 +7315,6 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url-join@^2.0.2: - version "2.0.5" - resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" - url-join@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a" @@ -7968,10 +7420,6 @@ vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" -vendors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" - verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -8009,9 +7457,9 @@ vue-hot-reload-api@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926" -vue-loader@^15.2.0: - version "15.2.0" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.2.0.tgz#5a8138e490a1040942d2f10ae68fa72b5a923364" +vue-loader@^15.2.4: + version "15.2.4" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.2.4.tgz#a7b923123d3cf87230a8ff54a1c16d31a6c5dbb4" dependencies: "@vue/component-compiler-utils" "^1.2.1" hash-sum "^1.0.2" @@ -8073,9 +7521,9 @@ wbuf@^1.1.0, wbuf@^1.7.2: dependencies: minimalistic-assert "^1.0.0" -webpack-bundle-analyzer@^2.11.1: - version "2.11.1" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.11.1.tgz#b9fbfb6a32c0a8c1c3237223e90890796b950ab9" +webpack-bundle-analyzer@^2.13.1: + version "2.13.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.13.1.tgz#07d2176c6e86c3cdce4c23e56fae2a7b6b4ad526" dependencies: acorn "^5.3.0" bfj-node4 "^5.2.0" @@ -8090,23 +7538,23 @@ webpack-bundle-analyzer@^2.11.1: opener "^1.4.3" ws "^4.0.0" -webpack-cli@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.2.tgz#e48c5662aff8ed5aac3db5f82f51d7f32e50459e" +webpack-cli@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.0.8.tgz#90eddcf04a4bfc31aa8c0edc4c76785bc4f1ccd9" dependencies: chalk "^2.4.1" cross-spawn "^6.0.5" enhanced-resolve "^4.0.0" global-modules-path "^2.1.0" import-local "^1.0.0" - inquirer "^5.2.0" + inquirer "^6.0.0" interpret "^1.1.0" loader-utils "^1.1.0" supports-color "^5.4.0" v8-compile-cache "^2.0.0" yargs "^11.1.0" -webpack-dev-middleware@3.1.3: +webpack-dev-middleware@3.1.3, webpack-dev-middleware@^3.0.1: version "3.1.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.1.3.tgz#8b32aa43da9ae79368c1bf1183f2b6cf5e1f39ed" dependencies: @@ -8118,18 +7566,6 @@ webpack-dev-middleware@3.1.3: url-join "^4.0.0" webpack-log "^1.0.1" -webpack-dev-middleware@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz#a51692801e8310844ef3e3790e1eacfe52326fd4" - dependencies: - loud-rejection "^1.6.0" - memory-fs "~0.4.1" - mime "^2.1.0" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - url-join "^2.0.2" - webpack-log "^1.0.1" - webpack-dev-server@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.4.tgz#9a08d13c4addd1e3b6d8ace116e86715094ad5b4" @@ -8183,21 +7619,21 @@ webpack-stats-plugin@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.2.1.tgz#1f5bac13fc25d62cbb5fd0ff646757dc802b8595" -webpack@^4.11.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.11.1.tgz#1aa0b936f7ae93a52cf38d2ad0d0f46dcf3c2723" +webpack@^4.16.0: + version "4.16.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.16.0.tgz#660dae90890e55b8ed17c6f9d17bebb01dab5b4c" dependencies: - "@webassemblyjs/ast" "1.5.10" - "@webassemblyjs/helper-module-context" "1.5.10" - "@webassemblyjs/wasm-edit" "1.5.10" - "@webassemblyjs/wasm-opt" "1.5.10" - "@webassemblyjs/wasm-parser" "1.5.10" - acorn "^5.0.0" + "@webassemblyjs/ast" "1.5.13" + "@webassemblyjs/helper-module-context" "1.5.13" + "@webassemblyjs/wasm-edit" "1.5.13" + "@webassemblyjs/wasm-opt" "1.5.13" + "@webassemblyjs/wasm-parser" "1.5.13" + acorn "^5.6.2" acorn-dynamic-import "^3.0.0" ajv "^6.1.0" ajv-keywords "^3.1.0" - chrome-trace-event "^0.1.1" - enhanced-resolve "^4.0.0" + chrome-trace-event "^1.0.0" + enhanced-resolve "^4.1.0" eslint-scope "^3.7.1" json-parse-better-errors "^1.0.2" loader-runner "^2.3.0" @@ -8227,10 +7663,6 @@ when@^3.7.7: version "3.7.8" resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" -whet.extend@~0.9.9: - version "0.9.9" - resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -8257,6 +7689,10 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +with-callback@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/with-callback/-/with-callback-1.0.2.tgz#a09629b9a920028d721404fb435bdcff5c91bc21" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" |