diff options
60 files changed, 684 insertions, 293 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76117a48730..9c4b4acbaf5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,7 +210,7 @@ rake brakeman: *exec rake flay: *exec license_finder: *exec rake downtime_check: *exec -rake ce_to_ee_merge_check: +rake ee_compat_check: <<: *exec only: - branches diff --git a/.scss-lint.yml b/.scss-lint.yml index f8fc1c077b8..5c8e5ac0758 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -191,7 +191,7 @@ linters: # Variables should be formatted with a single space separating the colon # from the variable's value. SpaceAfterVariableColon: - enabled: false + enabled: true # Variables should be formatted with no space between the name and the # colon. @@ -201,7 +201,7 @@ linters: # Operators should be formatted with a single space on both sides of an # infix operator. SpaceAroundOperator: - enabled: false + enabled: true # Opening braces should be preceded by a single space. SpaceBeforeBrace: @@ -223,7 +223,7 @@ linters: # Reports lines containing trailing whitespace. TrailingWhitespace: - enabled: false + enabled: true # Don't write trailing zeros for numeric values with a decimal point. TrailingZero: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f02cc1f102..60f932e1f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ Please view this file on the master branch, on stable branches it's out of date. - Trim leading and trailing whitespace on project_path (Linus Thiel) - Prevent award emoji via notes for issues/MRs authored by user (barthc) - Adds an optional path parameter to the Commits API to filter commits by path (Luis HGO) + - Fix extra space on Build sidebar on Firefox !7060 - Fix HipChat notifications rendering (airatshigapov, eisnerd) - Add hover to trash icon in notes !7008 (blackst0ne) + - Escape ref and path for relative links !6050 (winniehell) - Simpler arguments passed to named_route on toggle_award_url helper method - Fix: Backup restore doesn't clear cache - Use MergeRequestsClosingIssues cache data on Issue#closed_by_merge_requests method @@ -15,12 +17,16 @@ Please view this file on the master branch, on stable branches it's out of date. - Refactor email, use setter method instead AR callbacks for email attribute (Semyon Pupkov) ## 8.13.1 (unreleased) + - Fix bug where labels would be assigned to issues that were moved - Fix error in generating labels - Fix reply-by-email not working due to queue name mismatch + - Fixed hidden pipeline graph on commit and MR page !6895 - Expire and build repository cache after project import - Fix 404 for group pages when GitLab setup uses relative url - - Simpler arguments passed to named_route on toggle_award_url helper method + - Simpler arguments passed to named_route on toggle_award_url helper method + - Fix unauthorized users dragging on issue boards - Better handle when no users were selected for adding to group or project. (Linus Thiel) + - Only show register tab if signup enabled. ## 8.13.0 (2016-10-22) - Removes extra line for empty issue description. (!7045) @@ -102,6 +108,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Add visibility level to project repository - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) + - Fix showing commits from source project for merge request !6658 - Fix that manual jobs would no longer block jobs in the next stage. !6604 - Add configurable email subject suffix (Fu Xu) - Use defined colour for a language when available !6748 (nilsding) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 97462a5959c..f4c387a1a05 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -148,7 +148,7 @@ }; Build.prototype.translateSidebar = function(e) { - var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop; + var newPosition = this.sidebarTranslationLimits.max - (document.body.scrollTop || document.documentElement.scrollTop); if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min; this.$sidebar.css({ top: newPosition diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index afc0d6f8c62..a1fe57562fa 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -117,6 +117,9 @@ new ZenMode(); shortcut_handler = new ShortcutsNavigation(); break; + case 'projects:commit:builds': + new gl.Pipelines(); + break; case 'projects:commits:show': case 'projects:activity': shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 8657e7b4abf..8e8f9f29ab3 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -137,8 +137,11 @@ } initValidators () { - // select all non-hidden inputs in form - this.state.inputs = this.form.find(':input:not([type=hidden])').toArray() + // register selectors here as needed + const validateSelectors = [':text', ':password', '[type=email]'] + .map((selector) => `input${selector}`).join(','); + + this.state.inputs = this.form.find(validateSelectors).toArray() .filter((input) => !input.classList.contains(customValidationFlag)) .map((input) => new GlFieldError({ input, formErrors: this })); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 9f28738e06b..3dde979185b 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -282,6 +282,7 @@ document.querySelector("div#builds").innerHTML = data.html; gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); _this.buildsLoaded = true; + if (!this.pipelines) this.pipelines = new gl.Pipelines(); return _this.scrollToElement("#builds"); }; })(this) diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 0224cc2df21..f1d36efb3de 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -1,10 +1,10 @@ // This file is based off animate.css 3.5.1, available here: // https://github.com/daneden/animate.css/blob/3.5.1/animate.css -// +// // animate.css - http://daneden.me/animate // Version - 3.5.1 // Licensed under the MIT license - http://opensource.org/licenses/MIT -// +// // Copyright (c) 2016 Daniel Eden .animated { diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index a2d0b1353da..1de246600fd 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -404,7 +404,7 @@ .dropdown-content { max-height: 215px; - overflow-y: scroll; + overflow-y: auto; } .dropdown-footer { diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index a90e45bb5f4..429cfbe7235 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -61,7 +61,7 @@ 10%, 80% { fill: $tanuki-red; } - + 20%, 90% { fill: lighten($tanuki-red, 25%); } diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 8374f30d0b2..8cd49280e1c 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -3,7 +3,7 @@ padding: 15px; .form-actions { - margin: -$gl-padding+1; + margin: -$gl-padding + 1; margin-top: 15px; } diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index 915aa631ef8..44fe37d3a4a 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -16,21 +16,21 @@ // $gray-light: lighten($gray-base, 46.7%) // #777 // $gray-lighter: lighten($gray-base, 93.5%) // #eee -$brand-primary: $gl-primary; -$brand-success: $gl-success; -$brand-info: $gl-info; -$brand-warning: $gl-warning; -$brand-danger: $gl-danger; +$brand-primary: $gl-primary; +$brand-success: $gl-success; +$brand-info: $gl-info; +$brand-warning: $gl-warning; +$brand-danger: $gl-danger; -$border-radius-base: 3px !default; -$border-radius-large: 3px !default; -$border-radius-small: 3px !default; +$border-radius-base: 3px !default; +$border-radius-large: 3px !default; +$border-radius-small: 3px !default; //== Scaffolding // -$text-color: $gl-text-color; -$link-color: $gl-link-color; +$text-color: $gl-text-color; +$link-color: $gl-link-color; //== Typography @@ -38,112 +38,112 @@ $link-color: $gl-link-color; //## Font, line-height, and color for body text, headings, and more. $font-family-sans-serif: $regular_font; -$font-family-monospace: $monospace_font; -$font-size-base: $gl-font-size; +$font-family-monospace: $monospace_font; +$font-size-base: $gl-font-size; //== Components // //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). -$padding-base-vertical: $gl-vert-padding; -$padding-base-horizontal: $gl-padding; -$component-active-color: #fff; -$component-active-bg: $brand-info; +$padding-base-vertical: $gl-vert-padding; +$padding-base-horizontal: $gl-padding; +$component-active-color: #fff; +$component-active-bg: $brand-info; //== Forms // //## -$input-color: $text-color; -$input-border: $border-color; -$input-border-focus: $focus-border-color; -$legend-color: $text-color; +$input-color: $text-color; +$input-border: $border-color; +$input-border-focus: $focus-border-color; +$legend-color: $text-color; //== Pagination // //## -$pagination-color: $gl-gray; -$pagination-bg: #fff; -$pagination-border: $border-color; +$pagination-color: $gl-gray; +$pagination-bg: #fff; +$pagination-border: $border-color; -$pagination-hover-color: $gl-gray; -$pagination-hover-bg: $row-hover; -$pagination-hover-border: $border-color; +$pagination-hover-color: $gl-gray; +$pagination-hover-bg: $row-hover; +$pagination-hover-border: $border-color; -$pagination-active-color: $blue-dark; -$pagination-active-bg: #fff; -$pagination-active-border: $border-color; +$pagination-active-color: $blue-dark; +$pagination-active-bg: #fff; +$pagination-active-border: $border-color; -$pagination-disabled-color: #cdcdcd; -$pagination-disabled-bg: $background-color; -$pagination-disabled-border: $border-color; +$pagination-disabled-color: #cdcdcd; +$pagination-disabled-bg: $background-color; +$pagination-disabled-border: $border-color; //== Form states and alerts // //## Define colors for form feedback states and, by default, alerts. -$state-success-text: #fff; -$state-success-bg: $brand-success; -$state-success-border: $brand-success; +$state-success-text: #fff; +$state-success-bg: $brand-success; +$state-success-border: $brand-success; -$state-info-text: #fff; -$state-info-bg: $brand-info; -$state-info-border: $brand-info; +$state-info-text: #fff; +$state-info-bg: $brand-info; +$state-info-border: $brand-info; -$state-warning-text: #fff; -$state-warning-bg: $brand-warning; -$state-warning-border: $brand-warning; +$state-warning-text: #fff; +$state-warning-bg: $brand-warning; +$state-warning-border: $brand-warning; -$state-danger-text: #fff; -$state-danger-bg: $brand-danger; -$state-danger-border: $brand-danger; +$state-danger-text: #fff; +$state-danger-bg: $brand-danger; +$state-danger-border: $brand-danger; //== Alerts // //## Define alert colors, border radius, and padding. -$alert-border-radius: 0; +$alert-border-radius: 0; //== Panels // //## -$panel-border-radius: 2px; -$panel-default-text: $text-color; -$panel-default-border: $border-color; +$panel-border-radius: 2px; +$panel-default-text: $text-color; +$panel-default-border: $border-color; $panel-default-heading-bg: $background-color; -$panel-footer-bg: $background-color; -$panel-inner-border: $border-color; +$panel-footer-bg: $background-color; +$panel-inner-border: $border-color; //== Wells // //## -$well-bg: $gray-light; -$well-border: #eee; +$well-bg: $gray-light; +$well-border: #eee; //== Code // //## -$code-color: #c7254e; -$code-bg: #f9f2f4; +$code-color: #c7254e; +$code-bg: #f9f2f4; -$kbd-color: #fff; -$kbd-bg: #333; +$kbd-color: #fff; +$kbd-bg: #333; //== Buttons // //## -$btn-default-color: $gl-text-color; -$btn-default-bg: #fff; -$btn-default-border: #e7e9ed; +$btn-default-color: $gl-text-color; +$btn-default-bg: #fff; +$btn-default-border: #e7e9ed; //== Nav // @@ -153,8 +153,8 @@ $nav-link-padding: 13px $gl-padding; //== Code // //## -$pre-bg: $background-color !default; -$pre-color: $gl-gray !default; +$pre-bg: $background-color !default; +$pre-color: $gl-gray !default; $pre-border-color: $border-color; $table-bg-accent: $background-color; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index eafe84570a8..b271f8cf332 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -84,39 +84,39 @@ $warning-message-border: #f0e2bb; /* * UI elements */ -$border-color: #e5e5e5; -$focus-border-color: #3aabf0; -$table-border-color: #f0f0f0; -$background-color: $gray-light; +$border-color: #e5e5e5; +$focus-border-color: #3aabf0; +$table-border-color: #f0f0f0; +$background-color: $gray-light; $dark-background-color: #f5f5f5; -$table-text-gray: #8f8f8f; +$table-text-gray: #8f8f8f; /* * Text */ -$gl-font-size: 15px; -$gl-title-color: #333; -$gl-text-color: #5c5c5c; -$gl-text-color-light: #8c8c8c; -$gl-text-green: #4a2; -$gl-text-red: #d12f19; -$gl-text-orange: #d90; -$gl-link-color: #3084bb; -$gl-dark-link-color: #333; +$gl-font-size: 15px; +$gl-title-color: #333; +$gl-text-color: #5c5c5c; +$gl-text-color-light: #8c8c8c; +$gl-text-green: #4a2; +$gl-text-red: #d12f19; +$gl-text-orange: #d90; +$gl-link-color: #3084bb; +$gl-dark-link-color: #333; $gl-placeholder-color: #8f8f8f; -$gl-icon-color: $gl-placeholder-color; -$gl-grayish-blue: #7f8fa4; -$gl-gray: $gl-text-color; -$gl-gray-dark: #313236; -$gl-gray-light: $gl-placeholder-color; -$gl-header-color: #4c4e54; +$gl-icon-color: $gl-placeholder-color; +$gl-grayish-blue: #7f8fa4; +$gl-gray: $gl-text-color; +$gl-gray-dark: #313236; +$gl-gray-light: $gl-placeholder-color; +$gl-header-color: #4c4e54; /* * Lists */ -$list-font-size: $gl-font-size; +$list-font-size: $gl-font-size; $list-title-color: $gl-title-color; -$list-text-color: $gl-text-color; +$list-text-color: $gl-text-color; $list-text-height: 42px; /* diff --git a/app/assets/stylesheets/mailers/devise.scss b/app/assets/stylesheets/mailers/devise.scss index 9495c5b3f37..b2bce482fde 100644 --- a/app/assets/stylesheets/mailers/devise.scss +++ b/app/assets/stylesheets/mailers/devise.scss @@ -5,13 +5,13 @@ // Styles defined here are embedded directly into the resulting email HTML via // the `premailer` gem. -$body-background-color: #363636; +$body-background-color: #363636; $message-background-color: #fafafa; -$header-color: #6b4fbb; -$body-color: #444; -$cta-color: #e14329; -$footer-link-color: #7e7e7e; +$header-color: #6b4fbb; +$body-color: #444; +$cta-color: #e14329; +$footer-link-color: #7e7e7e; $font-family: Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index d732008de3d..572e1e7d558 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -9,15 +9,15 @@ padding: 24px 0; border-bottom: none; position: relative; - + @media (max-width: $screen-sm-min) { padding: 6px 0 24px; - } + } } .column { text-align: center; - + @media (max-width: $screen-sm-min) { padding: 15px 0; } @@ -36,7 +36,7 @@ &:last-child { text-align: right; - + @media (max-width: $screen-sm-min) { text-align: center; } @@ -51,7 +51,7 @@ .bordered-box { border: 1px solid $border-color; border-radius: $border-radius-default; - + } .content-list { @@ -73,10 +73,10 @@ font-weight: 600; color: $gl-title-color; } - + &.text { color: $layout-link-gray; - + &.value-col { color: $gl-title-color; } @@ -108,13 +108,13 @@ .svg-container { text-align: center; - + svg { width: 136px; height: 136px; } } - + .inner-content { @media (max-width: $screen-sm-min) { padding: 0 28px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 9627d1c841b..e0367d1d942 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -223,12 +223,12 @@ top: 13px; right: 7px; } - + .frame { top: 0; right: 0; position: absolute; - + &.deleted { margin: 0; display: block; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 12ee0a5dc3d..fc49ff780fc 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -37,10 +37,10 @@ .branch-name { color: $gl-dark-link-color; } - + .stop-env-link { color: $table-text-gray; - + .stop-env-icon { font-size: 14px; } @@ -48,11 +48,11 @@ .deployment { .build-column { - + .build-link { color: $gl-dark-link-color; } - + .avatar { float: none; } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 5d9a76dac05..3004959ff7b 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -142,7 +142,7 @@ .event-last-push { overflow: auto; width: 100%; - + .event-last-push-text { @include str-truncated(100%); padding: 4px 0; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 82e46377308..3d2b024fe5c 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -289,7 +289,7 @@ .new_user { position: relative; padding-bottom: 35px; - + @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { .forgot-password { float: none !important; diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index aa8057e4b9d..032feae8854 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -255,7 +255,7 @@ $colors: ( border-top: solid 2px $border-green-extra-light; } } - + .editor { pre { height: 350px; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index c1a3c082cfa..16ddef481bd 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -8,7 +8,7 @@ .diff-file .diff-content { tr.line_holder:hover > td .line_note_link { opacity: 1.0; - filter: alpha(opacity=100); + filter: alpha(opacity = 100); } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 586f21821f7..b90c91831f2 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -459,7 +459,7 @@ ul.notes { .discussion-next-btn { svg { margin: 0; - + path { fill: $gray-darkest; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index f1d53c7b8bc..01426e28e92 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -74,7 +74,7 @@ .ci-status-icon-success_with_warning { color: $gl-warning; } - + .ci-status-icon-running { color: $blue-normal; } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 99d53d52119..2b836fa1f4a 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -30,11 +30,11 @@ .last-commit { @include str-truncated(506px); - + @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { @include str-truncated(450px); } - + } .commit-history-link-spacer { diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 6ace14a4bb5..95e62cdb02a 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -35,8 +35,10 @@ class LabelsFinder < UnionFinder end def with_title(items) - items = items.where(title: title) if title - items + return items if title.nil? + return items.none if title.blank? + + items.where(title: title) end def group_id @@ -52,7 +54,7 @@ class LabelsFinder < UnionFinder end def title - params[:title].presence || params[:name].presence + params[:title] || params[:name] end def project diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index b7247ffa8b2..38c586ccd31 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -5,7 +5,7 @@ module BoardsHelper { endpoint: namespace_project_boards_path(@project.namespace, @project), board_id: board.id, - disabled: !can?(current_user, :admin_list, @project), + disabled: "#{!can?(current_user, :admin_list, @project)}", issue_link_base: namespace_project_issues_path(@project.namespace, @project) } end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index a6b606d13de..bf5f92f8462 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -3,8 +3,8 @@ module Ci include TokenAuthenticatable include AfterCommitQueue - belongs_to :runner, class_name: 'Ci::Runner' - belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' + belongs_to :runner + belongs_to :trigger_request belongs_to :erased_by, class_name: 'User' serialize :options diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d5c1e03b461..adda3b8f40c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -7,12 +7,12 @@ module Ci self.table_name = 'ci_commits' - belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id + belongs_to :project, foreign_key: :gl_project_id belongs_to :user has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id - has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id - has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id + has_many :builds, foreign_key: :commit_id + has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id validates_presence_of :sha, unless: :importing? validates_presence_of :ref, unless: :importing? diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 44cb19ece3b..123930273e0 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -6,9 +6,9 @@ module Ci AVAILABLE_SCOPES = %w[specific shared active paused online] FORM_EDITABLE = %i[description tag_list active run_untagged locked] - has_many :builds, class_name: 'Ci::Build' - has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' - has_many :projects, through: :runner_projects, class_name: '::Project', foreign_key: :gl_project_id + has_many :builds + has_many :runner_projects, dependent: :destroy + has_many :projects, through: :runner_projects, foreign_key: :gl_project_id has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build' diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb index 4b44ffa886e..1f9baeca5b1 100644 --- a/app/models/ci/runner_project.rb +++ b/app/models/ci/runner_project.rb @@ -2,8 +2,8 @@ module Ci class RunnerProject < ActiveRecord::Base extend Ci::Model - belongs_to :runner, class_name: 'Ci::Runner' - belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id + belongs_to :runner + belongs_to :project, foreign_key: :gl_project_id validates_uniqueness_of :runner_id, scope: :gl_project_id end diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index a0b19b51a12..62889fe80d8 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -4,8 +4,8 @@ module Ci acts_as_paranoid - belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id - has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + belongs_to :project, foreign_key: :gl_project_id + has_many :trigger_requests, dependent: :destroy validates_presence_of :token validates_uniqueness_of :token diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index fc674871743..2b807731d0d 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -2,9 +2,9 @@ module Ci class TriggerRequest < ActiveRecord::Base extend Ci::Model - belongs_to :trigger, class_name: 'Ci::Trigger' - belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id - has_many :builds, class_name: 'Ci::Build' + belongs_to :trigger + belongs_to :pipeline, foreign_key: :commit_id + has_many :builds serialize :variables diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 6959223aed9..94d9e2b3208 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -2,7 +2,7 @@ module Ci class Variable < ActiveRecord::Base extend Ci::Model - belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id + belongs_to :project, foreign_key: :gl_project_id validates_uniqueness_of :key, scope: :gl_project_id validates :key, diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 7b554be4f9a..4cb3a69416e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base self.table_name = 'ci_builds' - belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id + belongs_to :project, foreign_key: :gl_project_id belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id belongs_to :user diff --git a/app/models/group.rb b/app/models/group.rb index 00a595d2705..552e1154df6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -6,7 +6,7 @@ class Group < Namespace include AccessRequestable include Referable - has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' + has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source alias_method :members, :group_members has_many :users, through: :group_members has_many :owners, diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 1b54a85d064..204f34f0269 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -1,7 +1,7 @@ class GroupMember < Member SOURCE_TYPE = 'Namespace' - belongs_to :group, class_name: 'Group', foreign_key: 'source_id' + belongs_to :group, foreign_key: 'source_id' # Make sure group member points only to group as it source default_value_for :source_type, SOURCE_TYPE diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index e4880973117..008fff0857c 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -3,7 +3,7 @@ class ProjectMember < Member include Gitlab::ShellAdapter - belongs_to :project, class_name: 'Project', foreign_key: 'source_id' + belongs_to :project, foreign_key: 'source_id' # Make sure project member points only to project as it source default_value_for :source_type, SOURCE_TYPE diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c476a3bb14e..4872f8b8649 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -6,8 +6,8 @@ class MergeRequest < ActiveRecord::Base include Taskable include Importable - belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" - belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" + belongs_to :target_project, class_name: "Project" + belongs_to :source_project, class_name: "Project" belongs_to :merge_user, class_name: "User" has_many :merge_request_diffs, dependent: :destroy diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index b8a10b7968e..dd65a9a8b86 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -299,8 +299,10 @@ class MergeRequestDiff < ActiveRecord::Base end def keep_around_commits - repository.keep_around(start_commit_sha) - repository.keep_around(head_commit_sha) - repository.keep_around(base_commit_sha) + [repository, merge_request.source_project.repository].each do |repo| + repo.keep_around(start_commit_sha) + repo.keep_around(head_commit_sha) + repo.keep_around(base_commit_sha) + end end end diff --git a/app/models/project.rb b/app/models/project.rb index af117f0acb0..fbf7012972e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -63,11 +63,11 @@ class Project < ActiveRecord::Base alias_attribute :title, :name # Relations - belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' + belongs_to :creator, class_name: 'User' belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id' belongs_to :namespace - has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' + has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event' has_many :boards, before_add: :validate_board_limit, dependent: :destroy # Project services @@ -116,7 +116,7 @@ class Project < ActiveRecord::Base has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy - has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember' + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source alias_method :members, :project_members has_many :users, through: :project_members @@ -137,7 +137,7 @@ class Project < ActiveRecord::Base has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :project_feature, dependent: :destroy - has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id + has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id diff --git a/app/models/user.rb b/app/models/user.rb index f367f4616fb..9e76df63d31 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -47,7 +47,7 @@ class User < ActiveRecord::Base # # Namespace for personal projects - has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace" + has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id # Profile has_many :keys, dependent: :destroy @@ -66,17 +66,17 @@ class User < ActiveRecord::Base # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, class_name: 'ProjectMember' + has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy has_many :starred_projects, through: :users_star_projects, source: :project - has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" + has_many :snippets, dependent: :destroy, foreign_key: :author_id has_many :issues, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id - has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" + has_many :events, dependent: :destroy, foreign_key: :author_id has_many :subscriptions, dependent: :destroy has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" @@ -309,7 +309,7 @@ class User < ActiveRecord::Base username end - def to_reference(_from_project = nil) + def to_reference(_from_project = nil, _target_project = nil) "#{self.class.reference_prefix}#{username}" end diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 79b1d447a92..05246303fb6 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,5 +1,6 @@ %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} %li.active{ role: 'presentation' } %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in - %li{ role: 'presentation'} - %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register + - if signin_enabled? && signup_enabled? + %li{ role: 'presentation'} + %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index 0b05785430b..61020516bcf 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -3,4 +3,4 @@ Most recent commits displayed first %ol#commits-list.list-unstyled - = render "projects/commits/commits", project: @merge_request.project + = render "projects/commits/commits", project: @merge_request.source_project diff --git a/doc/administration/integration/koding.md b/doc/administration/integration/koding.md index a2c358af095..b95c425842c 100644 --- a/doc/administration/integration/koding.md +++ b/doc/administration/integration/koding.md @@ -61,6 +61,7 @@ executing commands in the following snippet. ```bash git clone https://github.com/koding/koding.git cd koding +docker-compose -f docker-compose-init.yml run init docker-compose up ``` diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 8827b501901..60b7bec2ba7 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -254,6 +254,12 @@ test: This will make GitLab CI initialize (fetch) and update (checkout) all your submodules recursively. +If Git does not use the newly added relative URLs but still uses your old URLs, +you might need to add `git submodule sync --recursive` to your `.gitlab-ci.yml`, +prior to running `git submodule update --init --recursive`. This transfers the +changes from your `.gitmodules` file into the `.git` folder, which is kept by +runners between runs. + In case your environment or your Docker image doesn't have Git installed, you have to either ask your Administrator or install the missing dependency yourself: diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 4fa8d05481f..f09d78be0ce 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -52,8 +52,8 @@ module Banzai relative_url_root, context[:project].path_with_namespace, uri_type(file_path), - ref, - file_path + Addressable::URI.escape(ref), + Addressable::URI.escape(file_path) ].compact.join('/').squeeze('/').chomp('/') uri diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb new file mode 100644 index 00000000000..b1a6d5fe0f6 --- /dev/null +++ b/lib/gitlab/ee_compat_check.rb @@ -0,0 +1,261 @@ +# rubocop: disable Rails/Output +module Gitlab + # Checks if a set of migrations requires downtime or not. + class EeCompatCheck + EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze + + attr_reader :ce_branch, :check_dir, :ce_repo + + def initialize(branch:, check_dir:, ce_repo: nil) + @ce_branch = branch + @check_dir = check_dir + @ce_repo = ce_repo || 'https://gitlab.com/gitlab-org/gitlab-ce.git' + end + + def check + ensure_ee_repo + delete_patches + + generate_patch(ce_branch, ce_patch_full_path) + + Dir.chdir(check_dir) do + step("In the #{check_dir} directory") + + step("Pulling latest master", %w[git pull --ff-only origin master]) + + status = catch(:halt_check) do + ce_branch_compat_check! + + delete_ee_branch_locally + + ee_branch_presence_check! + + ee_branch_compat_check! + end + + delete_ee_branch_locally + delete_patches + + if status.nil? + true + else + false + end + end + end + + private + + def ensure_ee_repo + if Dir.exist?(check_dir) + step("#{check_dir} already exists") + else + cmd = %W[git clone --branch master --single-branch --depth 1 #{EE_REPO} #{check_dir}] + step("Cloning #{EE_REPO} into #{check_dir}", cmd) + end + end + + def ce_branch_compat_check! + cmd = %W[git apply --check #{ce_patch_full_path}] + status = step("Checking if #{ce_patch_name} applies cleanly to EE/master", cmd) + + if status.zero? + puts ce_applies_cleanly_msg(ce_branch) + throw(:halt_check) + end + end + + def ee_branch_presence_check! + status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}]) + + unless status.zero? + puts + puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg + + throw(:halt_check, :ko) + end + end + + def ee_branch_compat_check! + step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD]) + + generate_patch(ee_branch, ee_patch_full_path) + cmd = %W[git apply --check #{ee_patch_full_path}] + status = step("Checking if #{ee_patch_name} applies cleanly to EE/master", cmd) + + unless status.zero? + puts + puts ee_branch_doesnt_apply_cleanly_msg + + throw(:halt_check, :ko) + end + + puts + puts ee_applies_cleanly_msg + end + + def generate_patch(branch, filepath) + FileUtils.rm(filepath, force: true) + + depth = 0 + loop do + depth += 10 + step("Fetching origin/master", %W[git fetch origin master --depth=#{depth}]) + status = step("Finding merge base with master", %W[git merge-base FETCH_HEAD #{branch}]) + + break if status.zero? || depth > 500 + end + + raise "#{branch} is too far behind master, please rebase it!" if depth > 500 + + step("Generating the patch against master") + output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout]) + throw(:halt_check, :ko) unless status.zero? + + File.write(filepath, output) + throw(:halt_check, :ko) unless File.exist?(filepath) + end + + def delete_ee_branch_locally + command(%w[git checkout master]) + step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}]) + end + + def delete_patches + step("Deleting #{ce_patch_full_path}") + FileUtils.rm(ce_patch_full_path, force: true) + + step("Deleting #{ee_patch_full_path}") + FileUtils.rm(ee_patch_full_path, force: true) + end + + def ce_patch_name + @ce_patch_name ||= "#{ce_branch}.patch" + end + + def ce_patch_full_path + @ce_patch_full_path ||= File.expand_path(ce_patch_name, check_dir) + end + + def ee_branch + @ee_branch ||= "#{ce_branch}-ee" + end + + def ee_patch_name + @ee_patch_name ||= "#{ee_branch}.patch" + end + + def ee_patch_full_path + @ee_patch_full_path ||= File.expand_path(ee_patch_name, check_dir) + end + + def step(desc, cmd = nil) + puts "\n=> #{desc}\n" + + if cmd + puts "\n$ #{cmd.join(' ')}" + command(cmd) + end + end + + def command(cmd) + output, status = Gitlab::Popen.popen(cmd) + puts output + + status + end + + def ce_applies_cleanly_msg(ce_branch) + <<-MSG.strip_heredoc + ================================================================= + 🎉 Congratulations!! 🎉 + + The #{ce_branch} branch applies cleanly to EE/master! + + Much ❤️!! + =================================================================\n + MSG + end + + def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg + <<-MSG.strip_heredoc + ================================================================= + 💥 Oh no! 💥 + + The #{ce_branch} branch does not apply cleanly to the current + EE/master, and no #{ee_branch} branch was found in the EE repository. + + Please create a #{ee_branch} branch that includes changes from + #{ce_branch} but also specific changes than can be applied cleanly + to EE/master. + + There are different ways to create such branch: + + 1. Create a new branch based on the CE branch and rebase it on top of EE/master + + # In the EE repo + $ git fetch #{ce_repo} #{ce_branch} + $ git checkout -b #{ee_branch} FETCH_HEAD + + # You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit + # before rebasing to limit the conflicts-resolving steps during the rebase + $ git fetch origin + $ git rebase origin/master + + At this point you will likely have conflicts. + Solve them, and continue/finish the rebase. + + You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE". + + 2. Create a new branch from master and cherry-pick your CE commits + + # In the EE repo + $ git fetch origin + $ git checkout -b #{ee_branch} FETCH_HEAD + $ git fetch #{ce_repo} #{ce_branch} + $ git cherry-pick SHA # Repeat for all the commits you want to pick + + You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit. + + Don't forget to push your branch to #{EE_REPO}: + + # In the EE repo + $ git push origin #{ee_branch} + + You can then retry this failed build, and hopefully it should pass. + + Stay 💪 ! + =================================================================\n + MSG + end + + def ee_branch_doesnt_apply_cleanly_msg + <<-MSG.strip_heredoc + ================================================================= + 💥 Oh no! 💥 + + The #{ce_branch} does not apply cleanly to the current + EE/master, and even though a #{ee_branch} branch exists in the EE + repository, it does not apply cleanly either to EE/master! + + Please update the #{ee_branch}, push it again to #{EE_REPO}, and + retry this build. + + Stay 💪 ! + =================================================================\n + MSG + end + + def ee_applies_cleanly_msg + <<-MSG.strip_heredoc + ================================================================= + 🎉 Congratulations!! 🎉 + + The #{ee_branch} branch applies cleanly to EE/master! + + Much ❤️!! + =================================================================\n + MSG + end + end +end diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake deleted file mode 100644 index 424e7883060..00000000000 --- a/lib/tasks/ce_to_ee_merge_check.rake +++ /dev/null @@ -1,4 +0,0 @@ -desc 'Checks if the branch would apply cleanly to EE' -task ce_to_ee_merge_check: :environment do - Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke -end diff --git a/lib/tasks/ee_compat_check.rake b/lib/tasks/ee_compat_check.rake new file mode 100644 index 00000000000..f494fa5c5c2 --- /dev/null +++ b/lib/tasks/ee_compat_check.rake @@ -0,0 +1,4 @@ +desc 'Checks if the branch would apply cleanly to EE' +task ee_compat_check: :environment do + Rake::Task['gitlab:dev:ee_compat_check'].invoke +end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake index 47bdb2d32d2..5ee99dfc810 100644 --- a/lib/tasks/gitlab/dev.rake +++ b/lib/tasks/gitlab/dev.rake @@ -1,106 +1,21 @@ namespace :gitlab do namespace :dev do desc 'Checks if the branch would apply cleanly to EE' - task ce_to_ee_merge_check: :environment do + task ee_compat_check: :environment do return if defined?(Gitlab::License) return unless ENV['CI'] - ce_repo = ENV['CI_BUILD_REPO'] - ce_branch = ENV['CI_BUILD_REF_NAME'] - - ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git' - ee_branch = "#{ce_branch}-ee" - ee_dir = 'gitlab-ee-merge-check' - - puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n" - `git clone #{ee_repo} #{ee_dir} --depth 1` - Dir.chdir(ee_dir) do - puts "\n => Fetching #{ce_repo}/#{ce_branch}\n" - `git fetch #{ce_repo} #{ce_branch} --depth 1` - - # Try to merge the current tested branch to EE/master... - puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge FETCH_HEAD` - - exit 0 if $?.success? - - # Check if the <branch>-ee branch exists... - puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n" - `git rev-parse --verify #{ee_branch}` - - # The <branch>-ee doesn't exist - unless $?.success? - puts - puts <<-MSG.strip_heredoc - ================================================================= - The #{ce_branch} branch cannot be merged without conflicts to the - current EE/master, and no #{ee_branch} branch was detected in - the EE repository. - - Please create a #{ee_branch} branch that includes changes from - #{ce_branch} but also specific changes than can be applied cleanly - to EE/master. - - You can create this branch as follows: - - 1. In the EE repo: - $ git fetch origin - $ git fetch #{ce_repo} #{ce_branch} - $ git checkout -b #{ee_branch} FETCH_HEAD - $ git rebase origin/master - 2. At this point you will likely have conflicts, solve them, and - continue/finish the rebase. Note: You can squash the CE commits - before rebasing. - 3. You can squash all the original #{ce_branch} commits into a - single "Port of #{ce_branch} to EE". - 4. Push your branch to #{ee_repo}: - $ git push origin #{ee_branch} - =================================================================\n - MSG - - exit 1 - end - - # Try to merge the <branch>-ee branch to EE/master... - puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" - `git merge #{ee_branch} master` - - # The <branch>-ee cannot be merged cleanly to EE/master... - unless $?.success? - puts - puts <<-MSG.strip_heredoc - ================================================================= - The #{ce_branch} branch cannot be merged without conflicts to - EE/master, and even though the #{ee_branch} branch exists in the EE - repository, it cannot be merged without conflicts to EE/master. - - Please update the #{ee_branch}, push it again to #{ee_repo}, and - retry this job. - =================================================================\n - MSG - - exit 2 - end - - puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge FETCH_HEAD` - exit 0 if $?.success? - - # The <branch>-ee can be merged cleanly to EE/master, but <branch> still - # cannot be merged cleanly to EE/master... - puts - puts <<-MSG.strip_heredoc - ================================================================= - The #{ce_branch} branch cannot be merged without conflicts to EE, and - even though the #{ee_branch} branch exists in the EE repository and - applies cleanly to EE/master, it doesn't prevent conflicts when - merging #{ce_branch} into EE. - - We may be in a complex situation here. - =================================================================\n - MSG - - exit 3 + success = + Gitlab::EeCompatCheck.new( + branch: ENV['CI_BUILD_REF_NAME'], + check_dir: File.expand_path('ee-compat-check', __dir__), + ce_repo: ENV['CI_BUILD_REPO'] + ).check + + if success + exit 0 + else + exit 1 end end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 0fb1608a0a3..c533ce1d87f 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -624,6 +624,10 @@ describe 'Issue Boards', feature: true, js: true do it 'does not show create new list' do expect(page).not_to have_selector('.js-new-board-list') end + + it 'does not allow dragging' do + expect(page).not_to have_selector('.user-can-drag') + end end context 'as guest user' do diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 996f39ea06d..76bcfbe523a 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -215,4 +215,69 @@ feature 'Login', feature: true do end end end + + describe 'UI tabs and panes' do + context 'when no defaults are changed' do + it 'correctly renders tabs and panes' do + ensure_tab_pane_correctness + end + end + + context 'when signup is disabled' do + before do + stub_application_setting(signup_enabled: false) + end + + it 'correctly renders tabs and panes' do + ensure_tab_pane_correctness + end + end + + context 'when ldap is enabled' do + before do + visit new_user_session_path + allow(page).to receive(:form_based_providers).and_return([:ldapmain]) + allow(page).to receive(:ldap_enabled).and_return(true) + end + + it 'correctly renders tabs and panes' do + ensure_tab_pane_correctness(false) + end + end + + context 'when crowd is enabled' do + before do + visit new_user_session_path + allow(page).to receive(:form_based_providers).and_return([:crowd]) + allow(page).to receive(:crowd_enabled?).and_return(true) + end + + it 'correctly renders tabs and panes' do + ensure_tab_pane_correctness(false) + end + end + + def ensure_tab_pane_correctness(visit_path = true) + if visit_path + visit new_user_session_path + end + + ensure_tab_pane_counts + ensure_one_active_tab + ensure_one_active_pane + end + + def ensure_tab_pane_counts + tabs_count = page.all('[role="tab"]').size + expect(page).to have_selector('[role="tabpanel"]', count: tabs_count) + end + + def ensure_one_active_tab + expect(page).to have_selector('.nav-tabs > li.active', count: 1) + end + + def ensure_one_active_pane + expect(page).to have_selector('.tab-pane.active', count: 1) + end + end end diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 27acc464ea2..10cfb66ec1c 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -38,6 +38,14 @@ describe LabelsFinder do expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4] end + + it 'returns labels available if nil title is supplied' do + group_2.add_developer(user) + # params[:title] will return `nil` regardless whether it is specified + finder = described_class.new(user, title: nil) + + expect(finder.execute).to eq [group_label_2, group_label_3, project_label_1, group_label_1, project_label_2, project_label_4] + end end context 'filtering by group_id' do @@ -64,6 +72,30 @@ describe LabelsFinder do expect(finder.execute).to eq [group_label_2] end + + it 'returns label with title alias' do + finder = described_class.new(user, name: 'Group Label 2') + + expect(finder.execute).to eq [group_label_2] + end + + it 'returns no labels if empty title is supplied' do + finder = described_class.new(user, title: []) + + expect(finder.execute).to be_empty + end + + it 'returns no labels if blank title is supplied' do + finder = described_class.new(user, title: '') + + expect(finder.execute).to be_empty + end + + it 'returns no labels if empty name is supplied' do + finder = described_class.new(user, name: []) + + expect(finder.execute).to be_empty + end end end end diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 index 36feb2b2aa5..da9259edd78 100644 --- a/spec/javascripts/gl_field_errors_spec.js.es6 +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -11,12 +11,12 @@ this.fieldErrors = new global.GlFieldErrors($form); }); - it('should properly initialize the form', function() { + it('should select the correct input elements', function() { expect(this.$form).toBeDefined(); expect(this.$form.length).toBe(1); expect(this.fieldErrors).toBeDefined(); const inputs = this.fieldErrors.state.inputs; - expect(inputs.length).toBe(5); + expect(inputs.length).toBe(4); }); it('should ignore elements with custom error handling', function() { diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 6b58f3e43ee..2bfa51deb20 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -50,14 +50,6 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do end end - shared_examples :relative_to_requested do - it 'rebuilds URL relative to the requested path' do - doc = filter(link('users.md')) - expect(doc.at_css('a')['href']). - to eq "/#{project_path}/blob/#{ref}/doc/api/users.md" - end - end - context 'with a project_wiki' do let(:project_wiki) { double('ProjectWiki') } include_examples :preserve_unchanged @@ -188,12 +180,38 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do context 'when requested path is a file in the repo' do let(:requested_path) { 'doc/api/README.md' } - include_examples :relative_to_requested + it 'rebuilds URL relative to the containing directory' do + doc = filter(link('users.md')) + expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" + end end context 'when requested path is a directory in the repo' do - let(:requested_path) { 'doc/api' } - include_examples :relative_to_requested + let(:requested_path) { 'doc/api/' } + it 'rebuilds URL relative to the directory' do + doc = filter(link('users.md')) + expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" + end + end + + context 'when ref name contains percent sign' do + let(:ref) { '100%branch' } + let(:commit) { project.commit('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') } + let(:requested_path) { 'foo/bar/' } + it 'correctly escapes the ref' do + doc = filter(link('.gitkeep')) + expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep" + end + end + + context 'when requested path is a directory with space in the repo' do + let(:ref) { 'master' } + let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') } + let(:requested_path) { 'with space/' } + it 'does not escape the space twice' do + doc = filter(link('README.md')) + expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md" + end end end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index b2fe96e2e02..f6b2ec5ae31 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe ProjectMember, models: true do describe 'associations' do - it { is_expected.to belong_to(:project).class_name('Project').with_foreign_key(:source_id) } + it { is_expected.to belong_to(:project).with_foreign_key(:source_id) } end describe 'validations' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6e5137602aa..1067ff7bb4d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -6,8 +6,8 @@ describe MergeRequest, models: true do subject { create(:merge_request) } describe 'associations' do - it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } - it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } + it { is_expected.to belong_to(:target_project).class_name('Project') } + it { is_expected.to belong_to(:source_project).class_name('Project') } it { is_expected.to belong_to(:merge_user).class_name("User") } it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) } end @@ -1286,7 +1286,8 @@ describe MergeRequest, models: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } - let(:merge_request) do + + let!(:merge_request) do create(:closed_merge_request, source_project: fork_project, target_project: project) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 65b2896930a..10c39b90212 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -15,11 +15,11 @@ describe User, models: true do describe 'associations' do it { is_expected.to have_one(:namespace) } - it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) } + it { is_expected.to have_many(:snippets).dependent(:destroy) } it { is_expected.to have_many(:project_members).dependent(:destroy) } it { is_expected.to have_many(:groups) } it { is_expected.to have_many(:keys).dependent(:destroy) } - it { is_expected.to have_many(:events).class_name('Event').dependent(:destroy) } + it { is_expected.to have_many(:events).dependent(:destroy) } it { is_expected.to have_many(:recent_events).class_name('Event') } it { is_expected.to have_many(:issues).dependent(:destroy) } it { is_expected.to have_many(:notes).dependent(:destroy) } diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index 93bf0f64963..f0ded06b785 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -23,14 +23,15 @@ describe Issues::MoveService, services: true do old_project.team << [user, :reporter] new_project.team << [user, :reporter] - ['label1', 'label2'].each do |label| + labels = Array.new(2) { |x| "label%d" % (x + 1) } + + labels.each do |label| old_issue.labels << create(:label, project_id: old_project.id, title: label) - end - new_project.labels << create(:label, title: 'label1') - new_project.labels << create(:label, title: 'label2') + new_project.labels << create(:label, title: label) + end end end @@ -207,10 +208,10 @@ describe Issues::MoveService, services: true do end end - describe 'rewritting references' do + describe 'rewriting references' do include_context 'issue move executed' - context 'issue reference' do + context 'issue references' do let(:another_issue) { create(:issue, project: old_project) } let(:description) { "Some description #{another_issue.to_reference}" } @@ -219,6 +220,16 @@ describe Issues::MoveService, services: true do .to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}" end end + + context "user references" do + let(:another_issue) { create(:issue, project: old_project) } + let(:description) { "Some description #{user.to_reference}" } + + it "doesn't throw any errors for issues containing user references" do + expect(new_issue.description) + .to eq "Some description #{user.to_reference}" + end + end end context 'moving to same project' do @@ -277,5 +288,25 @@ describe Issues::MoveService, services: true do it { expect { move }.to raise_error(StandardError, /permissions/) } end end + + context 'movable issue with no assigned labels' do + before do + old_project.team << [user, :reporter] + new_project.team << [user, :reporter] + + labels = Array.new(2) { |x| "label%d" % (x + 1) } + + labels.each do |label| + new_project.labels << create(:label, title: label) + end + end + + include_context 'issue move executed' + + it 'does not assign labels to new issue' do + expected_label_titles = new_issue.reload.labels.map(&:title) + expect(expected_label_titles.size).to eq 0 + end + end end end diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb new file mode 100644 index 00000000000..6f70b3daf8e --- /dev/null +++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe 'projects/merge_requests/show/_commits.html.haml' do + include Devise::Test::ControllerHelpers + + let(:user) { create(:user) } + let(:target_project) { create(:project) } + + let(:source_project) do + create(:project, forked_from_project: target_project) + end + + let(:merge_request) do + create(:merge_request, :simple, + source_project: source_project, + target_project: target_project, + author: user) + end + + before do + controller.prepend_view_path('app/views/projects') + + assign(:merge_request, merge_request) + assign(:commits, merge_request.commits) + end + + it 'shows commits from source project' do + render + + commit = source_project.commit(merge_request.source_branch) + href = namespace_project_commit_path( + source_project.namespace, + source_project, + commit) + + expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href) + end +end |