diff options
107 files changed, 1409 insertions, 845 deletions
diff --git a/.scss-lint.yml b/.scss-lint.yml index d2c972fa9c4..16a168b7c60 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -112,7 +112,7 @@ linters: # Reports when you define the same selector twice in a single sheet. MergeableSelector: - enabled: false + enabled: true # Functions, mixins, variables, and placeholders should be declared # with all lowercase letters and hyphens instead of underscores. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bca9944bb1..8ede091ec08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.1.1 (2017-10-31) + +- [FIXED] Auto Devops kubernetes default namespace is now correctly built out of gitlab project group-name. !14642 (Mircea Danila Dumitrescu) +- [FIXED] Forbid the usage of `Redis#keys`. !14889 +- [FIXED] Make the circuitbreaker more robust by adding higher thresholds, and multiple access attempts. !14933 +- [FIXED] Only cache last push event for existing projects when pushing to a fork. !14989 +- [FIXED] Fix bug preventing secondary emails from being confirmed. !15010 +- [FIXED] Fix broken wiki pages that link to a wiki file. !15019 +- [FIXED] Don't rename paths that were freed up when upgrading. !15029 +- [FIXED] Fix bitbucket login. !15051 +- [FIXED] Update gitaly in Gitlab 10.1 to 0.43.1 for temp file cleanup. !15055 +- [FIXED] Use the correct visibility attribute for projects in system hooks. !15065 +- [FIXED] Normalize LDAP DN when looking up identity. +- [FIXED] Adds callback functions for initial request in clusters page. +- [FIXED] Fix missing Import/Export issue assignees. +- [FIXED] Allow boards as top level route. +- [FIXED] Fix widget of locked merge requests not being presented. +- [FIXED] Fix editing issue description in mobile view. +- [FIXED] Fix deletion of container registry or images returning an error. +- [FIXED] Fix the writing of invalid environment refs. +- [CHANGED] Store circuitbreaker settings in the database instead of config. !14842 +- [CHANGED] Update default disabled merge request widget message to reflect a general failure. !14960 +- [PERFORMANCE] Stop merge requests with thousands of commits from timing out. !15063 + ## 10.1.0 (2017-10-22) - [SECURITY] Use a timeout on certain git operations. !14872 diff --git a/app/assets/javascripts/clusters.js b/app/assets/javascripts/clusters.js index 661870c226c..c9fef94efea 100644 --- a/app/assets/javascripts/clusters.js +++ b/app/assets/javascripts/clusters.js @@ -1,6 +1,7 @@ /* globals Flash */ import Visibility from 'visibilityjs'; import axios from 'axios'; +import setAxiosCsrfToken from './lib/utils/axios_utils'; import Poll from './lib/utils/poll'; import { s__ } from './locale'; import initSettingsPanels from './settings_panels'; @@ -17,6 +18,7 @@ import Flash from './flash'; class ClusterService { constructor(options = {}) { this.options = options; + setAxiosCsrfToken(); } fetchData() { return axios.get(this.options.endpoint); diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js new file mode 100644 index 00000000000..45bff245827 --- /dev/null +++ b/app/assets/javascripts/lib/utils/axios_utils.js @@ -0,0 +1,6 @@ +import axios from 'axios'; +import csrf from './csrf'; + +export default function setAxiosCsrfToken() { + axios.defaults.headers.common[csrf.headerKey] = csrf.token; +} diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 8819a0c20f4..def986180fc 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -40,6 +40,10 @@ &.top-block { border-top: none; + + .container-fluid { + background-color: inherit; + } } &.middle-block { @@ -98,10 +102,6 @@ background-color: $white-light; border-top: none; } - - &.top-block .container-fluid { - background-color: inherit; - } } .sub-header-block { diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index e0e46dd73af..1bd94c0acba 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -12,15 +12,15 @@ border-left: 3px solid $border-color; color: $text-color; background: $gray-light; -} -.bs-callout h4 { - margin-top: 0; - margin-bottom: 5px; -} + h4 { + margin-top: 0; + margin-bottom: 5px; + } -.bs-callout p:last-child { - margin-bottom: 0; + p:last-child { + margin-bottom: 0; + } } /* Variations */ diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index ed84b17af6a..ea3007f5e08 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -53,6 +53,14 @@ hr { .str-truncated { @include str-truncated; + + &-60 { + @include str-truncated(60%); + } + + &-100 { + @include str-truncated(100%); + } } .block-truncated { @@ -78,10 +86,17 @@ hr { font-size: 14px; } -table a code { - position: relative; - top: -2px; - margin-right: 3px; +table { + a code { + position: relative; + top: -2px; + margin-right: 3px; + } + + td.permission-x { + background: $table-permission-x-bg !important; + text-align: center; + } } .loading { @@ -266,13 +281,6 @@ img.emoji { margin-bottom: 10px; } -table { - td.permission-x { - background: $table-permission-x-bg !important; - text-align: center; - } -} - .btn-sign-in { text-shadow: none; @@ -338,10 +346,11 @@ table { .dropzone .dz-preview .dz-progress { border-color: $border-color !important; -} -.dropzone .dz-preview .dz-progress .dz-upload { - background: $gl-success !important; + .dz-upload { + background: $gl-success !important; + } + } .dz-message { @@ -402,16 +411,6 @@ table { border-radius: $border-radius-default; } -.str-truncated { - &-60 { - @include str-truncated(60%); - } - - &-100 { - @include str-truncated(100%); - } -} - .tooltip { .tooltip-inner { word-wrap: break-word; diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss index fa5d3833f3e..94a39baa4a6 100644 --- a/app/assets/stylesheets/framework/contextual-sidebar.scss +++ b/app/assets/stylesheets/framework/contextual-sidebar.scss @@ -141,15 +141,15 @@ svg { fill: $gl-text-color-secondary; } - } - .nav-item-name { - flex: 1; - } + .nav-item-name { + flex: 1; + } - li.active { - > a { - font-weight: $gl-font-weight-bold; + &.active { + > a { + font-weight: $gl-font-weight-bold; + } } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 1aa53b8f8cf..08c603edd23 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -727,11 +727,11 @@ .pika-single.animate-picker.is-bound { @include set-visible; -} -.pika-single.animate-picker.is-bound.is-hidden { - @include set-invisible; - overflow: hidden; + &.is-hidden { + @include set-invisible; + overflow: hidden; + } } @mixin dropdown-item-hover { @@ -938,9 +938,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { border-right: 0; } } -} -.projects-dropdown-container { .projects-list-frequent-container, .projects-list-search-container, { padding: 8px 0; @@ -951,11 +949,6 @@ header.header-content .dropdown-menu.projects-dropdown-menu { .projects-list-frequent-container li.section-empty, .projects-list-search-container li.section-empty { padding: 0 15px; - } - - .section-header, - .projects-list-frequent-container li.section-empty, - .projects-list-search-container li.section-empty { color: $gl-text-color-secondary; font-size: $gl-font-size; } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 5833ef939e9..6382551fcc9 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -165,22 +165,36 @@ &:last-child { border-right: none; } - } - td.blame-commit { - padding: 5px 10px; - min-width: 400px; - max-width: 400px; - background: $gray-light; - border-left: 3px solid; + &.blame-commit { + padding: 5px 10px; + min-width: 400px; + max-width: 400px; + background: $gray-light; + border-left: 3px solid; + + .commit-row-title { + display: flex; + } + + .item-title { + flex: 1; + margin-right: 0.5em; + } + } + + &.line-numbers { + float: none; + border-left: 1px solid $blame-line-numbers-border; - .commit-row-title { - display: flex; + i { + float: none; + margin-right: 0; + } } - .item-title { - flex: 1; - margin-right: 0.5em; + &.lines { + padding: 0; } } @@ -195,20 +209,6 @@ border-left-color: mix($blame-gray, $blame-cyan, $i / 4.0 * 100%); } } - - td.line-numbers { - float: none; - border-left: 1px solid $blame-line-numbers-border; - - i { - float: none; - margin-right: 0; - } - } - - td.lines { - padding: 0; - } } &.logs { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 0d80a85d521..dbe5067fcf5 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -469,10 +469,10 @@ word-break: break-all; } } -} -.filter-dropdown-item.droplab-item-active .btn { - @extend %filter-dropdown-item-btn-hover; + &.droplab-item-active .btn { + @extend %filter-dropdown-item-btn-hover; + } } .filter-dropdown-loading { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 62ba74ff582..5d777f0d468 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -352,7 +352,77 @@ .header-user .dropdown-menu-nav, .header-new .dropdown-menu-nav { - margin-top: $dropdown-vertical-offset; + margin-top: 4px; +} + +.search { + margin: 4px 8px 0; + + form { + height: 32px; + border: 0; + border-radius: $border-radius-default; + transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s; + + &:hover { + box-shadow: none; + } + } + + .search-input { + color: $white-light; + background: none; + transition: color ease-in-out 0.15s; + } + + .search-input::placeholder { + transition: color ease-in-out 0.15s; + } + + .location-badge { + font-size: 12px; + margin: -4px 4px -4px -4px; + line-height: 25px; + padding: 4px 8px; + border-radius: 2px 0 0 2px; + height: 32px; + transition: border-color ease-in-out 0.15s; + } + + &.search-active { + form { + background-color: rgba($indigo-200, .3); + box-shadow: none; + + .search-input { + color: $gl-text-color; + transition: color ease-in-out 0.15s; + } + + .search-input::placeholder { + color: $gl-text-color-tertiary; + } + + .search-input-wrap { + .search-icon, + .clear-icon { + color: $gl-text-color-tertiary; + transition: color ease-in-out 0.15s; + } + } + } + + .location-badge { + background-color: $nav-badge-bg; + border-color: $border-color; + } + + .search-input-wrap { + .clear-icon { + color: $white-light; + } + } + } } .breadcrumbs { diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 69d19ea2962..cb324ccc440 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -30,10 +30,10 @@ body { .container { padding-top: 0; z-index: 5; -} -.container .content { - margin: 0; + .content { + margin: 0; + } } .navless-container { @@ -82,26 +82,26 @@ body { transition: background-color 0.15s, border-color 0.15s; background-color: $orange-500; border-color: $orange-500; - } - .alert-warning + .alert-warning { - background-color: $orange-600; - border-color: $orange-600; - } + &:only-of-type { + background-color: $orange-500; + border-color: $orange-500; + } - .alert-warning + .alert-warning + .alert-warning { - background-color: $orange-700; - border-color: $orange-700; - } + + .alert-warning { + background-color: $orange-600; + border-color: $orange-600; - .alert-warning + .alert-warning + .alert-warning + .alert-warning { - background-color: $orange-800; - border-color: $orange-800; - } + + .alert-warning { + background-color: $orange-700; + border-color: $orange-700; - .alert-warning:only-of-type { - background-color: $orange-500; - border-color: $orange-500; + + .alert-warning { + background-color: $orange-800; + border-color: $orange-800; + } + } + } } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index d43f998cb82..511608c618c 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -299,40 +299,40 @@ ul.indent-list { } } -.group-list-tree .avatar-container.content-loading { - position: relative; +.group-list-tree { + .avatar-container.content-loading { + position: relative; - > a, - > a .avatar { - height: 100%; - border-radius: 50%; - } + > a, + > a .avatar { + height: 100%; + border-radius: 50%; + } - > a { - padding: 2px; - } + > a { + padding: 2px; - > a .avatar { - border: 2px solid $white-normal; + .avatar { + border: 2px solid $white-normal; - &.identicon { - line-height: 30px; + &.identicon { + line-height: 30px; + } + } } - } - &::after { - content: ""; - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - border: 2px outset $kdb-border; - border-radius: 50%; - animation: spin-avatar 3s infinite linear; + &::after { + content: ""; + position: absolute; + height: 100%; + width: 100%; + background-color: transparent; + border: 2px outset $kdb-border; + border-radius: 50%; + animation: spin-avatar 3s infinite linear; + } } -} -.group-list-tree { .folder-toggle-wrap { float: left; line-height: $list-text-height; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index e3920b5d3d9..0a5a16c09b0 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -173,21 +173,8 @@ ul > li { white-space: nowrap; } -} - -@media(max-width: $screen-xs-max) { - .atwho-view-ul { - width: 350px; - } - - .atwho-view ul li { - overflow: hidden; - text-overflow: ellipsis; - } -} -// TODO: fallback to global style -.atwho-view { + // TODO: fallback to global style .atwho-view-ul { padding: 8px 1px; @@ -220,3 +207,14 @@ } } } + +@media(max-width: $screen-xs-max) { + .atwho-view-ul { + width: 350px; + } + + .atwho-view ul li { + overflow: hidden; + text-overflow: ellipsis; + } +} diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss index 3fd2549b143..9e1f77e5726 100644 --- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss +++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss @@ -340,11 +340,64 @@ } } -.project-item-select-holder.btn-group { - display: flex; - max-width: 350px; - overflow: hidden; - float: right; +.page-with-layout-nav { + .right-sidebar { + top: ($header-height + 1) * 2; + } + + &.page-with-sub-nav { + .right-sidebar { + top: ($header-height + 1) * 3; + + &.affix { + top: $header-height; + } + } + } +} + +.with-performance-bar .page-with-layout-nav { + .right-sidebar { + top: ($header-height + 1) * 2 + $performance-bar-height; + } + + &.page-with-sub-nav { + .right-sidebar { + top: ($header-height + 1) * 3 + $performance-bar-height; + + &.affix { + top: $header-height + $performance-bar-height; + } + } + } +} + +@media (max-width: $screen-xs-max) { + .top-area { + flex-flow: row wrap; + + .nav-controls { + $controls-margin: $btn-xs-side-margin - 2px; + flex: 0 0 100%; + + &.controls-flex { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; + padding: 0 0 $gl-padding-top; + } + + .controls-item, + .controls-item-full, + .controls-item:last-child { + flex: 1 1 35%; + display: block; + width: 100%; + margin: $controls-margin; + } + } + } .new-project-item-link { white-space: nowrap; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 621eec4f158..aa35cd9bea4 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -60,22 +60,12 @@ border-radius: $border-radius-base; border: 1px solid $dropdown-border-color; min-width: 175px; - color: $gl-text-color; - z-index: 999; + color: $gl-grayish-blue; } -.select2-drop-mask { - z-index: 998; -} - -.select2-drop.select2-drop-above.select2-drop-active { - border-top: 1px solid $dropdown-border-color; - margin-top: -6px; -} - -.select2-results li.select2-result-with-children > .select2-result-label { - font-weight: $gl-font-weight-bold; - color: $gl-text-color; +.select2-results .select2-result-label, +.select2-more-results { + padding: 10px 15px; } .select2-container-active { @@ -144,58 +134,46 @@ .select2-drop-auto-width & { padding: 15px 15px 5px; } -} -.select2-search input { - padding: 2px 25px 2px 5px; - background: $white-light image-url('select2.png'); - background-repeat: no-repeat; - background-position: right 0 bottom 6px; - border: 1px solid $input-border; - border-radius: $border-radius-default; - transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; - - &:focus { - border-color: $input-border-focus; + input { + padding: 2px 25px 2px 5px; + background: $white-light image-url('select2.png'); + background-repeat: no-repeat; + background-position: right 0 bottom 6px; + border: 1px solid $input-border; + border-radius: $border-radius-default; + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + + &:focus { + border-color: $input-border-focus; + } + + &.select2-active { + background-color: $white-light; + background-image: image-url('select2-spinner.gif') !important; + background-repeat: no-repeat; + background-position: right 5px center !important; + background-size: 16px 16px !important; + } } } -.select2-search input.select2-active { - background-color: $white-light; - background-image: image-url('select2-spinner.gif') !important; - background-repeat: no-repeat; - background-position: right 5px center !important; - background-size: 16px 16px !important; +.select2-results .select2-no-results, +.select2-results .select2-searching, +.select2-results .select2-ajax-error, +.select2-results .select2-selection-limit { + background: $gray-light; + display: list-item; + padding: 10px 15px; } .select2-results { margin: 0; - padding: #{$gl-padding / 2} 0; - - .select2-no-results, - .select2-searching, - .select2-ajax-error, - .select2-selection-limit { - background: transparent; - padding: #{$gl-padding / 2} $gl-padding; - } - - .select2-result-label, - .select2-more-results { - padding: #{$gl-padding / 2} $gl-padding; - } + padding: 10px 0; - .select2-highlighted { - background: transparent; + li.select2-result-with-children > .select2-result-label { + font-weight: $gl-font-weight-bold; color: $gl-text-color; - - .select2-result-label { - background: $dropdown-item-hover-bg; - } - } - - .select2-result { - padding: 0 1px; } } @@ -212,6 +190,8 @@ } .select2-highlighted { + background: $gl-link-color !important; + .group-result { .group-path { color: $white-light; diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 65b140cd7f8..c3d8f0c61a2 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -217,13 +217,31 @@ $white-gc-bg: #eaf2f5; .cp { color: $white-cp; font-weight: $gl-font-weight-bold; } .c1 { color: $white-c1; font-style: italic; } .cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; } - .gd { color: $white-gd; background-color: $white-gd-bg; } - .gd .x { color: $white-gd-x; background-color: $white-gd-x-bg; } + + .gd { + color: $white-gd; + background-color: $white-gd-bg; + + .x { + color: $white-gd-x; + background-color: $white-gd-x-bg; + } + } + .ge { font-style: italic; } .gr { color: $white-gr; } .gh { color: $white-gh; } - .gi { color: $white-gi; background-color: $white-gi-bg; } - .gi .x { color: $white-gi-x; background-color: $white-gi-x-bg; } + + .gi { + color: $white-gi; + background-color: $white-gi-bg; + + .x { + color: $white-gi-x; + background-color: $white-gi-x-bg; + } + } + .go { color: $white-go; } .gp { color: $white-gp; } .gs { font-weight: $gl-font-weight-bold; } diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss index fbe538ad1d7..658ac26fca9 100644 --- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss +++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss @@ -158,13 +158,31 @@ span.highlight_word { .cp { color: $highlighted-cp; font-weight: $gl-font-weight-bold; } .c1 { color: $highlighted-c1; font-style: italic; } .cs { color: $highlighted-cs; font-weight: $gl-font-weight-bold; font-style: italic; } -.gd { color: $highlighted-gd; background-color: $highlighted-gd-bg; } -.gd .x { color: $highlighted-gd; background-color: $highlighted-gd-x-bg; } + +.gd { + color: $highlighted-gd; + background-color: $highlighted-gd-bg; + + .x { + color: $highlighted-gd; + background-color: $highlighted-gd-x-bg; + } +} + .ge { font-style: italic; } .gr { color: $highlighted-gr; } .gh { color: $highlighted-gh; } -.gi { color: $highlighted-gi; background-color: $highlighted-gi-bg; } -.gi .x { color: $highlighted-gi; background-color: $highlighted-gi-x-bg; } + +.gi { + color: $highlighted-gi; + background-color: $highlighted-gi-bg; + + .x { + color: $highlighted-gi; + background-color: $highlighted-gi-x-bg; + } +} + .go { color: $highlighted-go; } .gp { color: $highlighted-gp; } .gs { font-weight: $gl-font-weight-bold; } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 91296b354a7..278ec16bcd9 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -72,7 +72,7 @@ } .boards-list { - height: calc(100vh - 152px); + height: calc(100vh - 105px); width: 100%; padding-top: 25px; padding-bottom: 25px; @@ -81,10 +81,14 @@ overflow-x: scroll; white-space: nowrap; - @media (min-width: $screen-sm-min) { + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + height: calc(100vh - 90px); + } + + @media (min-width: $screen-md-min) { height: 475px; // Needed for PhantomJS // scss-lint:disable DuplicateProperty - height: calc(100vh - 222px); + height: calc(100vh - 160px); // scss-lint:enable DuplicateProperty min-height: 475px; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index e87ffe4f374..46978be8ba0 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -68,18 +68,18 @@ &.affix { top: $header-height; - } - // with sidebar - &.affix.sidebar-expanded { - right: 306px; - left: 16px; - } + // with sidebar + &.sidebar-expanded { + right: 306px; + left: 16px; + } - // without sidebar - &.affix.sidebar-collapsed { - right: 16px; - left: 16px; + // without sidebar + &.sidebar-collapsed { + right: 16px; + left: 16px; + } } &.affix-top { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 2a92673d9fa..82d9be29201 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -22,6 +22,11 @@ } } } + + svg { + width: 136px; + height: 136px; + } } .col-headers { @@ -155,11 +160,6 @@ } } - .landing svg { - width: 136px; - height: 136px; - } - .fa-spinner { font-size: 28px; position: relative; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 09f831dcb29..faa3d1fb4d5 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -380,6 +380,10 @@ } } } + + .line_content { + white-space: pre-wrap; + } } .file-content .diff-file { @@ -387,10 +391,6 @@ border: none; } -.diff-file .line_content { - white-space: pre-wrap; -} - .diff-wrap-lines .line_content { white-space: pre-wrap; } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 6c1d32bed2f..b5b0f3d9dfa 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -255,23 +255,6 @@ width: 100%; padding: 0; padding-bottom: 100%; -} - -.prometheus-svg-container > svg { - position: absolute; - height: 100%; - width: 100%; - left: 0; - top: 0; - - text { - fill: $gl-text-color; - stroke-width: 0; - } - - .text-metric-bold { - font-weight: $gl-font-weight-bold; - } .label-axis-text { fill: $black; @@ -286,42 +269,51 @@ font-size: 12px; } - .legend-axis-text { - fill: $black; - } + > svg { + position: absolute; + height: 100%; + width: 100%; + left: 0; + top: 0; - .tick { - > line { - stroke: $gray-darker; + .label-axis-text, + .text-metric-usage { + fill: $black; + font-weight: $gl-font-weight-normal; + font-size: 12px; } - > text { - font-size: 12px; + .legend-axis-text { + fill: $black; } - } - .text-metric-title { - font-size: 12px; - } + .tick > text { + font-size: 12px; + } - .y-label-text, - .x-label-text { - fill: $gray-darkest; - } + .text-metric-title { + font-size: 12px; + } - .axis-tick { - stroke: $gray-darker; - } + .y-label-text, + .x-label-text { + fill: $gray-darkest; + } - @media (max-width: $screen-sm-max) { - .label-axis-text, - .text-metric-usage, - .legend-axis-text { - font-size: 8px; + .axis-tick { + stroke: $gray-darker; } - .tick > text { - font-size: 8px; + @media (max-width: $screen-sm-max) { + .label-axis-text, + .text-metric-usage, + .legend-axis-text { + font-size: 8px; + } + + .tick > text { + font-size: 8px; + } } } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 88600a0e6d3..7059a4cfe85 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -127,7 +127,16 @@ } .right-sidebar { - a:not(.btn-retry), + position: absolute; + top: $header-height; + bottom: 0; + right: 0; + transition: width .3s; + background: $gray-light; + z-index: 200; + overflow: hidden; + + a, .btn-link { color: inherit; } @@ -228,17 +237,6 @@ .btn-clipboard:hover { color: $gl-text-color; } -} - -.right-sidebar { - position: absolute; - top: $header-height; - bottom: 0; - right: 0; - transition: width $right-sidebar-transition-duration; - background: $gray-light; - z-index: 200; - overflow: hidden; .issuable-sidebar { width: calc(100% + 100px); diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index cf5f933a762..92d49bd864a 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -109,6 +109,30 @@ border-top-right-radius: $border-radius-default; border-top-left-radius: $border-radius-default; + // Ldap configurations may need more tabs & the tab labels are user generated (arbitrarily long). + // These styles prevent this from breaking the layout, and only applied when providers are configured. + &.custom-provider-tabs { + flex-wrap: wrap; + + li { + min-width: 85px; + flex-basis: auto; + + // This styles tab elements that have wrapped to a second line. We cannot easily predict when this will happen. + // We are making somewhat of an assumption about the configuration here: that users do not have more than + // 3 LDAP servers configured (in addition to standard login) and they are not using especially long names for any + // of them. If either condition is false, this will work as expected. If both are true, there may be a missing border + // above one of the bottom row elements. If you know a better way, please implement it! + &:nth-child(n+5) { + border-top: 1px solid $border-color; + } + } + + a { + font-size: 16px; + } + } + li { flex: 1; text-align: center; @@ -154,32 +178,6 @@ } } - // Ldap configurations may need more tabs & the tab labels are user generated (arbitrarily long). - // These styles prevent this from breaking the layout, and only applied when providers are configured. - - .new-session-tabs.custom-provider-tabs { - flex-wrap: wrap; - - li { - min-width: 85px; - flex-basis: auto; - - // This styles tab elements that have wrapped to a second line. We cannot easily predict when this will happen. - // We are making somewhat of an assumption about the configuration here: that users do not have more than - // 3 LDAP servers configured (in addition to standard login) and they are not using especially long names for any - // of them. If either condition is false, this will work as expected. If both are true, there may be a missing border - // above one of the bottom row elements. If you know a better way, please implement it! - &:nth-child(n+5) { - border-top: 1px solid $border-color; - } - } - - a { - font-size: 16px; - } - } - - .form-control { &:active, &:focus { @@ -231,35 +229,35 @@ margin: 0; padding: 0; height: 100%; -} -// Fixes footer container to bottom of viewport -.devise-layout-html body { - // offset height of fixed header + 1 to avoid scroll - height: calc(100% - 51px); - margin: 0; - padding: 0; + // Fixes footer container to bottom of viewport + body { + // offset height of fixed header + 1 to avoid scroll + height: calc(100% - 51px); + margin: 0; + padding: 0; - .page-wrap { - min-height: 100%; - position: relative; - } + .page-wrap { + min-height: 100%; + position: relative; + } - .footer-container, - hr.footer-fixed { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 40px; - background: $white-light; - } + .footer-container, + hr.footer-fixed { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: $white-light; + } - .navless-container { - padding: 65px 15px; // height of footer + bottom padding of email confirmation link + .navless-container { + padding: 65px 15px; // height of footer + bottom padding of email confirmation link - @media (max-width: $screen-xs-max) { - padding: 0 15px 65px; + @media (max-width: $screen-xs-max) { + padding: 0 15px 65px; + } } } } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 692acf74a58..18c48405ecd 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -49,9 +49,17 @@ width: auto; } } + + &.existing-title { + @media (min-width: $screen-sm-min) { + float: left; + } + } } .member-form-control { + @include new-style-dropdown; + @media (max-width: $screen-xs-max) { padding-bottom: 5px; margin-left: 0; @@ -64,12 +72,6 @@ line-height: 43px; } -.member.existing-title { - @media (min-width: $screen-sm-min) { - float: left; - } -} - .member-search-form { @include new-style-dropdown; @@ -281,7 +283,3 @@ } } } - -.member-form-control { - @include new-style-dropdown; -} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 645fc1f3ebb..6e485ebad1b 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -156,6 +156,10 @@ &.media > *:first-child { margin-right: 10px; } + + .approve-btn { + margin-right: 5px; + } } .mr-widget-pipeline-graph { @@ -191,6 +195,10 @@ overflow: hidden; word-break: break-all; + &.media > *:first-child { + margin-right: 10px; + } + &.label-truncated { position: relative; display: inline-block; @@ -208,14 +216,7 @@ background-color: $gray-light; } } - } - .mr-widget-help { - padding: 10px 16px 10px 48px; - font-style: italic; - } - - .mr-widget-body { h4 { float: left; font-weight: $gl-font-weight-bold; @@ -238,6 +239,10 @@ margin-right: 7px; } + .approve-btn { + margin-right: 5px; + } + label { font-weight: $gl-font-weight-normal; } @@ -337,6 +342,22 @@ } } + .mini-pipeline-graph-dropdown-menu .mini-pipeline-graph-dropdown-item { + display: flex; + align-items: center; + + .ci-status-text, + .ci-status-icon { + top: 0; + margin-right: 10px; + } + } + + .mr-widget-help { + padding: 10px 16px 10px 48px; + font-style: italic; + } + .ci-coverage { float: right; } @@ -351,12 +372,6 @@ } } -.mr-state-widget .mr-widget-body { - .approve-btn { - margin-right: 5px; - } -} - .mr-widget-body-controls { flex-wrap: wrap; } @@ -470,16 +485,16 @@ padding-bottom: 0; } } -} -.mr-info-list.mr-memory-usage { - p { - float: left; - } + &.mr-memory-usage { + p { + float: left; + } - .memory-graph-container { - float: left; - margin-left: 5px; + .memory-graph-container { + float: left; + margin-left: 5px; + } } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 32039936be7..ae8fa45a2d7 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -66,6 +66,15 @@ height: 6px; margin: 0; } + + .sidebar-collapsed-icon { + clear: both; + padding: 15px 5px 5px; + + .progress { + margin: 5px 0; + } + } } .collapsed-milestone-date { @@ -93,17 +102,6 @@ margin-right: 0; } - .milestone-progress { - .sidebar-collapsed-icon { - clear: both; - padding: 15px 5px 5px; - - .progress { - margin: 5px 0; - } - } - } - .right-sidebar-collapsed & { .reference { border-top: 1px solid $border-gray-normal; @@ -156,18 +154,16 @@ .status-box { margin-top: 0; - } - - .milestone-buttons { - margin-left: auto; - } - - .status-box { order: 1; } .milestone-buttons { + margin-left: auto; order: 2; + + .verbose { + display: none; + } } .header-text-content { @@ -175,10 +171,6 @@ width: 100%; } - .milestone-buttons .verbose { - display: none; - } - @media (min-width: $screen-xs-min) { .milestone-buttons .verbose { display: inline; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index f0cad30f4f3..5127307c5e7 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -111,24 +111,9 @@ margin: auto; align-items: center; - .icon { - margin-right: $issuable-warning-icon-margin; - } -} - -.disabled-comment .issuable-note-warning { - border: none; - border-radius: $label-border-radius; - padding-top: $gl-vert-padding; - padding-bottom: $gl-vert-padding; - - .icon svg { - position: relative; - top: 2px; - margin-right: $btn-xs-side-margin; - width: $gl-font-size; - height: $gl-font-size; - fill: $orange-600; + + .md-area { + border-top-left-radius: 0; + border-top-right-radius: 0; } } @@ -155,11 +140,6 @@ } } -.issuable-note-warning + .md-area { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - .discussion-form { background-color: $white-light; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 312917bd13a..0918dc56dcc 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -312,57 +312,72 @@ ul.notes { } } -.diff-file .notes_holder { - font-family: $regular_font; +.diff-file { + .is-over { + .add-diff-note { + display: inline-block; + } + } - td { - border: 1px solid $white-normal; - border-left: none; + // Merge request notes in diffs + // Diff is inline + .notes_content .note-header .note-headline-light { + display: inline-block; + position: relative; + } - &.notes_line { - vertical-align: middle; - text-align: center; - padding: 10px 0; - background: $gray-light; - color: $text-color; - } + .notes_holder { + font-family: $regular_font; - &.notes_line2 { - text-align: center; - padding: 10px 0; - border-left: 1px solid $note-line2-border !important; - } + td { + border: 1px solid $white-normal; + border-left: none; - &.notes_content { - background-color: $gray-light; - border-width: 1px 0; - padding: 0; - vertical-align: top; - white-space: normal; + &.notes_line { + vertical-align: middle; + text-align: center; + padding: 10px 0; + background: $gray-light; + color: $text-color; + } - &.parallel { - border-width: 1px; + &.notes_line2 { + text-align: center; + padding: 10px 0; + border-left: 1px solid $note-line2-border !important; } - .discussion-notes { - &:not(:first-child) { - border-top: 1px solid $white-normal; - margin-top: 20px; + &.notes_content { + background-color: $gray-light; + border-width: 1px 0; + padding: 0; + vertical-align: top; + white-space: normal; + + &.parallel { + border-width: 1px; } - &:not(:last-child) { - border-bottom: 1px solid $white-normal; - margin-bottom: 20px; + .discussion-notes { + &:not(:first-child) { + border-top: 1px solid $white-normal; + margin-top: 20px; + } + + &:not(:last-child) { + border-bottom: 1px solid $white-normal; + margin-bottom: 20px; + } } - } - .notes { - background-color: $white-light; - } + .notes { + background-color: $white-light; + } - a code { - top: 0; - margin-right: 0; + a code { + top: 0; + margin-right: 0; + } } } } @@ -457,8 +472,9 @@ ul.notes { margin-left: 10px; color: $gray-darkest; - .btn-group > .discussion-next-btn { - margin-left: -1px; + @include notes-media('max', $screen-md-max) { + float: none; + margin-left: 0; } } @@ -502,13 +518,6 @@ ul.notes { min-width: 180px; } -.discussion-actions { - @include notes-media('max', $screen-md-max) { - float: none; - margin-left: 0; - } -} - .note-actions-item { margin-left: 12px; display: flex; @@ -665,14 +674,6 @@ ul.notes { } } -.diff-file { - .is-over { - .add-diff-note { - display: inline-block; - } - } -} - .disabled-comment { background-color: $gray-light; border-radius: $border-radius-base; @@ -714,20 +715,20 @@ ul.notes { svg path { fill: $gray-darkest; } - } - .btn.discussion-create-issue-btn { - margin-left: -4px; - border-radius: 0; - border-right: 0; + &.discussion-create-issue-btn { + margin-left: -4px; + border-radius: 0; + border-right: 0; - a { - padding: 0; - line-height: 0; + a { + padding: 0; + line-height: 0; - &:hover { - text-decoration: none; - border: 0; + &:hover { + text-decoration: none; + border: 0; + } } } } @@ -801,12 +802,3 @@ ul.notes { .line-resolve-text { vertical-align: middle; } - -// Merge request notes in diffs -.diff-file { - // Diff is inline - .notes_content .note-header .note-headline-light { - display: inline-block; - position: relative; - } -} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 6604b471560..2a8cbc61af7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -175,6 +175,25 @@ } } + /** + * Play button with icon in dropdowns + */ + .no-btn { + border: none; + background: none; + outline: none; + width: 100%; + text-align: left; + + .icon-play { + position: relative; + top: 2px; + margin-right: 5px; + height: 13px; + width: 12px; + } + } + .duration, .finished-at { color: $gl-text-color-secondary; @@ -450,48 +469,48 @@ @extend .build-content:hover; } - // Action Icons in big pipeline-graph nodes - .ci-action-icon-container.ci-action-icon-wrapper { - height: 30px; - width: 30px; - background: $white-light; - border: 1px solid $border-color; - border-radius: 100%; - display: block; + .ci-action-icon-container { + position: absolute; + right: 5px; + top: 5px; - &:hover { - background-color: $stage-hover-bg; - border: 1px solid $dropdown-toggle-active-border-color; - } + // Action Icons in big pipeline-graph nodes + &.ci-action-icon-wrapper { + height: 30px; + width: 30px; + background: $white-light; + border: 1px solid $border-color; + border-radius: 100%; + display: block; - svg { - fill: $gl-text-color-secondary; - position: relative; - left: 5px; - top: 2px; - width: 18px; - height: 18px; - } + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $dropdown-toggle-active-border-color; + + svg { + fill: $gl-text-color; + } + } - &.play { svg { - width: #{$ci-action-icon-size - 8}; - height: #{$ci-action-icon-size - 8}; - left: 8px; + fill: $gl-text-color-secondary; + position: relative; + left: 5px; + top: 2px; + width: 18px; + height: 18px; } - } - &:hover svg { - fill: $gl-text-color; + &.play { + svg { + width: #{$ci-action-icon-size - 8}; + height: #{$ci-action-icon-size - 8}; + left: 8px; + } + } } } - .ci-action-icon-container { - position: absolute; - right: 5px; - top: 5px; - } - .ci-status-icon svg { height: 20px; width: 20px; @@ -735,6 +754,28 @@ button.mini-pipeline-graph-dropdown-toggle { left: -3px; position: relative; top: -2px; + + &.icon-action-stop, + &.icon-action-cancel { + width: 12px; + height: 12px; + top: 1px; + left: -1px; + } + + &.icon-action-play { + width: 11px; + height: 11px; + top: 1px; + left: 1px; + } + + &.icon-action-retry { + width: 16px; + height: 16px; + top: 0; + left: -3px; + } } &:hover svg, @@ -751,27 +792,6 @@ button.mini-pipeline-graph-dropdown-toggle { } } - svg.icon-action-stop, - svg.icon-action-cancel { - width: 12px; - height: 12px; - top: 1px; - left: -1px; - } - - svg.icon-action-play { - width: 11px; - height: 11px; - top: 1px; - left: 1px; - } - - svg.icon-action-retry { - width: 16px; - height: 16px; - top: 0; - left: -3px; - } } @@ -840,13 +860,10 @@ button.mini-pipeline-graph-dropdown-toggle { left: 100%; top: -10px; box-shadow: 0 1px 5px $black-transparent; -} - -/** - * Top arrow in the dropdown in the big pipeline graph - */ -.big-pipeline-graph-dropdown-menu { + /** + * Top arrow in the dropdown in the big pipeline graph + */ &::before, &::after { content: ''; @@ -908,22 +925,23 @@ button.mini-pipeline-graph-dropdown-toggle { margin-top: 1px; border-bottom-color: $white-light; } -} -/** - * Center dropdown menu in mini graph - */ -.mini-pipeline-graph-dropdown-menu.dropdown-menu { - transform: translate(-80%, 0); - min-width: 150px; + /** + * Center dropdown menu in mini graph + */ + &.dropdown-menu { + transform: translate(-80%, 0); + min-width: 150px; - @media(min-width: $screen-md-min) { - transform: translate(-50%, 0); - right: auto; - left: 50%; - min-width: 240px; + @media(min-width: $screen-md-min) { + transform: translate(-50%, 0); + right: auto; + left: 50%; + min-width: 240px; + } } } + /** * Terminal */ @@ -947,25 +965,6 @@ button.mini-pipeline-graph-dropdown-toggle { } } -/** - * Play button with icon in dropdowns - */ -.ci-table .no-btn { - border: none; - background: none; - outline: none; - width: 100%; - text-align: left; - - .icon-play { - position: relative; - top: 2px; - margin-right: 5px; - height: 13px; - width: 12px; - } -} - .ci-header-container { min-height: 55px; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index bd385db9692..b0c3474e3d5 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -88,7 +88,8 @@ transition: background 2s ease-out; &:disabled { - opacity: 0.75; + opacity: 0.5; + pointer-events: none; } .highlight-changes & { @@ -778,35 +779,35 @@ a.deploy-project-label { .nav { padding-top: 12px; padding-bottom: 12px; - } - .nav > li { - display: inline-block; + > li { + display: inline-block; - &:not(:last-child) { - margin-right: $gl-padding; - } + &:not(:last-child) { + margin-right: $gl-padding; + } - &.right { - vertical-align: top; - margin-top: 0; + &.right { + vertical-align: top; + margin-top: 0; - @media (min-width: $screen-lg-min) { - float: right; + @media (min-width: $screen-lg-min) { + float: right; + } } - } - } - .nav > li > a { - padding: 0; - background-color: transparent; - font-size: 14px; - line-height: 29px; - color: $notes-light-color; + > a { + padding: 0; + background-color: transparent; + font-size: 14px; + line-height: 29px; + color: $notes-light-color; - &:hover, - &:focus { - color: $gl-text-color; + &:hover, + &:focus { + color: $gl-text-color; + } + } } } @@ -1160,13 +1161,6 @@ pre.light-well { } } -.project-repo-select { - &.disabled { - opacity: 0.5; - pointer-events: none; - } -} - .variables-table { table-layout: fixed; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index db0a04a5eb3..eed711b1b66 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -78,6 +78,10 @@ input[type="checkbox"]:hover { } .search-input-wrap { + // Fallback if flexbox is not supported + display: inline-block; + width: 100%; + .search-icon, .clear-icon { position: absolute; diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 968a94c68cf..8b9b47a41bc 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -241,11 +241,11 @@ margin-left: 5px; background: $badge-bg; } - } - /* Ensure we don't add border if there's only single li */ - li + li { - border-top: 1px solid $border-color; + /* Ensure we don't add border if there's only single li */ + + li { + border-top: 1px solid $border-color; + } } } } diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss index bfe065dbbaf..2bf0bedb1f5 100644 --- a/app/assets/stylesheets/pages/sherlock.scss +++ b/app/assets/stylesheets/pages/sherlock.scss @@ -5,10 +5,10 @@ table .sherlock-code { .sherlock-code { pre { word-wrap: normal; - } - pre code { - white-space: pre; + code { + white-space: pre; + } } } @@ -21,13 +21,13 @@ table .sherlock-code { text-align: right; padding: 0 10px !important; } + + .slow { + color: $red-500; + font-weight: $gl-font-weight-bold; + } } .sherlock-file-sample pre { padding-top: 28px !important; } - -.sherlock-line-samples-table .slow { - color: $red-500; - font-weight: $gl-font-weight-bold; -} diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index dfa4d033fb8..cede147d559 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -40,16 +40,16 @@ @media (max-width: $screen-xs-max) { width: 100%; } - } - .person .spark { - display: block; - background: $stat-graph-common-bg; - width: 100%; - } + .spark { + display: block; + background: $stat-graph-common-bg; + width: 100%; + } - .person .area-contributor { - fill: $stat-graph-orange-fill; + .area-contributor { + fill: $stat-graph-orange-fill; + } } } diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index b7d4e7bf582..e150f96f3fa 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -161,10 +161,10 @@ ul.wiki-pages-list.content-list { list-style: none; margin-left: 0; padding-left: 15px; - } - ul li { - padding: 5px 0; + li { + padding: 5px 0; + } } } diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb index fb6d8c0bb81..5be23c76a95 100644 --- a/app/controllers/admin/applications_controller.rb +++ b/app/controllers/admin/applications_controller.rb @@ -19,10 +19,12 @@ class Admin::ApplicationsController < Admin::ApplicationController end def create - @application = Doorkeeper::Application.new(application_params) + @application = Applications::CreateService.new(current_user, application_params).execute(request) - if @application.save - redirect_to_admin_page + if @application.persisted? + flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) + + redirect_to admin_application_url(@application) else render :new end @@ -41,13 +43,6 @@ class Admin::ApplicationsController < Admin::ApplicationController redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.' end - protected - - def redirect_to_admin_page - flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) - redirect_to admin_application_url(@application) - end - private def set_application diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 2b6afaa6233..738afd612f0 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -94,10 +94,9 @@ module LfsRequest @storage_project ||= begin result = project - loop do - break unless result.forked? - result = result.forked_from_project - end + # TODO: Make this go to the fork_network root immeadiatly + # dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-ce/issues/39769 + result = result.fork_source while result.forked? result end diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index b02e64a132b..2443f529c7b 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -16,25 +16,18 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController end def create - @application = Doorkeeper::Application.new(application_params) + @application = Applications::CreateService.new(current_user, create_application_params).execute(request) - @application.owner = current_user + if @application.persisted? + flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) - if @application.save - redirect_to_oauth_application_page + redirect_to oauth_application_url(@application) else set_index_vars render :index end end - protected - - def redirect_to_oauth_application_page - flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) - redirect_to oauth_application_url(@application) - end - private def verify_user_oauth_applications_enabled @@ -61,4 +54,10 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController rescue_from ActiveRecord::RecordNotFound do |exception| render "errors/not_found", layout: "errors", status: 404 end + + def create_application_params + application_params.tap do |params| + params[:owner] = current_user + end + end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 069e6a810f2..f0e5d2aa94e 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -11,10 +11,10 @@ class Profiles::KeysController < Profiles::ApplicationController end def create - @key = Keys::CreateService.new(current_user, key_params).execute + @key = Keys::CreateService.new(current_user, key_params.merge(ip_address: request.remote_ip)).execute if @key.persisted? - redirect_to_profile_key_path + redirect_to profile_key_path(@key) else @keys = current_user.keys.select(&:persisted?) render :index @@ -50,12 +50,6 @@ class Profiles::KeysController < Profiles::ApplicationController end end - protected - - def redirect_to_profile_key_path - redirect_to profile_key_path(@key) - end - private def key_params diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d085c1a0e57..f48d47953e4 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -110,7 +110,15 @@ module ProjectsHelper def remove_fork_project_message(project) _("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") % - { forked_from_project: @project.forked_from_project.name_with_namespace } + { forked_from_project: fork_source_name(project) } + end + + def fork_source_name(project) + if @project.fork_source + @project.fork_source.full_name + else + @project.fork_network&.deleted_root_project_name + end end def project_nav_tabs @@ -140,8 +148,8 @@ module ProjectsHelper def can_change_visibility_level?(project, current_user) return false unless can?(current_user, :change_visibility_level, project) - if project.forked? - project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE + if project.fork_source + project.fork_source.visibility_level > Gitlab::VisibilityLevel::PRIVATE else true end diff --git a/app/models/environment.rb b/app/models/environment.rb index e613d21add6..8d6b0a32c13 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -110,7 +110,7 @@ class Environment < ActiveRecord::Base end def ref_path - "refs/#{Repository::REF_ENVIRONMENTS}/#{generate_slug}" + "refs/#{Repository::REF_ENVIRONMENTS}/#{slug}" end def formatted_external_url @@ -164,6 +164,10 @@ class Environment < ActiveRecord::Base end end + def slug + super.presence || generate_slug + end + # An environment name is not necessarily suitable for use in URLs, DNS # or other third-party contexts, so provide a slugified version. A slug has # the following properties: diff --git a/app/models/epic.rb b/app/models/epic.rb index 0d5f21fb617..62898a02e2d 100644 --- a/app/models/epic.rb +++ b/app/models/epic.rb @@ -1,8 +1,6 @@ # Placeholder class for model that is implemented in EE # It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE class Epic < ActiveRecord::Base - prepend EE::Epic - # TODO: this will be implemented as part of #3853 def to_reference end diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb index 218e37a5312..7f1728e8c77 100644 --- a/app/models/fork_network.rb +++ b/app/models/fork_network.rb @@ -12,4 +12,8 @@ class ForkNetwork < ActiveRecord::Base def find_forks_in(other_projects) projects.where(id: other_projects) end + + def merge_requests + MergeRequest.where(target_project: projects) + end end diff --git a/app/models/group.rb b/app/models/group.rb index 4e8023cdb7f..c660de7fcb6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -42,6 +42,7 @@ class Group < Namespace after_create :post_create_hook after_destroy :post_destroy_hook after_save :update_two_factor_requirement + after_update :path_changed_hook, if: :path_changed? class << self def supports_nested_groups? @@ -295,6 +296,12 @@ class Group < Namespace list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten end + def full_path_was + return path_was unless has_parent? + + "#{parent.full_path}/#{path_was}" + end + private def update_two_factor_requirement @@ -303,6 +310,10 @@ class Group < Namespace users.find_each(&:update_two_factor_requirement) end + def path_changed_hook + system_hook_service.execute_hooks_for(self, :rename) + end + def visibility_level_allowed_by_parent return if visibility_level_allowed_by_parent? diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index faf0b95f842..1eda0f9cbbd 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base # Collect information about commits and diff from repository # and save it to the database as serialized data def save_git_content + MergeRequest + .where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id) + .update_all(latest_merge_request_diff_id: self.id) + ensure_commit_shas save_commits save_diffs diff --git a/app/models/project.rb b/app/models/project.rb index 413866b994a..2f9b80d0514 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1040,6 +1040,10 @@ class Project < ActiveRecord::Base !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) end + def fork_source + forked_from_project || fork_network&.root_project + end + def personal? !group end diff --git a/app/models/user.rb b/app/models/user.rb index 6c9349ed9dd..bcda4564595 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -168,6 +168,7 @@ class User < ActiveRecord::Base before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) } before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? } after_save :ensure_namespace_correct + after_update :username_changed_hook, if: :username_changed? after_destroy :post_destroy_hook after_commit :update_emails_with_primary_email, on: :update, if: -> { previous_changes.key?('email') } after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') } @@ -871,6 +872,10 @@ class User < ActiveRecord::Base end end + def username_changed_hook + system_hook_service.execute_hooks_for(self, :rename) + end + def post_destroy_hook log_info("User \"#{name}\" (#{email}) was removed") system_hook_service.execute_hooks_for(self, :destroy) diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb new file mode 100644 index 00000000000..35d45f25a71 --- /dev/null +++ b/app/services/applications/create_service.rb @@ -0,0 +1,13 @@ +module Applications + class CreateService + def initialize(current_user, params) + @current_user = current_user + @params = params + @ip_address = @params.delete(:ip_address) + end + + def execute(request = nil) + Doorkeeper::Application.create(@params) + end + end +end diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb index 545832d0bd4..f78791932a7 100644 --- a/app/services/keys/base_service.rb +++ b/app/services/keys/base_service.rb @@ -4,6 +4,7 @@ module Keys def initialize(user, params) @user, @params = user, params + @ip_address = @params.delete(:ip_address) end def notification_service diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb index a02eee4961b..6b3939aeba5 100644 --- a/app/services/metrics_service.rb +++ b/app/services/metrics_service.rb @@ -6,8 +6,7 @@ class MetricsService Gitlab::HealthChecks::Redis::RedisCheck, Gitlab::HealthChecks::Redis::CacheCheck, Gitlab::HealthChecks::Redis::QueuesCheck, - Gitlab::HealthChecks::Redis::SharedStateCheck, - Gitlab::HealthChecks::FsShardsCheck + Gitlab::HealthChecks::Redis::SharedStateCheck ].freeze def prometheus_metrics_text diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index 2b82e5732e4..c499f384426 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -3,18 +3,24 @@ module Projects def execute return unless @project.forked? - @project.forked_from_project.lfs_objects.find_each do |lfs_object| - lfs_object.projects << @project + if fork_source = @project.fork_source + fork_source.lfs_objects.find_each do |lfs_object| + lfs_object.projects << @project + end + + refresh_forks_count(fork_source) end - merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project) + merge_requests = @project.fork_network + .merge_requests + .opened + .where.not(target_project: @project) + .from_project(@project) merge_requests.each do |mr| ::MergeRequests::CloseService.new(@project, @current_user).execute(mr) end - refresh_forks_count(@project.forked_from_project) - @project.fork_network_member.destroy @project.forked_project_link.destroy end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 5d275967821..911cc919bb8 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -35,24 +35,22 @@ class SystemHooksService data[:old_path_with_namespace] = model.old_path_with_namespace end when User - data.merge!({ - name: model.name, - email: model.email, - user_id: model.id, - username: model.username - }) + data.merge!(user_data(model)) + + if event == :rename + data[:old_username] = model.username_was + end when ProjectMember data.merge!(project_member_data(model)) when Group - owner = model.owner + data.merge!(group_data(model)) - data.merge!( - name: model.name, - path: model.path, - group_id: model.id, - owner_name: owner.respond_to?(:name) ? owner.name : nil, - owner_email: owner.respond_to?(:email) ? owner.email : nil - ) + if event == :rename + data.merge!( + old_path: model.path_was, + old_full_path: model.full_path_was + ) + end when GroupMember data.merge!(group_member_data(model)) end @@ -104,6 +102,19 @@ class SystemHooksService } end + def group_data(model) + owner = model.owner + + { + name: model.name, + path: model.path, + full_path: model.full_path, + group_id: model.id, + owner_name: owner.try(:name), + owner_email: owner.try(:email) + } + end + def group_member_data(model) { group_name: model.group.name, @@ -116,4 +127,13 @@ class SystemHooksService group_access: model.human_access } end + + def user_data(model) + { + name: model.name, + email: model.email, + user_id: model.id, + username: model.username + } + end end diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f62a0cd681e..a5686002328 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -8,7 +8,7 @@ %li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }> = link_to todos_filter_path(state: 'pending') do %span - To do + Todos %span.badge = number_with_delimiter(todos_pending_count) %li.todos-done{ class: active_when(params[:state] == 'done') }> diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 619b632918e..1d644dda177 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,6 +1,5 @@ - empty_repo = @project.empty_repo? - fork_network = @project.fork_network -- forked_from_project = @project.forked_from_project || fork_network&.root_project .project-home-panel.text-center{ class: ("empty-project" if empty_repo) } .limit-container-width{ class: container_class } .avatar-container.s70.project-avatar @@ -16,13 +15,13 @@ - if @project.forked? %p - - if forked_from_project + - if @project.fork_source #{ s_('ForkedFromProjectPath|Forked from') } - = link_to project_path(forked_from_project) do - = forked_from_project.full_name + = link_to project_path(@project.fork_source) do + = fork_source_name(@project) - else - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)') - = deleted_message % { project_name: fork_network.deleted_root_project_name } + = deleted_message % { project_name: fork_source_name(@project) } .project-repo-buttons .count-buttons diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 5703ef1d4bb..5ebeae5c35f 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -173,7 +173,10 @@ %p This will remove the fork relationship to source project = succeed "." do - = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) + - if @project.fork_source + = link_to(fork_source_name(@project), project_path(@project.fork_source)) + - else + = fork_source_name(@project) = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f| %p %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source. diff --git a/changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml b/changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml new file mode 100644 index 00000000000..a7127f49c16 --- /dev/null +++ b/changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml @@ -0,0 +1,5 @@ +--- +title: Add a latest_merge_request_diff_id column to merge_requests +merge_request: 15035 +author: +type: performance diff --git a/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml deleted file mode 100644 index 47bf30ecb5a..00000000000 --- a/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Stop merge requests with thousands of commits from timing out -merge_request: 15063 -author: -type: performance diff --git a/changelogs/unreleased/39188-change-default-disabled-merge-message.yml b/changelogs/unreleased/39188-change-default-disabled-merge-message.yml deleted file mode 100644 index 7de65f5c3f6..00000000000 --- a/changelogs/unreleased/39188-change-default-disabled-merge-message.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update default disabled merge request widget message to reflect a general failure -merge_request: 14960 -author: -type: changed diff --git a/changelogs/unreleased/39366-email-confirmation-fails.yml b/changelogs/unreleased/39366-email-confirmation-fails.yml deleted file mode 100644 index a5568670c70..00000000000 --- a/changelogs/unreleased/39366-email-confirmation-fails.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Fix bug preventing secondary emails from being confirmed' -merge_request: 15010 -author: -type: fixed diff --git a/changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml b/changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml new file mode 100644 index 00000000000..edf142f0311 --- /dev/null +++ b/changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml @@ -0,0 +1,5 @@ +--- +title: Todos spelled correctly on Todos list page +merge_request: 15015 +author: +type: changed diff --git a/changelogs/unreleased/39441-bring-edit-form-back.yml b/changelogs/unreleased/39441-bring-edit-form-back.yml deleted file mode 100644 index 025417e4da9..00000000000 --- a/changelogs/unreleased/39441-bring-edit-form-back.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix editing issue description in mobile view -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/39495-fix-bitbucket-login.yml b/changelogs/unreleased/39495-fix-bitbucket-login.yml deleted file mode 100644 index b48d557108b..00000000000 --- a/changelogs/unreleased/39495-fix-bitbucket-login.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix bitbucket login -merge_request: 15051 -author: -type: fixed diff --git a/changelogs/unreleased/39639-clusters-poll.yml b/changelogs/unreleased/39639-clusters-poll.yml deleted file mode 100644 index f0a82f58b19..00000000000 --- a/changelogs/unreleased/39639-clusters-poll.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds callback functions for initial request in clusters page -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/bvl-circuitbreaker-backoff.yml b/changelogs/unreleased/bvl-circuitbreaker-backoff.yml deleted file mode 100644 index 5cb90e7c085..00000000000 --- a/changelogs/unreleased/bvl-circuitbreaker-backoff.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Make the circuitbreaker more robust by adding higher thresholds, and multiple - access attempts. -merge_request: 14933 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-circuitbreaker-improvements.yml b/changelogs/unreleased/bvl-circuitbreaker-improvements.yml deleted file mode 100644 index 15cbd5592e9..00000000000 --- a/changelogs/unreleased/bvl-circuitbreaker-improvements.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Store circuitbreaker settings in the database instead of config -merge_request: 14842 -author: -type: changed diff --git a/changelogs/unreleased/bvl-do-not-use-redis-keys.yml b/changelogs/unreleased/bvl-do-not-use-redis-keys.yml deleted file mode 100644 index f703aad2065..00000000000 --- a/changelogs/unreleased/bvl-do-not-use-redis-keys.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Forbid the usage of `Redis#keys` -merge_request: 14889 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-dont-rename-free-names.yml b/changelogs/unreleased/bvl-dont-rename-free-names.yml deleted file mode 100644 index 60a4ec8afbe..00000000000 --- a/changelogs/unreleased/bvl-dont-rename-free-names.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Don't rename paths that were freed up when upgrading -merge_request: 15029 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml b/changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml deleted file mode 100644 index 2a7d80270ac..00000000000 --- a/changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Only cache last push event for existing projects when pushing to a fork -merge_request: 14989 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml b/changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml deleted file mode 100644 index a17ed51c9b8..00000000000 --- a/changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use the correct visibility attribute for projects in system hooks -merge_request: 15065 -author: -type: fixed diff --git a/changelogs/unreleased/bvl-unlink-fixes.yml b/changelogs/unreleased/bvl-unlink-fixes.yml new file mode 100644 index 00000000000..685d78f479d --- /dev/null +++ b/changelogs/unreleased/bvl-unlink-fixes.yml @@ -0,0 +1,5 @@ +--- +title: Fix issues with forked projects of which the source was deleted +merge_request: 15150 +author: +type: fixed diff --git a/changelogs/unreleased/dm-ldap-identity-normalize-dn.yml b/changelogs/unreleased/dm-ldap-identity-normalize-dn.yml deleted file mode 100644 index 7ab25f79143..00000000000 --- a/changelogs/unreleased/dm-ldap-identity-normalize-dn.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Normalize LDAP DN when looking up identity -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/enable-scss-lint-mergeable-selector.yml b/changelogs/unreleased/enable-scss-lint-mergeable-selector.yml new file mode 100644 index 00000000000..5f6e0cafe88 --- /dev/null +++ b/changelogs/unreleased/enable-scss-lint-mergeable-selector.yml @@ -0,0 +1,4 @@ +--- +title: Enable MergeableSelector in scss-lint +merge_request: 12810 +author: Takuya Noguchi diff --git a/changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml b/changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml deleted file mode 100644 index 0847b5f6733..00000000000 --- a/changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix broken wiki pages that link to a wiki file -merge_request: 15019 -author: -type: fixed diff --git a/changelogs/unreleased/fix-import-issue-assignees.yml b/changelogs/unreleased/fix-import-issue-assignees.yml deleted file mode 100644 index 063b6afaf08..00000000000 --- a/changelogs/unreleased/fix-import-issue-assignees.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix missing Import/Export issue assignees -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/fix_global_board_routes_39073.yml b/changelogs/unreleased/fix_global_board_routes_39073.yml deleted file mode 100644 index cc9ae8592db..00000000000 --- a/changelogs/unreleased/fix_global_board_routes_39073.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow boards as top level route -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/make-merge-jid-handling-less-stateful.yml b/changelogs/unreleased/make-merge-jid-handling-less-stateful.yml deleted file mode 100644 index fe945e822fd..00000000000 --- a/changelogs/unreleased/make-merge-jid-handling-less-stateful.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix widget of locked merge requests not being presented -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/mr-14642.yml b/changelogs/unreleased/mr-14642.yml deleted file mode 100644 index 048cc79e323..00000000000 --- a/changelogs/unreleased/mr-14642.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Auto Devops kubernetes default namespace is now correctly built out of gitlab - project group-name -merge_request: 14642 -author: Mircea Danila Dumitrescu -type: fixed diff --git a/changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml b/changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml new file mode 100644 index 00000000000..556d7d069d3 --- /dev/null +++ b/changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml @@ -0,0 +1,5 @@ +--- +title: Remove Filesystem check metrics that use too much CPU to handle requests +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/sh-fix-container-registry-destroy.yml b/changelogs/unreleased/sh-fix-container-registry-destroy.yml deleted file mode 100644 index 21a463da62a..00000000000 --- a/changelogs/unreleased/sh-fix-container-registry-destroy.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix deletion of container registry or images returning an error -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-environment-slug-generation.yml b/changelogs/unreleased/sh-fix-environment-slug-generation.yml new file mode 100644 index 00000000000..8a9c670c52c --- /dev/null +++ b/changelogs/unreleased/sh-fix-environment-slug-generation.yml @@ -0,0 +1,5 @@ +--- +title: Avoid regenerating the ref path for the environment +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-environment-write-ref.yml b/changelogs/unreleased/sh-fix-environment-write-ref.yml deleted file mode 100644 index 8f291843ebe..00000000000 --- a/changelogs/unreleased/sh-fix-environment-write-ref.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix the writing of invalid environment refs -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/winh-namespace-rename-hooks.yml b/changelogs/unreleased/winh-namespace-rename-hooks.yml new file mode 100644 index 00000000000..f5090b03b74 --- /dev/null +++ b/changelogs/unreleased/winh-namespace-rename-hooks.yml @@ -0,0 +1,5 @@ +--- +title: Add system hooks user_rename and group_rename +merge_request: 15123 +author: +type: changed diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 4bfa5be0136..7547ba4a8fa 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -501,7 +501,7 @@ production: &base # Gitaly settings gitaly: # Path to the directory containing Gitaly client executables. - client_path: /home/git/gitaly + client_path: /home/git/gitaly/bin # Default Gitaly authentication token. Can be overriden per storage. Can # be left blank when Gitaly is running locally on a Unix socket, which # is the normal way to deploy Gitaly. diff --git a/db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb b/db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb new file mode 100644 index 00000000000..74a2badc130 --- /dev/null +++ b/db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb @@ -0,0 +1,26 @@ +class AddLatestMergeRequestDiffIdToMergeRequests < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column :merge_requests, :latest_merge_request_diff_id, :integer + add_concurrent_index :merge_requests, :latest_merge_request_diff_id + + add_concurrent_foreign_key :merge_requests, :merge_request_diffs, + column: :latest_merge_request_diff_id, + on_delete: :nullify + end + + def down + remove_foreign_key :merge_requests, column: :latest_merge_request_diff_id + + if index_exists?(:merge_requests, :latest_merge_request_diff_id) + remove_concurrent_index :merge_requests, :latest_merge_request_diff_id + end + + remove_column :merge_requests, :latest_merge_request_diff_id + end +end diff --git a/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb b/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb new file mode 100644 index 00000000000..a7ebbbf34c0 --- /dev/null +++ b/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb @@ -0,0 +1,27 @@ +class PopulateMergeRequestsLatestMergeRequestDiffId < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 1_000 + + class MergeRequest < ActiveRecord::Base + self.table_name = 'merge_requests' + + include ::EachBatch + end + + disable_ddl_transaction! + + def up + update = ' + latest_merge_request_diff_id = ( + SELECT MAX(id) + FROM merge_request_diffs + WHERE merge_requests.id = merge_request_diffs.merge_request_id + )'.squish + + MergeRequest.where(latest_merge_request_diff_id: nil).each_batch(of: BATCH_SIZE) do |relation| + relation.update_all(update) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index be381751238..80d8ff92d6e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171017145932) do +ActiveRecord::Schema.define(version: 20171026082505) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -973,6 +973,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do t.boolean "ref_fetched" t.string "merge_jid" t.boolean "discussion_locked" + t.integer "latest_merge_request_diff_id" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -981,6 +982,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree + add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", 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_id_and_source_branch", using: :btree @@ -1844,6 +1846,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify + add_foreign_key "merge_requests", "merge_request_diffs", column: "latest_merge_request_diff_id", name: "fk_06067f5644", on_delete: :nullify add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index a45a4eb9e49..f2a9b1d769b 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -1,6 +1,24 @@ # System hooks -Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `project_update`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. +Your GitLab instance can perform HTTP POST requests on the following events: + +- `project_create` +- `project_destroy` +- `project_rename` +- `project_transfer` +- `project_update` +- `user_add_to_team` +- `user_remove_from_team` +- `user_create` +- `user_destroy` +- `user_rename` +- `key_create` +- `key_destroy` +- `group_create` +- `group_destroy` +- `group_rename` +- `user_add_to_group` +- `user_remove_from_group` The triggers for most of these are self-explanatory, but `project_update` and `project_rename` deserve some clarification: `project_update` is fired any time an attribute of a project is changed (name, description, tags, etc.) *unless* the `path` attribute is also changed. In that case, a `project_rename` is triggered instead (so that, for instance, if all you care about is the repo URL, you can just listen for `project_rename`). @@ -72,6 +90,9 @@ X-Gitlab-Event: System Hook } ``` +Note that `project_rename` is not triggered if the namespace changes. +Please refer to `group_rename` and `user_rename` for that case. + **Project transferred:** ```json @@ -175,6 +196,21 @@ X-Gitlab-Event: System Hook } ``` +**User renamed:** + +```json +{ + "event_name": "user_rename", + "created_at": "2017-11-01T11:21:04Z", + "updated_at": "2017-11-01T14:04:47Z", + "name": "new-name", + "email": "best-email@example.tld", + "user_id": 58, + "username": "new-exciting-name", + "old_username": "old-boring-name" +} +``` + **Key added** ```json @@ -209,13 +245,15 @@ X-Gitlab-Event: System Hook "updated_at": "2012-07-21T07:38:22Z", "event_name": "group_create", "name": "StoreCloud", - "owner_email": "johnsmith@gmail.com", - "owner_name": "John Smith", + "owner_email": null, + "owner_name": null, "path": "storecloud", "group_id": 78 } ``` +`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675. + **Group removed:** ```json @@ -224,13 +262,35 @@ X-Gitlab-Event: System Hook "updated_at": "2012-07-21T07:38:22Z", "event_name": "group_destroy", "name": "StoreCloud", - "owner_email": "johnsmith@gmail.com", - "owner_name": "John Smith", + "owner_email": null, + "owner_name": null, "path": "storecloud", "group_id": 78 } ``` +`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675. + +**Group renamed:** + +```json +{ + "event_name": "group_rename", + "created_at": "2017-10-30T15:09:00Z", + "updated_at": "2017-11-01T10:23:52Z", + "name": "Better Name", + "path": "better-name", + "full_path": "parent-group/better-name", + "group_id": 64, + "owner_name": null, + "owner_email": null, + "old_path": "old-name", + "old_full_path": "parent-group/old-name" +} +``` + +`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675. + **New Group Member:** ```json diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index e68761066d8..561779182bc 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -114,6 +114,7 @@ excluded_attributes: - :milestone_id - :ref_fetched - :merge_jid + - :latest_merge_request_diff_id award_emoji: - :awardable_id statuses: diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb new file mode 100644 index 00000000000..33b23db302a --- /dev/null +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe LfsRequest do + include ProjectForksHelper + + controller(Projects::GitHttpClientController) do + # `described_class` is not available in this context + include LfsRequest # rubocop:disable RSpec/DescribedClass + + def show + storage_project + + render nothing: true + end + + def project + @project ||= Project.find(params[:id]) + end + + def download_request? + true + end + + def ci? + false + end + end + + let(:project) { create(:project, :public) } + + before do + stub_lfs_setting(enabled: true) + end + + describe '#storage_project' do + it 'assigns the project as storage project' do + get :show, id: project.id + + expect(assigns(:storage_project)).to eq(project) + end + + it 'assigns the source of a forked project' do + forked_project = fork_project(project) + + get :show, id: forked_project.id + + expect(assigns(:storage_project)).to eq(project) + end + end +end diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb index 7b0976e3e67..4aed2a25baa 100644 --- a/spec/controllers/metrics_controller_spec.rb +++ b/spec/controllers/metrics_controller_spec.rb @@ -59,17 +59,6 @@ describe MetricsController do expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/) end - it 'returns file system check metrics' do - get :index - - expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/) - expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/) - expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/) - expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/) - expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/) - end - context 'prometheus metrics are disabled' do before do allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(false) diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 01aca443f4a..39ac68af493 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -52,7 +52,7 @@ feature 'Dashboard Todos' do end it 'updates todo count' do - expect(page).to have_content 'To do 0' + expect(page).to have_content 'Todos 0' expect(page).to have_content 'Done 1' end @@ -81,7 +81,7 @@ feature 'Dashboard Todos' do end it 'updates todo count' do - expect(page).to have_content 'To do 1' + expect(page).to have_content 'Todos 1' expect(page).to have_content 'Done 0' end end @@ -200,7 +200,7 @@ feature 'Dashboard Todos' do end it 'updates todo count' do - expect(page).to have_content 'To do 1' + expect(page).to have_content 'Todos 1' expect(page).to have_content 'Done 0' end end @@ -256,7 +256,7 @@ feature 'Dashboard Todos' do end it 'shows "All done" message!' do - expect(page).to have_content 'To do 0' + expect(page).to have_content 'Todos 0' expect(page).to have_content "You're all done!" expect(page).not_to have_selector('.gl-pagination') end @@ -283,7 +283,7 @@ feature 'Dashboard Todos' do it 'updates todo count' do mark_all_and_undo - expect(page).to have_content 'To do 2' + expect(page).to have_content 'Todos 2' expect(page).to have_content 'Done 0' end diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb new file mode 100644 index 00000000000..28954a4fb40 --- /dev/null +++ b/spec/features/projects/settings/forked_project_settings_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +feature 'Settings for a forked project', :js do + include ProjectForksHelper + let(:user) { create(:user) } + let(:original_project) { create(:project) } + let(:forked_project) { fork_project(original_project, user) } + + before do + original_project.add_master(user) + forked_project.add_master(user) + sign_in(user) + end + + shared_examples 'project settings for a forked projects' do + it 'allows deleting the link to the forked project' do + visit edit_project_path(forked_project) + + click_button 'Remove fork relationship' + + wait_for_requests + + fill_in('confirm_name_input', with: forked_project.name) + click_button('Confirm') + + expect(page).to have_content('The fork relationship has been removed.') + expect(forked_project.reload.forked?).to be_falsy + end + end + + it_behaves_like 'project settings for a forked projects' + + context 'when the original project is deleted' do + before do + original_project.destroy! + end + + it_behaves_like 'project settings for a forked projects' + end +end diff --git a/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb b/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb new file mode 100644 index 00000000000..4ea7f441f7c --- /dev/null +++ b/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20171026082505_populate_merge_requests_latest_merge_request_diff_id') + +describe PopulateMergeRequestsLatestMergeRequestDiffId, :migration do + let(:projects_table) { table(:projects) } + let(:merge_requests_table) { table(:merge_requests) } + let(:merge_request_diffs_table) { table(:merge_request_diffs) } + + let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') } + + def create_mr!(name, diffs: 0) + merge_request = + merge_requests_table.create!(target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: name, + title: name) + + diffs.times do + merge_request_diffs_table.create!(merge_request_id: merge_request.id) + end + + merge_request + end + + def diffs_for(merge_request) + merge_request_diffs_table.where(merge_request_id: merge_request.id) + end + + describe '#up' do + it 'ignores MRs without diffs' do + merge_request_without_diff = create_mr!('without_diff') + + expect(merge_request_without_diff.latest_merge_request_diff_id).to be_nil + + expect { migrate! } + .not_to change { merge_request_without_diff.reload.latest_merge_request_diff_id } + end + + it 'ignores MRs that have a diff ID already set' do + merge_request_with_multiple_diffs = create_mr!('with_multiple_diffs', diffs: 3) + diff_id = diffs_for(merge_request_with_multiple_diffs).minimum(:id) + + merge_request_with_multiple_diffs.update!(latest_merge_request_diff_id: diff_id) + + expect { migrate! } + .not_to change { merge_request_with_multiple_diffs.reload.latest_merge_request_diff_id } + end + + it 'migrates multiple MR diffs to the correct values' do + merge_requests = Array.new(3).map.with_index { |_, i| create_mr!(i, diffs: 3) } + + migrate! + + merge_requests.each do |merge_request| + expect(merge_request.reload.latest_merge_request_diff_id) + .to eq(diffs_for(merge_request).maximum(:id)) + end + end + end +end diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index ab8773b7ede..3106207811a 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -134,6 +134,7 @@ describe Group, 'Routable' do context 'with RequestStore active', :request_store do it 'does not load the route table more than once' do + group.expires_full_path_cache expect(group).to receive(:uncached_full_path).once.and_call_original 3.times { group.full_path } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index e1be23541e8..f75de0a0d88 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -547,6 +547,15 @@ describe Environment do expect(environment.slug).to eq(original_slug) end + + it "regenerates the slug if nil" do + environment = build(:environment, slug: nil) + + new_slug = environment.slug + + expect(new_slug).not_to be_nil + expect(environment.slug).to eq(new_slug) + end end describe '#generate_slug' do @@ -583,6 +592,12 @@ describe Environment do it 'returns a path that uses the slug and does not have spaces' do expect(environment.ref_path).to start_with('refs/environments/staging-review-1-') end + + it "doesn't change when the slug is nil initially" do + environment.slug = nil + + expect(environment.ref_path).to eq(environment.ref_path) + end end describe '#external_url_for' do diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb index 605ccd6db06..a43baf1820a 100644 --- a/spec/models/fork_network_spec.rb +++ b/spec/models/fork_network_spec.rb @@ -24,6 +24,16 @@ describe ForkNetwork do end end + describe '#merge_requests' do + it 'finds merge requests within the fork network' do + project = create(:project) + forked_project = fork_project(project) + merge_request = create(:merge_request, source_project: forked_project, target_project: project) + + expect(project.fork_network.merge_requests).to include(merge_request) + end + end + context 'for a deleted project' do it 'keeps the fork network' do project = create(:project, :public) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index f36d6eeb327..0e1a7fdce0b 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -488,6 +488,47 @@ describe Group do end end + describe '#path_changed_hook' do + let(:system_hook_service) { SystemHooksService.new } + + context 'for a new group' do + let(:group) { build(:group) } + + before do + expect(group).to receive(:system_hook_service).and_return(system_hook_service) + end + + it 'does not trigger system hook' do + expect(system_hook_service).to receive(:execute_hooks_for).with(group, :create) + + group.save! + end + end + + context 'for an existing group' do + let(:group) { create(:group, path: 'old-path') } + + context 'when the path is changed' do + let(:new_path) { 'very-new-path' } + + it 'triggers the rename system hook' do + expect(group).to receive(:system_hook_service).and_return(system_hook_service) + expect(system_hook_service).to receive(:execute_hooks_for).with(group, :rename) + + group.update_attributes!(path: new_path) + end + end + + context 'when the path is not changed' do + it 'does not trigger system hook' do + expect(group).not_to receive(:system_hook_service) + + group.update_attributes!(name: 'new name') + end + end + end + end + describe '#secret_variables_for' do let(:project) { create(:project, group: group) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ed6e42d476e..e8588975118 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1923,6 +1923,20 @@ describe Project do expect(forked_project.in_fork_network_of?(other_project)).to be_falsy end end + + describe '#fork_source' do + let!(:second_fork) { fork_project(forked_project) } + + it 'returns the direct source if it exists' do + expect(second_fork.fork_source).to eq(forked_project) + end + + it 'returns the root of the fork network when the directs source was deleted' do + forked_project.destroy + + expect(second_fork.fork_source).to eq(project) + end + end end describe '#pushes_since_gc' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fb03e320734..e0896d64c8f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2217,6 +2217,42 @@ describe User do end end + describe '#username_changed_hook' do + context 'for a new user' do + let(:user) { build(:user) } + + it 'does not trigger system hook' do + expect(user).not_to receive(:system_hook_service) + + user.save! + end + end + + context 'for an existing user' do + let(:user) { create(:user, username: 'old-username') } + + context 'when the username is changed' do + let(:new_username) { 'very-new-name' } + + it 'triggers the rename system hook' do + system_hook_service = SystemHooksService.new + expect(system_hook_service).to receive(:execute_hooks_for).with(user, :rename) + expect(user).to receive(:system_hook_service).and_return(system_hook_service) + + user.update_attributes!(username: new_username) + end + end + + context 'when the username is not changed' do + it 'does not trigger system hook' do + expect(user).not_to receive(:system_hook_service) + + user.update_attributes!(email: 'asdf@asdf.com') + end + end + end + end + describe '#sync_attribute?' do let(:user) { described_class.new } diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb new file mode 100644 index 00000000000..47a2a9d6403 --- /dev/null +++ b/spec/services/applications/create_service_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe ::Applications::CreateService do + let(:user) { create(:user) } + let(:params) { attributes_for(:application) } + let(:request) { ActionController::TestRequest.new(remote_ip: '127.0.0.1') } + + subject { described_class.new(user, params) } + + it 'creates an application' do + expect { subject.execute(request) }.to change { Doorkeeper::Application.count }.by(1) + end +end diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb index 50d3a4ec982..2bba71fef4f 100644 --- a/spec/services/projects/unlink_fork_service_spec.rb +++ b/spec/services/projects/unlink_fork_service_spec.rb @@ -12,6 +12,9 @@ describe Projects::UnlinkForkService do context 'with opened merge request on the source project' do let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) } + let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) } + let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) } + let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) } before do @@ -22,9 +25,14 @@ describe Projects::UnlinkForkService do it 'close all pending merge requests' do expect(mr_close_service).to receive(:execute).with(merge_request) + expect(mr_close_service).to receive(:execute).with(merge_request2) subject.execute end + + it 'does not close merge requests for the project being unlinked' do + expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork) + end end it 'remove fork relation' do @@ -53,4 +61,14 @@ describe Projects::UnlinkForkService do expect(source.forks_count).to be_zero end + + context 'when the original project was deleted' do + it 'does not fail when the original project is deleted' do + source = forked_project.forked_from_project + source.destroy + forked_project.reload + + expect { subject.execute }.not_to raise_error + end + end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 8f7aea533dc..46cd10cdc12 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -69,11 +69,48 @@ describe SystemHooksService do expect(data[:project_visibility]).to eq('private') end + + context 'group_rename' do + it 'contains old and new path' do + allow(group).to receive(:path_was).and_return('old-path') + + data = event_data(group, :rename) + + expect(data).to include(:event_name, :name, :created_at, :updated_at, :full_path, :path, :group_id, :old_path, :old_full_path) + expect(data[:path]).to eq(group.path) + expect(data[:full_path]).to eq(group.path) + expect(data[:old_path]).to eq(group.path_was) + expect(data[:old_full_path]).to eq(group.path_was) + end + + it 'contains old and new full_path for subgroup' do + subgroup = create(:group, parent: group) + allow(subgroup).to receive(:path_was).and_return('old-path') + + data = event_data(subgroup, :rename) + + expect(data[:full_path]).to eq(subgroup.full_path) + expect(data[:old_path]).to eq('old-path') + end + end + + context 'user_rename' do + it 'contains old and new username' do + allow(user).to receive(:username_was).and_return('old-username') + + data = event_data(user, :rename) + + expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :old_username) + expect(data[:username]).to eq(user.username) + expect(data[:old_username]).to eq(user.username_was) + end + end end context 'event names' do it { expect(event_name(user, :create)).to eq "user_create" } it { expect(event_name(user, :destroy)).to eq "user_destroy" } + it { expect(event_name(user, :rename)).to eq 'user_rename' } it { expect(event_name(project, :create)).to eq "project_create" } it { expect(event_name(project, :destroy)).to eq "project_destroy" } it { expect(event_name(project, :rename)).to eq "project_rename" } @@ -85,6 +122,7 @@ describe SystemHooksService do it { expect(event_name(key, :destroy)).to eq 'key_destroy' } it { expect(event_name(group, :create)).to eq 'group_create' } it { expect(event_name(group, :destroy)).to eq 'group_destroy' } + it { expect(event_name(group, :rename)).to eq 'group_rename' } it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' } it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' } end diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index 4d448a55978..4ead78529c3 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -38,6 +38,10 @@ module StubConfiguration allow(Gitlab.config.backup).to receive_messages(to_settings(messages)) end + def stub_lfs_setting(messages) + allow(Gitlab.config.lfs).to receive_messages(to_settings(messages)) + end + def stub_storage_settings(messages) # Default storage is always required messages['default'] ||= Gitlab.config.repositories.storages.default diff --git a/vendor/assets/javascripts/peek.js b/vendor/assets/javascripts/peek.js index f7e77de34ff..6a341a3f0fe 100644 --- a/vendor/assets/javascripts/peek.js +++ b/vendor/assets/javascripts/peek.js @@ -1,5 +1,14 @@ +/* + * This is a modified version of https://github.com/peek/peek/blob/master/app/assets/javascripts/peek.js + * + * - Removed the dependency on jquery.tipsy + * - Removed the initializeTipsy and toggleBar functions + * - Customized updatePerformanceBar to handle SQL queries report specificities + * - Changed /peek/results to /-/peek/results + * - Removed the keypress, pjax:end, page:change, and turbolinks:load handlers + */ (function($) { - var fetchRequestResults, getRequestId, peekEnabled, toggleBar, updatePerformanceBar; + var fetchRequestResults, getRequestId, peekEnabled, updatePerformanceBar; getRequestId = function() { return $('#peek').data('request-id'); }; @@ -41,22 +50,6 @@ }); return $(document).trigger('peek:render', [getRequestId(), results]); }; - toggleBar = function(event) { - var wrapper; - if ($(event.target).is(':input')) { - return; - } - if (event.which === 96 && !event.metaKey) { - wrapper = $('#peek'); - if (wrapper.hasClass('disabled')) { - wrapper.removeClass('disabled'); - return document.cookie = "peek=true; path=/"; - } else { - wrapper.addClass('disabled'); - return document.cookie = "peek=false; path=/"; - } - } - }; fetchRequestResults = function() { return $.ajax('/-/peek/results', { data: { @@ -68,7 +61,6 @@ error: function(xhr, textStatus, error) {} }); }; - $(document).on('keypress', toggleBar); $(document).on('peek:update', fetchRequestResults); return $(function() { if (peekEnabled()) { |