diff options
90 files changed, 837 insertions, 365 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c29c289310d..87260744190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -992,6 +992,11 @@ entry. - Added type to CHANGELOG entries. (Jacopo Beschi @jacopo-beschi) - [BUGIFX] Improves subgroup creation permissions. !13418 +## 9.5.10 (2017-11-08) + +- [SECURITY] Add SSRF protections for hostnames that will never resolve but will still connect to localhost +- [SECURITY] Include X-Content-Type-Options (XCTO) header into API responses + ## 9.5.9 (2017-10-16) - [SECURITY] Move project repositories between namespaces when renaming users. @@ -115,7 +115,7 @@ gem 'google-api-client', '~> 0.13.6' gem 'unf', '~> 0.1.4' # Seed data -gem 'seed-fu', '2.3.6' # Upgrade to > 2.3.7 once https://github.com/mbleigh/seed-fu/issues/123 is solved +gem 'seed-fu', '~> 2.3.7' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' diff --git a/Gemfile.lock b/Gemfile.lock index b83a3f0f7a4..10e2585a0e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -831,7 +831,7 @@ GEM rake (>= 0.9, < 13) sass (~> 3.5.3) securecompare (1.0.0) - seed-fu (2.3.6) + seed-fu (2.3.7) activerecord (>= 3.1) activesupport (>= 3.1) select2-rails (3.5.9.3) @@ -1170,7 +1170,7 @@ DEPENDENCIES sanitize (~> 2.0) sass-rails (~> 5.0.6) scss_lint (~> 0.56.0) - seed-fu (= 2.3.6) + seed-fu (~> 2.3.7) select2-rails (~> 3.5.9) selenium-webdriver (~> 3.5) sentry-raven (~> 2.5.3) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index afa1bc19790..1c6336073e9 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,5 +1,4 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ -import { s__ } from './locale'; import projectSelect from './project_select'; import Milestone from './milestone'; import IssuableForm from './issuable_form'; @@ -14,19 +13,16 @@ import Project from './project'; import projectAvatar from './project_avatar'; import MergeRequest from './merge_request'; import Compare from './compare'; -import initCompareAutocomplete from './compare_autocomplete'; import ProjectNew from './project_new'; import Labels from './labels'; import LabelManager from './label_manager'; import Sidebar from './right_sidebar'; import IssuableTemplateSelectors from './templates/issuable_template_selectors'; import Flash from './flash'; -import CommitsList from './commits'; import BindInOut from './behaviors/bind_in_out'; import SecretValues from './behaviors/secret_values'; import Group from './group'; import ProjectsList from './projects_list'; -import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import UserCallout from './user_callout'; import ShortcutsWiki from './shortcuts_wiki'; import BlobViewer from './blob/viewer/index'; @@ -42,8 +38,6 @@ import PerformanceBar from './performance_bar'; import initNotes from './init_notes'; import initIssuableSidebar from './init_issuable_sidebar'; import initProjectVisibilitySelector from './project_visibility'; -import GpgBadges from './gpg_badges'; -import initChangesDropdown from './init_changes_dropdown'; import NewGroupChild from './groups/new_group_child'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import GlFieldErrors from './gl_field_errors'; @@ -54,12 +48,10 @@ import ShortcutsIssuable from './shortcuts_issuable'; import U2FAuthenticate from './u2f/authenticate'; import Members from './members'; import memberExpirationDate from './member_expiration_date'; -import DueDateSelectors from './due_date_select'; import Diff from './diff'; import ProjectLabelSubscription from './project_label_subscription'; import SearchAutocomplete from './search_autocomplete'; import Activities from './activities'; -import { fetchCommitMergeRequests } from './commit_merge_requests'; (function() { var Dispatcher; @@ -185,23 +177,33 @@ import { fetchCommitMergeRequests } from './commit_merge_requests'; .catch(fail); break; case 'projects:milestones:new': + case 'projects:milestones:create': + import('./pages/projects/milestones/new') + .then(callDefault) + .catch(fail); + break; case 'projects:milestones:edit': case 'projects:milestones:update': - new ZenMode(); - new DueDateSelectors(); - new GLForm($('.milestone-form'), true); + import('./pages/projects/milestones/edit') + .then(callDefault) + .catch(fail); break; case 'groups:milestones:new': + case 'groups:milestones:create': + import('./pages/groups/milestones/new') + .then(callDefault) + .catch(fail); + break; case 'groups:milestones:edit': case 'groups:milestones:update': - new ZenMode(); - new DueDateSelectors(); - new GLForm($('.milestone-form'), false); + import('./pages/groups/milestones/edit') + .then(callDefault) + .catch(fail); break; case 'projects:compare:show': - new Diff(); - const paddingTop = 16; - initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop); + import('./pages/projects/compare/show') + .then(callDefault) + .catch(fail); break; case 'projects:branches:new': import('./pages/projects/branches/new') @@ -315,23 +317,15 @@ import { fetchCommitMergeRequests } from './commit_merge_requests'; .catch(fail); break; case 'projects:commit:show': - new Diff(); - new ZenMode(); - shortcut_handler = new ShortcutsNavigation(); - new MiniPipelineGraph({ - container: '.js-commit-pipeline-graph', - }).bindEvents(); - initNotes(); - const stickyBarPaddingTop = 16; - initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop); - $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); - fetchCommitMergeRequests(); + import('./pages/projects/commit/show') + .then(callDefault) + .catch(fail); + shortcut_handler = true; break; case 'projects:commit:pipelines': - new MiniPipelineGraph({ - container: '.js-commit-pipeline-graph', - }).bindEvents(); - $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); + import('./pages/projects/commit/pipelines') + .then(callDefault) + .catch(fail); break; case 'projects:activity': import('./pages/projects/activity') @@ -340,9 +334,10 @@ import { fetchCommitMergeRequests } from './commit_merge_requests'; shortcut_handler = true; break; case 'projects:commits:show': - CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); - shortcut_handler = new ShortcutsNavigation(); - GpgBadges.fetch(); + import('./pages/projects/commits/show') + .then(callDefault) + .catch(fail); + shortcut_handler = true; break; case 'projects:show': shortcut_handler = new ShortcutsNavigation(); @@ -564,20 +559,14 @@ import { fetchCommitMergeRequests } from './commit_merge_requests'; .catch(fail); break; case 'projects:clusters:show': - import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle') - .then(cluster => new cluster.default()) // eslint-disable-line new-cap - .catch((err) => { - Flash(s__('ClusterIntegration|Problem setting up the cluster')); - throw err; - }); + import('./pages/projects/clusters/show') + .then(callDefault) + .catch(fail); break; case 'projects:clusters:index': - import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index') - .then(clusterIndex => clusterIndex.default()) - .catch((err) => { - Flash(s__('ClusterIntegration|Problem setting up the clusters list')); - throw err; - }); + import('./pages/projects/clusters/index') + .then(callDefault) + .catch(fail); break; } switch (path[0]) { @@ -657,7 +646,9 @@ import { fetchCommitMergeRequests } from './commit_merge_requests'; projectAvatar(); switch (path[1]) { case 'compare': - initCompareAutocomplete(); + import('./pages/projects/compare') + .then(callDefault) + .catch(fail); break; case 'edit': shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 8f32dcc94e2..9b5092c5e3f 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -3,7 +3,6 @@ import { visitUrl } from './lib/utils/url_utility'; import bp from './breakpoints'; import { numberToHumanSize } from './lib/utils/number_utils'; import { setCiStatusFavicon } from './lib/utils/common_utils'; -import { timeFor } from './lib/utils/datetime_utility'; export default class Job { constructor(options) { @@ -71,7 +70,6 @@ export default class Job { .off('resize.build') .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100)); - this.updateArtifactRemoveDate(); this.initAffixTopArea(); this.getBuildTrace(); @@ -261,16 +259,7 @@ export default class Job { sidebarOnClick() { if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); } - // eslint-disable-next-line class-methods-use-this, consistent-return - updateArtifactRemoveDate() { - const $date = $('.js-artifacts-remove'); - if ($date.length) { - const date = $date.text(); - return $date.text( - timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))), - ); - } - } + // eslint-disable-next-line class-methods-use-this populateJobs(stage) { $('.build-job').hide(); diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue index 321a4872ccc..357bc9aab17 100644 --- a/app/assets/javascripts/jobs/components/header.vue +++ b/app/assets/javascripts/jobs/components/header.vue @@ -76,6 +76,7 @@ <loading-icon v-if="isLoading" size="2" + class="prepend-top-default append-bottom-default" /> </div> </template> diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index cb3cdea8111..e26bf437efc 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -110,7 +110,7 @@ MergeRequest.prototype.initCommitMessageListeners = function() { }); }; -MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, newStatusText) { +MergeRequest.updateStatusText = function(classToRemove, classToAdd, newStatusText) { $('.detail-page-header .status-box') .removeClass(classToRemove) .addClass(classToAdd) @@ -118,14 +118,14 @@ MergeRequest.prototype.updateStatusText = function(classToRemove, classToAdd, ne .text(newStatusText); }; -MergeRequest.prototype.decreaseCounter = function(by = 1) { - const $el = $('.nav-links .js-merge-counter'); +MergeRequest.decreaseCounter = function(by = 1) { + const $el = $('.js-merge-counter'); const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0); $el.text(addDelimiter(count)); }; -MergeRequest.prototype.hideCloseButton = function() { +MergeRequest.hideCloseButton = function() { const el = document.querySelector('.merge-request .js-issuable-actions'); const closeDropdownItem = el.querySelector('li.close-item'); if (closeDropdownItem) { diff --git a/app/assets/javascripts/pages/groups/milestones/edit/index.js b/app/assets/javascripts/pages/groups/milestones/edit/index.js new file mode 100644 index 00000000000..5c99c90e24d --- /dev/null +++ b/app/assets/javascripts/pages/groups/milestones/edit/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(false); diff --git a/app/assets/javascripts/pages/groups/milestones/new/index.js b/app/assets/javascripts/pages/groups/milestones/new/index.js new file mode 100644 index 00000000000..5c99c90e24d --- /dev/null +++ b/app/assets/javascripts/pages/groups/milestones/new/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(false); diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js new file mode 100644 index 00000000000..d531ab81dc7 --- /dev/null +++ b/app/assets/javascripts/pages/projects/clusters/index/index.js @@ -0,0 +1,5 @@ +import ClustersIndex from '~/clusters/clusters_index'; + +export default () => { + new ClustersIndex(); // eslint-disable-line no-new +}; diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js new file mode 100644 index 00000000000..0458c02a66f --- /dev/null +++ b/app/assets/javascripts/pages/projects/clusters/show/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +export default () => { + new ClustersBundle(); // eslint-disable-line no-new +}; diff --git a/app/assets/javascripts/pages/projects/commit/pipelines/index.js b/app/assets/javascripts/pages/projects/commit/pipelines/index.js new file mode 100644 index 00000000000..523ad567021 --- /dev/null +++ b/app/assets/javascripts/pages/projects/commit/pipelines/index.js @@ -0,0 +1,8 @@ +import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; + +export default () => { + new MiniPipelineGraph({ + container: '.js-commit-pipeline-graph', + }).bindEvents(); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); +}; diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js new file mode 100644 index 00000000000..5ac38e6f278 --- /dev/null +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -0,0 +1,22 @@ +/* eslint-disable no-new */ +import Diff from '~/diff'; +import ZenMode from '~/zen_mode'; +import ShortcutsNavigation from '~/shortcuts_navigation'; +import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; +import initNotes from '~/init_notes'; +import initChangesDropdown from '~/init_changes_dropdown'; +import { fetchCommitMergeRequests } from '~/commit_merge_requests'; + +export default () => { + new Diff(); + new ZenMode(); + new ShortcutsNavigation(); + new MiniPipelineGraph({ + container: '.js-commit-pipeline-graph', + }).bindEvents(); + initNotes(); + const stickyBarPaddingTop = 16; + initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop); + $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); + fetchCommitMergeRequests(); +}; diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js new file mode 100644 index 00000000000..90b5882a24f --- /dev/null +++ b/app/assets/javascripts/pages/projects/commits/show/index.js @@ -0,0 +1,9 @@ +import CommitsList from '~/commits'; +import GpgBadges from '~/gpg_badges'; +import ShortcutsNavigation from '~/shortcuts_navigation'; + +export default () => { + CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit); + new ShortcutsNavigation(); // eslint-disable-line no-new + GpgBadges.fetch(); +}; diff --git a/app/assets/javascripts/pages/projects/compare/index.js b/app/assets/javascripts/pages/projects/compare/index.js new file mode 100644 index 00000000000..890062eeee6 --- /dev/null +++ b/app/assets/javascripts/pages/projects/compare/index.js @@ -0,0 +1,5 @@ +import initCompareAutocomplete from '~/compare_autocomplete'; + +export default () => { + initCompareAutocomplete(); +}; diff --git a/app/assets/javascripts/pages/projects/compare/show/index.js b/app/assets/javascripts/pages/projects/compare/show/index.js new file mode 100644 index 00000000000..6b8d4503568 --- /dev/null +++ b/app/assets/javascripts/pages/projects/compare/show/index.js @@ -0,0 +1,8 @@ +import Diff from '~/diff'; +import initChangesDropdown from '~/init_changes_dropdown'; + +export default () => { + new Diff(); // eslint-disable-line no-new + const paddingTop = 16; + initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop); +}; diff --git a/app/assets/javascripts/pages/projects/milestones/edit/index.js b/app/assets/javascripts/pages/projects/milestones/edit/index.js new file mode 100644 index 00000000000..10e3979a36e --- /dev/null +++ b/app/assets/javascripts/pages/projects/milestones/edit/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(); diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js new file mode 100644 index 00000000000..10e3979a36e --- /dev/null +++ b/app/assets/javascripts/pages/projects/milestones/new/index.js @@ -0,0 +1,3 @@ +import initForm from '../../../../shared/milestones/form'; + +export default () => initForm(); diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index 942acc8c412..e08c2092680 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -92,6 +92,7 @@ <loading-icon v-if="isLoading" size="2" + class="prepend-top-default append-bottom-default" /> </div> </template> diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js new file mode 100644 index 00000000000..db466f722c4 --- /dev/null +++ b/app/assets/javascripts/shared/milestones/form.js @@ -0,0 +1,9 @@ +import ZenMode from '../../zen_mode'; +import DueDateSelectors from '../../due_date_select'; +import GLForm from '../../gl_form'; + +export default (initGFM = true) => { + new ZenMode(); // eslint-disable-line no-new + new DueDateSelectors(); // eslint-disable-line no-new + new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js index e82fb979162..60f42c46ffe 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js @@ -1,6 +1,7 @@ import successSvg from 'icons/_icon_status_success.svg'; import warningSvg from 'icons/_icon_status_warning.svg'; import simplePoll from '~/lib/utils/simple_poll'; +import MergeRequest from '../../../merge_request'; import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon'; import eventHub from '../../event_hub'; @@ -165,11 +166,9 @@ export default { // If state is merged we should update the widget and stop the polling eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('FetchActionsContent'); - if (window.mergeRequest) { - window.mergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); - window.mergeRequest.hideCloseButton(); - window.mergeRequest.decreaseCounter(); - } + MergeRequest.updateStatusText('status-box-open', 'status-box-merged', 'Merged'); + MergeRequest.hideCloseButton(); + MergeRequest.decreaseCounter(); stopPolling(); // If user checked remove source branch and we didn't remove the branch yet diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue index c103c45c7dd..8227428d8ba 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/modal.vue @@ -122,7 +122,7 @@ > <button type="button" - class="btn pull-left" + class="btn" :class="btnCancelKindClass" @click="emitCancel($event)" data-dismiss="modal" @@ -132,7 +132,7 @@ <button v-if="primaryButtonLabel" type="button" - class="btn pull-right js-primary-button" + class="btn js-primary-button" :disabled="submitDisabled" :class="btnKindClass" @click="emitSubmit($event)" diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 1be66d0ab21..924472f2d7e 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -1,4 +1,5 @@ .modal-header { + background-color: $modal-body-bg; padding: #{3 * $grid-size} #{2 * $grid-size}; .page-title { @@ -8,6 +9,7 @@ .modal-body { background-color: $modal-body-bg; + min-height: $modal-body-height; position: relative; padding: #{3 * $grid-size} #{2 * $grid-size}; @@ -20,6 +22,30 @@ } } +.modal-footer { + display: flex; + flex-direction: row; + + .btn + .btn { + margin-left: $grid-size; + } + + @media (max-width: $screen-xs-max) { + flex-direction: column; + + .btn + .btn { + margin-left: 0; + margin-top: $grid-size; + } + } + + @media (min-width: $screen-sm-min) { + .btn:first-of-type { + margin-left: auto; + } + } +} + body.modal-open { overflow: hidden; } @@ -32,12 +58,6 @@ body.modal-open { } } -@media (min-width: $screen-md-min) { - .modal-dialog { - width: 860px; - } -} - @media (min-width: $screen-lg-min) { .modal-full { width: 98%; diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index a23131e0818..d04e555769b 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -194,6 +194,6 @@ $modal-body-bg: $white-light; //** Modal footer border color // $modal-footer-border-color: $modal-header-border-color -// $modal-lg: 900px -// $modal-md: 600px +$modal-lg: 860px; +$modal-md: 540px; // $modal-sm: 300px diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index ef1520f1f63..da18ddf78d3 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -733,3 +733,8 @@ $popup-box-shadow-color: rgba(90, 90, 90, 0.05); Multi file editor */ $border-color-settings: #e1e1e1; + +/* +Modals +*/ +$modal-body-height: 134px; diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index c7297a34ad8..7d40c61da26 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -3,22 +3,21 @@ // see also: https://gist.github.com/jasonm23/2868981 $black: #000; - $red: #cd0000; - $green: #00cd00; - $yellow: #cdcd00; - $blue: #00e; // according to wikipedia, this is the xterm standard - //$blue: #1e90ff; // this is used by all the terminals I tried (when configured with the xterm color profile) - $magenta: #cd00cd; - $cyan: #00cdcd; - $white: #e5e5e5; + $red: #ea1010; + $green: #009900; + $yellow: #999900; + $blue: #0073e6; + $magenta: #d411d4; + $cyan: #009999; + $white: #ccc; $l-black: #373b41; - $l-red: #c66; - $l-green: #b5bd68; - $l-yellow: #f0c674; - $l-blue: #81a2be; - $l-magenta: #b294bb; - $l-cyan: #8abeb7; - $l-white: $gray-darkest; + $l-red: #ff6161; + $l-green: #00d600; + $l-yellow: #bdbd00; + $l-blue: #5797ff; + $l-magenta: #d96dd9; + $l-cyan: #00bdbd; + $l-white: #fff; /* * xterm colors diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 3d2926d5d75..0df80fa700f 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -43,11 +43,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap end def diffs - @diffs = if @merge_request.can_be_created - @merge_request.diffs(diff_options) - else - [] - end + @diffs = @merge_request.diffs(diff_options) if @merge_request.can_be_created @diff_notes_disabled = true diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 5e3b2e5581c..a6e1de6ffdc 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,6 +1,8 @@ module BlobHelper def highlight(blob_name, blob_content, repository: nil, plain: false) + plain ||= blob_content.length > Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository) + raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>) end diff --git a/app/models/commit.rb b/app/models/commit.rb index 21904c87f01..2d2d89af030 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -372,19 +372,19 @@ class Commit # uri_type('doc/README.md') # => :blob # uri_type('doc/logo.png') # => :raw # uri_type('doc/api') # => :tree - # uri_type('not/found') # => :nil + # uri_type('not/found') # => nil # # Returns a symbol def uri_type(path) - entry = @raw.rugged_tree_entry(path) + entry = @raw.tree_entry(path) + return unless entry + if entry[:type] == :blob blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project) blob.image? || blob.video? ? :raw : :blob else entry[:type] end - rescue Rugged::TreeError - nil end def raw_diffs(*args) diff --git a/app/models/push_event.rb b/app/models/push_event.rb index 83ce9014094..90c085c888e 100644 --- a/app/models/push_event.rb +++ b/app/models/push_event.rb @@ -46,10 +46,11 @@ class PushEvent < Event # Returns PushEvent instances for which no merge requests have been created. def self.without_existing_merge_requests - existing_mrs = MergeRequest.except(:order) + existing_mrs = MergeRequest.except(:order, :where) .select(1) .where('merge_requests.source_project_id = events.project_id') .where('merge_requests.source_branch = push_event_payloads.ref') + .where(state: :opened) # For reasons unknown the use of #eager_load will result in the # "push_event_payload" association not being set. Because of this we're diff --git a/app/models/repository.rb b/app/models/repository.rb index 2ffd9558ebc..b4bc0f87458 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -932,15 +932,17 @@ class Repository return [] if empty? || query.blank? offset = 2 - args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) + args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref}) run_git(args).first.scrub.split(/^--$/) end def search_files_by_name(query, ref) - return [] if empty? || query.blank? + safe_query = Regexp.escape(query.sub(/^\/*/, "")) + + return [] if empty? || safe_query.blank? - args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)}) + args = %W(ls-tree --full-tree -r #{ref || root_ref} --name-status | #{safe_query}) run_git(args).first.lines.map(&:strip) end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 30a5aab13bf..06b23cd7076 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -68,21 +68,14 @@ module SystemNoteService # # Returns the created Note object def change_issue_assignees(issue, project, author, old_assignees) - body = - if issue.assignees.any? && old_assignees.any? - unassigned_users = old_assignees - issue.assignees - added_users = issue.assignees.to_a - old_assignees - - text_parts = [] - text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any? - text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any? - - text_parts.join(' and ') - elsif old_assignees.any? - "removed assignee" - elsif issue.assignees.any? - "assigned to #{issue.assignees.map(&:to_reference).to_sentence}" - end + unassigned_users = old_assignees - issue.assignees + added_users = issue.assignees.to_a - old_assignees + + text_parts = [] + text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any? + text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any? + + body = text_parts.join(' and ') create_note(NoteSummary.new(issue, project, author, body, action: 'assignee')) end diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml index ecdf76ef5c5..7a3f3667ac1 100644 --- a/app/views/dashboard/_activity_head.html.haml +++ b/app/views/dashboard/_activity_head.html.haml @@ -2,7 +2,7 @@ %ul.nav-links %li{ class: active_when(params[:filter].nil?) }> = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects + Your projects %li{ class: active_when(params[:filter] == 'starred') }> = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do - Starred Projects + Starred projects diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 7330f4cb523..a9488df07bd 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -2,10 +2,10 @@ %ul.nav-links = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do - Your Snippets + Your snippets = nav_link(page: explore_snippets_path) do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do - Explore Snippets + Explore snippets - if current_user .nav-controls.hidden-xs diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml index 86ebec0179c..e44506ec9c9 100644 --- a/app/views/profiles/gpg_keys/index.html.haml +++ b/app/views/profiles/gpg_keys/index.html.haml @@ -3,12 +3,12 @@ = render 'profiles/head' .row.prepend-top-default - .col-lg-3.profile-settings-sidebar + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 = page_title %p GPG keys allow you to verify signed commits. - .col-lg-9 + .col-lg-8 %h5.prepend-top-0 Add a GPG key %p.profile-settings-content diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index 03ab1bb59e4..5d48a35dc4c 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -1,5 +1,5 @@ #modal-create-new-dir.modal - .modal-dialog + .modal-dialog.modal-lg .modal-content .modal-header %a.close{ href: "#", "data-dismiss" => "modal" } × diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 05b7dfe2872..21b6aa4bad9 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -1,5 +1,5 @@ #modal-upload-blob.modal - .modal-dialog + .modal-dialog.modal-lg .modal-content .modal-header %a.close{ href: "#", "data-dismiss" => "modal" } × diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 8e8c911185a..a94d9c14722 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -10,37 +10,28 @@ - if can_create_issue %li - = link_to new_project_issue_path(@project) do - #{ _('New issue') } - + = link_to _('New issue'), new_project_issue_path(@project) - if merge_project %li - = link_to project_new_merge_request_path(merge_project) do - #{ _('New merge request') } - + = link_to _('New merge request'), project_new_merge_request_path(merge_project) - if can_create_snippet %li - = link_to new_project_snippet_path(@project) do - #{ _('New snippet') } + = link_to _('New snippet'), new_project_snippet_path(@project) - if can_create_issue || merge_project || can_create_snippet %li.divider - if can?(current_user, :push_code, @project) %li - = link_to project_new_blob_path(@project, @project.default_branch || 'master') do - #{ _('New file') } + = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') - unless @project.empty_repo? %li - = link_to new_project_branch_path(@project) do - #{ _('New branch') } + = link_to _('New branch'), new_project_branch_path(@project) %li - = link_to new_project_tag_path(@project) do - #{ _('New tag') } + = link_to _('New tag'), new_project_tag_path(@project) - elsif current_user && current_user.already_forked?(@project) %li - = link_to project_new_blob_path(@project, @project.default_branch || 'master') do - #{ _('New file') } + = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') - elsif can?(current_user, :fork_project, @project) %li - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'), @@ -48,5 +39,4 @@ notice_now: edit_in_new_fork_notice_now } - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params) - = link_to fork_path, method: :post do - #{ _('New file') } + = link_to _('New file'), fork_path, method: :post diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 111cbcda266..21a4702a2a9 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -31,11 +31,11 @@ - if current_user && can?(current_user, :fork_project, @project) - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do - = custom_icon('icon_fork') + = sprite_icon('fork', size: 12) %span Fork - else = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do - = custom_icon('icon_fork') + = sprite_icon('fork', size: 12) %span Fork diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index a71333497e6..e779473c239 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -24,7 +24,7 @@ - elsif @build.has_expiring_artifacts? %p.build-detail-row The artifacts will be removed in - %span.js-artifacts-remove= @build.artifacts_expire_at + %span= time_ago_in_words @build.artifacts_expire_at - if @build.artifacts? .btn-group.btn-group-justified{ role: :group } diff --git a/app/views/projects/merge_requests/creations/_diffs.html.haml b/app/views/projects/merge_requests/creations/_diffs.html.haml index 627fc4e9671..5b70e894b39 100644 --- a/app/views/projects/merge_requests/creations/_diffs.html.haml +++ b/app/views/projects/merge_requests/creations/_diffs.html.haml @@ -1 +1,5 @@ -= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false +- if @merge_request.can_be_created + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, show_whitespace_toggle: false +- else + .nothing-here-block + This merge request cannot be created. diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 63aa4e29ec9..2a75b46d376 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -52,7 +52,7 @@ = render_project_pipeline_status(project.pipeline_status) - if forks %span.prepend-left-10 - = sprite_icon('fork') + = sprite_icon('fork', size: 12) = number_with_delimiter(project.forks_count) - if stars %span.prepend-left-10 diff --git a/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml b/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml new file mode 100644 index 00000000000..e94b4f8bb26 --- /dev/null +++ b/changelogs/unreleased/16301-update-removed-assignee-note-to-include-old-assignee-reference.yml @@ -0,0 +1,5 @@ +--- +title: "Update 'removed assignee' note to include old assignee reference" +merge_request: 16301 +author: Maurizio De Santis +type: changed diff --git a/changelogs/unreleased/36571-ignore-root-in-repo.yml b/changelogs/unreleased/36571-ignore-root-in-repo.yml new file mode 100644 index 00000000000..396e82be51b --- /dev/null +++ b/changelogs/unreleased/36571-ignore-root-in-repo.yml @@ -0,0 +1,5 @@ +--- +title: Ignore leading slashes when searching for files within context of repository. +merge_request: +author: Andrew McCallum +type: fixed diff --git a/changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml b/changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml new file mode 100644 index 00000000000..813b9ab81fa --- /dev/null +++ b/changelogs/unreleased/37898-increase-readability-of-colored-text-in-job-output-log.yml @@ -0,0 +1,5 @@ +--- +title: increase-readability-of-colored-text-in-job-output-log +merge_request: +author: +type: other diff --git a/changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml b/changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml new file mode 100644 index 00000000000..c57caf31d10 --- /dev/null +++ b/changelogs/unreleased/40818-last-push-widget-does-not-appear-after-pushing-new-commit.yml @@ -0,0 +1,5 @@ +--- +title: Last push widget will show banner for new pushes to previously merged branch +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml b/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml new file mode 100644 index 00000000000..bb5c1fdf082 --- /dev/null +++ b/changelogs/unreleased/41476-enable-project-milestons-deletion-via-api.yml @@ -0,0 +1,5 @@ +--- +title: Enables Project Milestone Deletion via the API +merge_request: 16478 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml b/changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml new file mode 100644 index 00000000000..48893862071 --- /dev/null +++ b/changelogs/unreleased/41666-cannot-search-with-keyword-merge-2.yml @@ -0,0 +1,5 @@ +--- +title: Only highlight search results under the highlighting size limit +merge_request: 16462 +author: +type: performance diff --git a/changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml b/changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml new file mode 100644 index 00000000000..3a6fa425c9c --- /dev/null +++ b/changelogs/unreleased/41666-cannot-search-with-keyword-merge.yml @@ -0,0 +1,6 @@ +--- +title: Fix file search results when they match file contents with a number between + two colons +merge_request: 16462 +author: +type: fixed diff --git a/changelogs/unreleased/42031-fix-links-to-uploads-in-wikis.yml b/changelogs/unreleased/42031-fix-links-to-uploads-in-wikis.yml new file mode 100644 index 00000000000..027cb414f23 --- /dev/null +++ b/changelogs/unreleased/42031-fix-links-to-uploads-in-wikis.yml @@ -0,0 +1,5 @@ +--- +title: Fix links to uploaded files on wiki pages +merge_request: 16499 +author: +type: fixed diff --git a/changelogs/unreleased/42046-fork-icon.yml b/changelogs/unreleased/42046-fork-icon.yml new file mode 100644 index 00000000000..def89ff7b08 --- /dev/null +++ b/changelogs/unreleased/42046-fork-icon.yml @@ -0,0 +1,5 @@ +--- +title: Fix giant fork icons on forks page +merge_request: 16474 +author: +type: fixed diff --git a/changelogs/unreleased/42047-pg-10-support.yml b/changelogs/unreleased/42047-pg-10-support.yml new file mode 100644 index 00000000000..f98e59329c3 --- /dev/null +++ b/changelogs/unreleased/42047-pg-10-support.yml @@ -0,0 +1,5 @@ +--- +title: Support PostgreSQL 10 +merge_request: 16471 +author: +type: added diff --git a/changelogs/unreleased/issue_41460.yml b/changelogs/unreleased/issue_41460.yml new file mode 100644 index 00000000000..24d3eae6bf8 --- /dev/null +++ b/changelogs/unreleased/issue_41460.yml @@ -0,0 +1,5 @@ +--- +title: Fix error on changes tab when merge request cannot be created +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/jej-lfs-rev-list-handles-non-utf-paths-41627.yml b/changelogs/unreleased/jej-lfs-rev-list-handles-non-utf-paths-41627.yml new file mode 100644 index 00000000000..24f18c07ac5 --- /dev/null +++ b/changelogs/unreleased/jej-lfs-rev-list-handles-non-utf-paths-41627.yml @@ -0,0 +1,5 @@ +--- +title: Prevent RevList failing on non utf8 paths +merge_request: 16440 +author: +type: fixed diff --git a/changelogs/unreleased/mr-status-box-update.yml b/changelogs/unreleased/mr-status-box-update.yml new file mode 100644 index 00000000000..68265be16a1 --- /dev/null +++ b/changelogs/unreleased/mr-status-box-update.yml @@ -0,0 +1,5 @@ +--- +title: Fixed merge request status badge not updating after merging +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/winh-style-modals.yml b/changelogs/unreleased/winh-style-modals.yml new file mode 100644 index 00000000000..b7d0293960d --- /dev/null +++ b/changelogs/unreleased/winh-style-modals.yml @@ -0,0 +1,5 @@ +--- +title: Adjust modal style to new design +merge_request: 16310 +author: +type: other diff --git a/config/initializers/ar5_pg_10_support.rb b/config/initializers/ar5_pg_10_support.rb new file mode 100644 index 00000000000..6fae770015c --- /dev/null +++ b/config/initializers/ar5_pg_10_support.rb @@ -0,0 +1,57 @@ +raise "Vendored ActiveRecord 5 code! Delete #{__FILE__}!" if ActiveRecord::VERSION::MAJOR >= 5 + +require 'active_record/connection_adapters/postgresql_adapter' +require 'active_record/connection_adapters/postgresql/schema_statements' + +# +# Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330 +# +# Updates sequence logic to support PostgreSQL 10. +# +# rubocop:disable all +module ActiveRecord + module ConnectionAdapters + + # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu + # to work. In ActiveRecord 4, it is protected. + # https://github.com/mbleigh/seed-fu/issues/123 + class PostgreSQLAdapter + public :postgresql_version + end + + module PostgreSQL + module SchemaStatements + # Resets the sequence of a table's primary key to the maximum value. + def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: + unless pk and sequence + default_pk, default_sequence = pk_and_sequence_for(table) + + pk ||= default_pk + sequence ||= default_sequence + end + + if @logger && pk && !sequence + @logger.warn "#{table} has primary key #{pk} with no default sequence" + end + + if pk && sequence + quoted_sequence = quote_table_name(sequence) + max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}") + if max_pk.nil? + if postgresql_version >= 100000 + minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass") + else + minvalue = select_value("SELECT min_value FROM #{quoted_sequence}") + end + end + + select_value <<-end_sql, 'SCHEMA' + SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false}) + end_sql + end + end + end + end + end +end +# rubocop:enable all diff --git a/db/migrate/20171207185153_add_merge_request_state_index.rb b/db/migrate/20171207185153_add_merge_request_state_index.rb new file mode 100644 index 00000000000..72f846c5c38 --- /dev/null +++ b/db/migrate/20171207185153_add_merge_request_state_index.rb @@ -0,0 +1,18 @@ +class AddMergeRequestStateIndex < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :merge_requests, [:source_project_id, :source_branch], + where: "state = 'opened'", + name: 'index_merge_requests_on_source_project_and_branch_state_opened' + end + + def down + remove_concurrent_index_by_name :merge_requests, + 'index_merge_requests_on_source_project_and_branch_state_opened' + end +end diff --git a/db/schema.rb b/db/schema.rb index f47accca21a..a32d20b8f28 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1109,6 +1109,7 @@ ActiveRecord::Schema.define(version: 20180105212544) do add_index "merge_requests", ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)", using: :btree add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree + add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)", using: :btree add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree add_index "merge_requests", ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 84930f0bdc9..d35e940d7b1 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -93,6 +93,19 @@ Parameters: - `start_date` (optional) - The start date of the milestone - `state_event` (optional) - The state event of the milestone (close|activate) +## Delete project milestone + +Only for user with developer access to the project. + +``` +DELETE /projects/:id/milestones/:milestone_id +``` + +Parameters: + +- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user +- `milestone_id` (required) - The ID of the project's milestone + ## Get all issues assigned to a single milestone Gets all issues assigned to a single project milestone. diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md index 10fd2616fab..7f9ab1f3a5e 100644 --- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md @@ -10,6 +10,7 @@ This is what the `.gitlab-ci.yml` file looks like for this project: ```yaml test: + stage: test script: - apt-get update -qy - apt-get install -y nodejs @@ -18,7 +19,7 @@ test: - bundle exec rake test staging: - type: deploy + stage: deploy script: - gem install dpl - dpl --provider=heroku --app=gitlab-ci-ruby-test-staging --api-key=$HEROKU_STAGING_API_KEY @@ -26,7 +27,7 @@ staging: - master production: - type: deploy + stage: deploy script: - gem install dpl - dpl --provider=heroku --app=gitlab-ci-ruby-test-prod --api-key=$HEROKU_PRODUCTION_API_KEY diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 8db617594e5..f41d31797af 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -201,7 +201,7 @@ You can combine one or more of the following: - Keep all file names in lower case. - Consider using PNG images instead of JPEG. - Compress all images with <https://tinypng.com/> or similar tool. -- Compress gifs with <https://ezgif.com/optimize> or similar toll. +- Compress gifs with <https://ezgif.com/optimize> or similar tool. - Images should be used (only when necessary) to _illustrate_ the description of a process, not to _replace_ it. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index baecf9455b0..272148033b5 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -121,7 +121,7 @@ Existing users using GitLab with MySQL/MariaDB are advised to ### PostgreSQL Requirements -As of GitLab 10.0, PostgreSQL 9.6 or newer (but less than 10) is required, and earlier versions are +As of GitLab 10.0, PostgreSQL 9.6 or newer is required, and earlier versions are not supported. We highly recommend users to use PostgreSQL 9.6 as this is the PostgreSQL version used for development and testing. diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index a116ab3c9bd..9c205514b3a 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -38,6 +38,7 @@ module API builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) + builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project) present paginate(builds), with: Entities::Job end diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 0cb209a02d0..306dc0e63d7 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -60,6 +60,15 @@ module API update_milestone_for(user_project) end + desc 'Remove a project milestone' + delete ":id/milestones/:milestone_id" do + authorize! :admin_milestone, user_project + + user_project.milestones.find(params[:milestone_id]).destroy + + status(204) + end + desc 'Get all issues for a single project milestone' do success Entities::IssueBasic end diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb index fa0bef39602..ac76fece931 100644 --- a/lib/api/v3/builds.rb +++ b/lib/api/v3/builds.rb @@ -36,6 +36,7 @@ module API builds = user_project.builds.order('id DESC') builds = filter_builds(builds, params[:scope]) + builds = builds.preload(:user, :job_artifacts_archive, :runner, pipeline: :project) present paginate(builds), with: ::API::V3::Entities::Build end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 05aa79dc160..f27ce4d2b2b 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -108,7 +108,10 @@ module Backup $progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}" exit 1 elsif backup_file_list.many? && ENV["BACKUP"].nil? - $progress.puts 'Found more than one backup, please specify which one you want to restore:' + $progress.puts 'Found more than one backup:' + # print list of available backups + $progress.puts " " + available_timestamps.join("\n ") + $progress.puts 'Please specify which one you want to restore:' $progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup' exit 1 end @@ -169,6 +172,10 @@ module Backup @backup_file_list ||= Dir.glob("*#{FILE_NAME_SUFFIX}") end + def available_timestamps + @backup_file_list.map {|item| item.gsub("#{FILE_NAME_SUFFIX}", "")} + end + def connect_to_remote_directory(connection_settings) # our settings use string keys, but Fog expects symbols connection = ::Fog::Storage.new(connection_settings.symbolize_keys) diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb index e7a1ec8457d..072d24e5a11 100644 --- a/lib/banzai/filter/wiki_link_filter/rewriter.rb +++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb @@ -9,6 +9,10 @@ module Banzai end def apply_rules + # Special case: relative URLs beginning with `/uploads/` refer to + # user-uploaded files and will be handled elsewhere. + return @uri.to_s if @uri.relative? && @uri.path.starts_with?('/uploads/') + apply_file_link_rules! apply_hierarchical_link_rules! apply_relative_link_rules! diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 016437b2419..46e0c0e82a2 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -436,6 +436,16 @@ module Gitlab parent_ids.size > 1 end + def tree_entry(path) + @repository.gitaly_migrate(:commit_tree_entry) do |is_migrated| + if is_migrated + gitaly_tree_entry(path) + else + rugged_tree_entry(path) + end + end + end + def to_gitaly_commit return raw_commit if raw_commit.is_a?(Gitaly::GitCommit) @@ -450,11 +460,6 @@ module Gitlab ) end - # Is this the same as Blob.find_entry_by_path ? - def rugged_tree_entry(path) - rugged_commit.tree.path(path) - end - private def init_from_hash(hash) @@ -501,6 +506,28 @@ module Gitlab SERIALIZE_KEYS end + def gitaly_tree_entry(path) + # We're only interested in metadata, so limit actual data to 1 byte + # since Gitaly doesn't support "send no data" option. + entry = @repository.gitaly_commit_client.tree_entry(id, path, 1) + return unless entry + + # To be compatible with the rugged format + entry = entry.to_h + entry.delete(:data) + entry[:name] = File.basename(path) + entry[:type] = entry[:type].downcase + + entry + end + + # Is this the same as Blob.find_entry_by_path ? + def rugged_tree_entry(path) + rugged_commit.tree.path(path) + rescue Rugged::TreeError + nil + end + def gitaly_commit_author_from_rugged(author_or_committer) Gitaly::CommitAuthor.new( name: author_or_committer[:name].b, diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index 4974205b8fd..f8b2e7e0e21 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -95,7 +95,7 @@ module Gitlab object_output.map do |output_line| sha, path = output_line.split(' ', 2) - next if require_path && path.blank? + next if require_path && path.to_s.empty? sha end.reject(&:nil?) diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index e2662fc362b..7771b15069b 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -44,25 +44,20 @@ module Gitlab ref = nil filename = nil basename = nil + data = "" startline = 0 - result.each_line.each_with_index do |line, index| - matches = line.match(/^(?<ref>[^:]*):(?<filename>.*):(?<startline>\d+):/) - if matches + result.strip.each_line.each_with_index do |line, index| + prefix ||= line.match(/^(?<ref>[^:]*):(?<filename>.*)\x00(?<startline>\d+)\x00/)&.tap do |matches| ref = matches[:ref] filename = matches[:filename] startline = matches[:startline] startline = startline.to_i - index extname = Regexp.escape(File.extname(filename)) basename = filename.sub(/#{extname}$/, '') - break end - end - - data = "" - result.each_line do |line| - data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') + data << line.sub(prefix.to_s, '') end FoundBlob.new( diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb index 7e2366847f4..92db7284e0e 100644 --- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb @@ -4,6 +4,16 @@ describe Projects::MergeRequests::CreationsController do let(:project) { create(:project, :repository) } let(:user) { project.owner } let(:fork_project) { create(:forked_project_with_submodules) } + let(:get_diff_params) do + { + namespace_id: fork_project.namespace.to_param, + project_id: fork_project, + merge_request: { + source_branch: 'remove-submodule', + target_branch: 'master' + } + } + end before do fork_project.add_master(user) @@ -13,18 +23,23 @@ describe Projects::MergeRequests::CreationsController do describe 'GET new' do context 'merge request that removes a submodule' do - render_views - it 'renders new merge request widget template' do - get :new, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - } + get :new, get_diff_params + + expect(response).to be_success + end + end + end + + describe 'GET diffs' do + context 'when merge request cannot be created' do + it 'does not assign diffs var' do + allow_any_instance_of(MergeRequest).to receive(:can_be_created).and_return(false) + + get :diffs, get_diff_params.merge(format: 'json') expect(response).to be_success + expect(assigns[:diffs]).to be_nil end end end @@ -37,14 +52,7 @@ describe Projects::MergeRequests::CreationsController do end it 'renders JSON including serialized pipelines' do - get :pipelines, - namespace_id: fork_project.namespace.to_param, - project_id: fork_project, - merge_request: { - source_branch: 'remove-submodule', - target_branch: 'master' - }, - format: :json + get :pipelines, get_diff_params.merge(format: 'json') expect(response).to be_ok expect(json_response).to have_key 'pipelines' diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 04620f6d88c..a030796c54e 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -22,6 +22,13 @@ describe BlobHelper do expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">:type "assem"))</span></code></pre>]) end + it 'returns plaintext for long blobs' do + stub_const('Blob::MAXIMUM_TEXT_HIGHLIGHT_SIZE', 1) + result = helper.highlight(blob_name, blob_content) + + expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line" lang="">(make-pathname :defaults name</span>\n<span id="LC2" class="line" lang="">:type "assem"))</span></code></pre>]) + end + it 'highlights single block' do expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line" lang="common_lisp"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span> <span id="LC2" class="line" lang="common_lisp"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>] diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js index b740c9ed893..feb341d22e6 100644 --- a/spec/javascripts/job_spec.js +++ b/spec/javascripts/job_spec.js @@ -52,11 +52,6 @@ describe('Job', () => { expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false); expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); }); - - it('displays the remove date correctly', () => { - const removeDateElement = document.querySelector('.js-artifacts-remove'); - expect(removeDateElement.innerText.trim()).toBe('1 year remaining'); - }); }); describe('running build', () => { diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 9d6ea3781bc..bae3219b043 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -70,8 +70,8 @@ import IssuablesHelper from '~/helpers/issuables_helper'; beforeEach(() => { loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); this.el = document.querySelector('.js-issuable-actions'); - const merge = new MergeRequest(); - merge.hideCloseButton(); + new MergeRequest(); // eslint-disable-line no-new + MergeRequest.hideCloseButton(); }); it('hides the dropdown close item and selects the next item', () => { @@ -90,8 +90,7 @@ import IssuablesHelper from '~/helpers/issuables_helper'; beforeEach(() => { loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); this.el = document.querySelector('.js-issuable-actions'); - const merge = new MergeRequest(); - merge.hideCloseButton(); + MergeRequest.hideCloseButton(); }); it('hides the close button', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 1127576617b..11858e45386 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -371,6 +371,10 @@ describe('MRWidgetReadyToMerge', () => { }); }); + beforeEach(() => { + loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + }); + it('should call start and stop polling when MR merged', (done) => { spyOn(eventHub, '$emit'); spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); @@ -392,6 +396,47 @@ describe('MRWidgetReadyToMerge', () => { }, 333); }); + it('updates status box', (done) => { + spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); + spyOn(vm, 'initiateRemoveSourceBranchPolling'); + + vm.handleMergePolling(() => {}, () => {}); + + setTimeout(() => { + const statusBox = document.querySelector('.status-box'); + expect(statusBox.classList.contains('status-box-merged')).toBeTruthy(); + expect(statusBox.textContent).toContain('Merged'); + + done(); + }); + }); + + it('hides close button', (done) => { + spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); + spyOn(vm, 'initiateRemoveSourceBranchPolling'); + + vm.handleMergePolling(() => {}, () => {}); + + setTimeout(() => { + expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy(); + + done(); + }); + }); + + it('updates merge request count badge', (done) => { + spyOn(vm.service, 'poll').and.returnValue(returnPromise('merged')); + spyOn(vm, 'initiateRemoveSourceBranchPolling'); + + vm.handleMergePolling(() => {}, () => {}); + + setTimeout(() => { + expect(document.querySelector('.js-merge-counter').textContent).toBe('0'); + + done(); + }); + }); + it('should continue polling until MR is merged', (done) => { spyOn(vm.service, 'poll').and.returnValue(returnPromise('some_other_state')); spyOn(vm, 'initiateRemoveSourceBranchPolling'); diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index b68301a066a..5100f5737c2 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -194,6 +194,12 @@ describe Backup::Manager do ) end + it 'prints the list of available backups' do + expect { subject.unpack }.to raise_error SystemExit + expect(progress).to have_received(:puts) + .with(a_string_matching('1451606400_2016_01_01_1.2.3\n 1451520000_2015_12_31')) + end + it 'fails the operation and prints an error' do expect { subject.unpack }.to raise_error SystemExit expect(progress).to have_received(:puts) diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb index 9596f004052..50d053011b3 100644 --- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb +++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb @@ -10,15 +10,23 @@ describe Banzai::Filter::WikiLinkFilter do it "doesn't rewrite absolute links" do filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0] + expect(filtered_link.attribute('href').value).to eq('http://example.com:8000/') end + it "doesn't rewrite links to project uploads" do + filtered_link = filter("<a href='/uploads/a.test'>Link</a>", project_wiki: wiki).children[0] + + expect(filtered_link.attribute('href').value).to eq('/uploads/a.test') + end + describe "invalid links" do invalid_links = ["http://:8080", "http://", "http://:8080/path"] invalid_links.each do |invalid_link| it "doesn't rewrite invalid invalid_links like #{invalid_link}" do filtered_link = filter("<a href='#{invalid_link}'>Link</a>", project_wiki: wiki).children[0] + expect(filtered_link.attribute('href').value).to eq(invalid_link) end end diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb index 98730602863..d21183b668b 100644 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb @@ -15,6 +15,10 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :t .to receive(:commits_count=).and_return(nil) end + after do + [Project, MergeRequest, MergeRequestDiff].each(&:reset_column_information) + end + def diffs_to_hashes(diffs) diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access) end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index eaf74951b0e..90fbef9d248 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -39,7 +39,7 @@ describe Gitlab::Git::RevList do ] expect(rev_list).to receive(:popen).with(*params) do |*_, lazy_block:| - lazy_block.call(output.split("\n").lazy) + lazy_block.call(output.lines.lazy.map(&:chomp)) end end @@ -64,6 +64,15 @@ describe Gitlab::Git::RevList do expect(rev_list.new_objects(require_path: true)).to eq(%w[sha2]) end + it 'can handle non utf-8 paths' do + non_utf_char = [0x89].pack("c*").force_encoding("UTF-8") + stub_lazy_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_lazy_popen_rev_list('newrev', '--not', '--all', '--objects', output: "sha1\nsha2") diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index 17937726f2c..1ebb0105cf5 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -70,15 +70,6 @@ describe Gitlab::ProjectSearchResults do subject { described_class.parse_search_result(search_result) } - it 'can correctly parse filenames including ":"' do - special_char_result = "\nmaster:testdata/project::function1.yaml-1----\nmaster:testdata/project::function1.yaml:2:test: data1\n" - - blob = described_class.parse_search_result(special_char_result) - - expect(blob.ref).to eq('master') - expect(blob.filename).to eq('testdata/project::function1.yaml') - end - it "returns a valid FoundBlob" do is_expected.to be_an Gitlab::SearchResults::FoundBlob expect(subject.id).to be_nil @@ -90,8 +81,32 @@ describe Gitlab::ProjectSearchResults do expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") end + context 'when the matching filename contains a colon' do + let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/project::function1.yaml') + expect(subject.basename).to eq('testdata/project::function1') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq('---') + end + end + + context 'when the matching content contains a number surrounded by colons' do + let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" } + + it 'returns a valid FoundBlob' do + expect(subject.filename).to eq('testdata/foo.txt') + expect(subject.basename).to eq('testdata/foo') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(1) + expect(subject.data).to eq('blah:9:blah') + end + end + context "when filename has extension" do - let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } + let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" } it { expect(subject.path).to eq('CONTRIBUTE.md') } it { expect(subject.filename).to eq('CONTRIBUTE.md') } @@ -99,7 +114,7 @@ describe Gitlab::ProjectSearchResults do end context "when file under directory" do - let(:search_result) { "master:a/b/c.md:5:a b c\n" } + let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" } it { expect(subject.path).to eq('a/b/c.md') } it { expect(subject.filename).to eq('a/b/c.md') } @@ -144,7 +159,7 @@ describe Gitlab::ProjectSearchResults do end it 'finds by content' do - expect(results).to include("master:Title.md:1:Content\n") + expect(results).to include("master:Title.md\x001\x00Content\n") end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index d3826417762..f8a98b43e46 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -439,15 +439,25 @@ eos end describe '#uri_type' do - it 'returns the URI type at the given path' do - expect(commit.uri_type('files/html')).to be(:tree) - expect(commit.uri_type('files/images/logo-black.png')).to be(:raw) - expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw) - expect(commit.uri_type('files/js/application.js')).to be(:blob) + shared_examples 'URI type' do + it 'returns the URI type at the given path' do + expect(commit.uri_type('files/html')).to be(:tree) + expect(commit.uri_type('files/images/logo-black.png')).to be(:raw) + expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw) + expect(commit.uri_type('files/js/application.js')).to be(:blob) + end + + it "returns nil if the path doesn't exists" do + expect(commit.uri_type('this/path/doesnt/exist')).to be_nil + end + end + + context 'when Gitaly commit_tree_entry feature is enabled' do + it_behaves_like 'URI type' end - it "returns nil if the path doesn't exists" do - expect(commit.uri_type('this/path/doesnt/exist')).to be_nil + context 'when Gitaly commit_tree_entry feature is disabled', :disable_gitaly do + it_behaves_like 'URI type' end end diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb index ad3c3a406d9..bfe7a30b96a 100644 --- a/spec/models/push_event_spec.rb +++ b/spec/models/push_event_spec.rb @@ -63,12 +63,14 @@ describe PushEvent do let(:event2) { create(:push_event, project: project) } let(:event3) { create(:push_event, project: project) } let(:event4) { create(:push_event, project: project) } + let(:event5) { create(:push_event, project: project) } before do create(:push_event_payload, event: event1, ref: 'foo', action: :created) create(:push_event_payload, event: event2, ref: 'bar', action: :created) - create(:push_event_payload, event: event3, ref: 'baz', action: :removed) - create(:push_event_payload, event: event4, ref: 'baz', ref_type: :tag) + create(:push_event_payload, event: event3, ref: 'qux', action: :created) + create(:push_event_payload, event: event4, ref: 'baz', action: :removed) + create(:push_event_payload, event: event5, ref: 'baz', ref_type: :tag) project.repository.create_branch('bar', 'master') @@ -78,6 +80,16 @@ describe PushEvent do target_project: project, source_branch: 'bar' ) + + project.repository.create_branch('qux', 'master') + + create( + :merge_request, + :closed, + source_project: project, + target_project: project, + source_branch: 'qux' + ) end let(:relation) { described_class.without_existing_merge_requests } @@ -86,16 +98,20 @@ describe PushEvent do expect(relation).to include(event1) end - it 'does not include events that have a corresponding merge request' do + it 'does not include events that have a corresponding open merge request' do expect(relation).not_to include(event2) end + it 'includes events that has corresponding closed/merged merge requests' do + expect(relation).to include(event3) + end + it 'does not include events for removed refs' do - expect(relation).not_to include(event3) + expect(relation).not_to include(event4) end it 'does not include events for pushing to tags' do - expect(relation).not_to include(event4) + expect(relation).not_to include(event5) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f3456e5b354..baaa9e3ef44 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -657,7 +657,7 @@ describe Repository do subject { results.first } it { is_expected.to be_an String } - it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") } + it { expect(subject.lines[2]).to eq("master:CHANGELOG\x00190\x00 - Feature: Replace teams with group membership\n") } end end @@ -668,6 +668,18 @@ describe Repository do expect(results.first).to eq('files/html/500.html') end + it 'ignores leading slashes' do + results = repository.search_files_by_name('/files', 'master') + + expect(results.first).to eq('files/html/500.html') + end + + it 'properly handles when query is only slashes' do + results = repository.search_files_by_name('//', 'master') + + expect(results).to match_array([]) + end + it 'properly handles when query is not present' do results = repository.search_files_by_name('', 'master') diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 805496e4a54..e211c347266 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -25,8 +25,10 @@ describe API::Jobs do describe 'GET /projects/:id/jobs' do let(:query) { Hash.new } - before do - get api("/projects/#{project.id}/jobs", api_user), query + before do |example| + unless example.metadata[:skip_before_request] + get api("/projects/#{project.id}/jobs", api_user), query + end end context 'authorized user' do @@ -51,6 +53,23 @@ describe API::Jobs do expect(json_job['pipeline']['status']).to eq job.pipeline.status end + it 'avoids N+1 queries', skip_before_request: true do + first_build = create(:ci_build, :artifacts, pipeline: pipeline) + first_build.runner = create(:ci_runner) + first_build.user = create(:user) + first_build.save + + control_count = ActiveRecord::QueryRecorder.new { go }.count + + second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) + second_build = create(:ci_build, :artifacts, pipeline: second_pipeline) + second_build.runner = create(:ci_runner) + second_build.user = create(:user) + second_build.save + + expect { go }.not_to exceed_query_limit(control_count) + end + context 'filter project with one scope element' do let(:query) { { 'scope' => 'pending' } } @@ -83,6 +102,10 @@ describe API::Jobs do expect(response).to have_gitlab_http_status(401) end end + + def go + get api("/projects/#{project.id}/jobs", api_user), query + end end describe 'GET /projects/:id/pipelines/:pipeline_id/jobs' do diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb index 08ea7314bb3..6c05c166bd6 100644 --- a/spec/requests/api/project_milestones_spec.rb +++ b/spec/requests/api/project_milestones_spec.rb @@ -14,6 +14,46 @@ describe API::ProjectMilestones do let(:route) { "/projects/#{project.id}/milestones" } end + describe 'DELETE /projects/:id/milestones/:milestone_id' do + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + project.add_reporter(reporter) + end + + it 'returns 404 response when the project does not exists' do + delete api("/projects/999/milestones/#{milestone.id}", user) + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 404 response when the milestone does not exists' do + delete api("/projects/#{project.id}/milestones/999", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 from guest user deleting a milestone" do + delete api("/projects/#{project.id}/milestones/#{milestone.id}", guest) + + expect(response).to have_gitlab_http_status(404) + end + + it "rejects a member with reporter access from deleting a milestone" do + delete api("/projects/#{project.id}/milestones/#{milestone.id}", reporter) + + expect(response).to have_gitlab_http_status(403) + end + + it 'deletes the milestone when the user has developer access to the project' do + delete api("/projects/#{project.id}/milestones/#{milestone.id}", user) + + expect(project.milestones.find_by_id(milestone.id)).to be_nil + expect(response).to have_gitlab_http_status(204) + end + end + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do it 'creates an activity event when an milestone is closed' do expect(Event).to receive(:create!) diff --git a/spec/requests/api/v3/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb index a73bb456b52..cdbc5692e19 100644 --- a/spec/requests/api/v3/builds_spec.rb +++ b/spec/requests/api/v3/builds_spec.rb @@ -13,10 +13,12 @@ describe API::V3::Builds do describe 'GET /projects/:id/builds ' do let(:query) { '' } - before do + before do |example| create(:ci_build, :skipped, pipeline: pipeline) - get v3_api("/projects/#{project.id}/builds?#{query}", api_user) + unless example.metadata[:skip_before_request] + get v3_api("/projects/#{project.id}/builds?#{query}", api_user) + end end context 'authorized user' do @@ -40,6 +42,23 @@ describe API::V3::Builds do expect(json_build['pipeline']['status']).to eq build.pipeline.status end + it 'avoids N+1 queries', skip_before_request: true do + first_build = create(:ci_build, :artifacts, pipeline: pipeline) + first_build.runner = create(:ci_runner) + first_build.user = create(:user) + first_build.save + + control_count = ActiveRecord::QueryRecorder.new { go }.count + + second_pipeline = create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) + second_build = create(:ci_build, :artifacts, pipeline: second_pipeline) + second_build.runner = create(:ci_runner) + second_build.user = create(:user) + second_build.save + + expect { go }.not_to exceed_query_limit(control_count) + end + context 'filter project with one scope element' do let(:query) { 'scope=pending' } @@ -84,6 +103,10 @@ describe API::V3::Builds do expect(response).to have_gitlab_http_status(401) end end + + def go + get v3_api("/projects/#{project.id}/builds?#{query}", api_user) + end end describe 'GET /projects/:id/repository/commits/:sha/builds' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 965fd39c967..ab3a257f36f 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -158,7 +158,7 @@ describe SystemNoteService do end it 'builds a correct phrase when assignee removed' do - expect(build_note([assignee1], [])).to eq 'removed assignee' + expect(build_note([assignee1], [])).to eq "unassigned @#{assignee1.username}" end it 'builds a correct phrase when assignees changed' do diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 75de266369d..eec356b9f47 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -90,10 +90,14 @@ codequality: performance: stage: performance - image: - name: sitespeedio/sitespeed.io:6.0.3 - entrypoint: [""] + image: docker:latest + variables: + DOCKER_DRIVER: overlay2 + allow_failure: true + services: + - docker:dind script: + - setup_docker - performance artifacts: paths: @@ -112,7 +116,7 @@ sast: - sast . artifacts: paths: [gl-sast-report.json] - + sast:container: image: docker:latest variables: @@ -260,7 +264,7 @@ production: export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE - + function sast_container() { docker run -d --name db arminc/clair-db:latest docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 @@ -466,26 +470,26 @@ production: --docker-email="$GITLAB_USER_EMAIL" \ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - } - + function performance() { export CI_ENVIRONMENT_URL=$(cat environment_url.txt) - + mkdir gitlab-exporter wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js - + mkdir sitespeed-results - + if [ -f .gitlab-urls.txt ] then sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt - /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt + docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt else - /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results $CI_ENVIRONMENT_URL + docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.0.3 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" fi - + mv sitespeed-results/data/performance.json performance.json } - + function persist_environment_url() { echo $CI_ENVIRONMENT_URL > environment_url.txt } diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml index fdc687b8980..5249449c7f8 100644 --- a/vendor/prometheus/values.yaml +++ b/vendor/prometheus/values.yaml @@ -18,138 +18,100 @@ serverFiles: rule_files: - /etc/config/rules - /etc/config/alerts - scrape_configs: - job_name: prometheus static_configs: - targets: - localhost:9090 - - - job_name: 'kubernetes-apiservers' - - kubernetes_sd_configs: - - role: endpoints - + - job_name: kubernetes-cadvisor scheme: https - tls_config: - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" insecure_skip_verify: true - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" + kubernetes_sd_configs: + - role: node + api_server: https://kubernetes.default.svc:443 + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" relabel_configs: - - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] - action: keep - regex: default;kubernetes;https - - - job_name: 'kubernetes-nodes' + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + - target_label: __address__ + replacement: kubernetes.default.svc:443 + - source_labels: + - __meta_kubernetes_node_name + regex: "(.+)" + target_label: __metrics_path__ + replacement: "/api/v1/nodes/${1}/proxy/metrics/cadvisor" + metric_relabel_configs: + - source_labels: + - pod_name + target_label: environment + regex: "(.+)-.+-.+" + - job_name: kubernetes-nodes scheme: https tls_config: - ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" insecure_skip_verify: true - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token - + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" kubernetes_sd_configs: - - role: node - + - role: node + api_server: https://kubernetes.default.svc:443 + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" relabel_configs: - - action: labelmap - regex: __meta_kubernetes_node_label_(.+) - - target_label: __address__ - replacement: kubernetes.default.svc:443 - - source_labels: [__meta_kubernetes_node_name] - regex: (.+) - target_label: __metrics_path__ - replacement: /api/v1/nodes/${1}/proxy/metrics - - job_name: 'kubernetes-service-endpoints' - - kubernetes_sd_configs: - - role: endpoints - - relabel_configs: - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape] - action: keep - regex: true - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme] - action: replace - target_label: __scheme__ - regex: (https?) - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path] - action: replace - target_label: __metrics_path__ - regex: (.+) - - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] - action: replace - target_label: __address__ - regex: (.+)(?::\d+);(\d+) - replacement: $1:$2 - - action: labelmap - regex: __meta_kubernetes_service_label_(.+) - - source_labels: [__meta_kubernetes_namespace] - action: replace - target_label: kubernetes_namespace - - source_labels: [__meta_kubernetes_service_name] - action: replace - target_label: kubernetes_name - - - job_name: 'prometheus-pushgateway' - honor_labels: true - - kubernetes_sd_configs: - - role: service - - relabel_configs: - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe] - action: keep - regex: pushgateway - - job_name: 'kubernetes-services' - - metrics_path: /probe - params: - module: [http_2xx] - - kubernetes_sd_configs: - - role: service - - relabel_configs: - - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe] - action: keep - regex: true - - source_labels: [__address__] - target_label: __param_target - - target_label: __address__ - replacement: blackbox - - source_labels: [__param_target] - target_label: instance - - action: labelmap - regex: __meta_kubernetes_service_label_(.+) - - source_labels: [__meta_kubernetes_namespace] - target_label: kubernetes_namespace - - source_labels: [__meta_kubernetes_service_name] - target_label: kubernetes_name - - - job_name: 'kubernetes-pods' - + - action: labelmap + regex: __meta_kubernetes_node_label_(.+) + - target_label: __address__ + replacement: kubernetes.default.svc:443 + - source_labels: + - __meta_kubernetes_node_name + regex: "(.+)" + target_label: __metrics_path__ + replacement: "/api/v1/nodes/${1}/proxy/metrics" + metric_relabel_configs: + - source_labels: + - pod_name + target_label: environment + regex: "(.+)-.+-.+" + - job_name: kubernetes-pods + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + insecure_skip_verify: true + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" kubernetes_sd_configs: - - role: pod - + - role: pod + api_server: https://kubernetes.default.svc:443 + tls_config: + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token" relabel_configs: - - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] - action: keep - regex: true - - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] - action: replace - target_label: __metrics_path__ - regex: (.+) - - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] - action: replace - regex: (.+):(?:\d+);(\d+) - replacement: ${1}:${2} - target_label: __address__ - - action: labelmap - regex: __meta_kubernetes_pod_label_(.+) - - source_labels: [__meta_kubernetes_namespace] - action: replace - target_label: kubernetes_namespace - - source_labels: [__meta_kubernetes_pod_name] - action: replace - target_label: kubernetes_pod_name + - source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_scrape + action: keep + regex: 'true' + - source_labels: + - __meta_kubernetes_pod_annotation_prometheus_io_path + action: replace + target_label: __metrics_path__ + regex: "(.+)" + - source_labels: + - __address__ + - __meta_kubernetes_pod_annotation_prometheus_io_port + action: replace + regex: "([^:]+)(?::[0-9]+)?;([0-9]+)" + replacement: "$1:$2" + target_label: __address__ + - action: labelmap + regex: __meta_kubernetes_pod_label_(.+) + - source_labels: + - __meta_kubernetes_namespace + action: replace + target_label: kubernetes_namespace + - source_labels: + - __meta_kubernetes_pod_name + action: replace + target_label: kubernetes_pod_name |