diff options
74 files changed, 467 insertions, 820 deletions
@@ -2,6 +2,7 @@ source 'https://rubygems.org' gem 'rails', '4.2.8' gem 'rails-deprecated_sanitizer', '~> 1.0.3' +gem 'bootsnap', '~> 1.0.0' # Responders respond_to and respond_with gem 'responders', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index b5f9c3beca7..d34b84df5e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,6 +82,8 @@ GEM bindata (2.3.5) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + bootsnap (1.0.0) + msgpack (~> 1.0) bootstrap-sass (3.3.6) autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) @@ -459,6 +461,7 @@ GEM minitest (5.7.0) mmap2 (2.2.6) mousetrap-rails (1.4.6) + msgpack (1.1.0) multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) @@ -888,6 +891,7 @@ DEPENDENCIES benchmark-ips (~> 2.3.0) better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) + bootsnap (~> 1.0.0) bootstrap-sass (~> 3.3.0) brakeman (~> 3.6.0) browser (~> 2.2) diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 4568b86f298..dc636050221 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -35,7 +35,7 @@ export default class BlobFileDropzone { this.removeFile(file); }); this.on('sending', function (file, xhr, formData) { - formData.append('branch_name', form.find('input[name="branch_name"]').val()); + formData.append('branch_name', form.find('.js-branch-name').val()); formData.append('create_merge_request', form.find('.js-create-merge-request').val()); formData.append('commit_message', form.find('.js-commit-message').val()); }); diff --git a/app/assets/javascripts/blob/create_branch_dropdown.js b/app/assets/javascripts/blob/create_branch_dropdown.js deleted file mode 100644 index 95517f51b1c..00000000000 --- a/app/assets/javascripts/blob/create_branch_dropdown.js +++ /dev/null @@ -1,88 +0,0 @@ -class CreateBranchDropdown { - constructor(el, targetBranchDropdown) { - this.targetBranchDropdown = targetBranchDropdown; - this.el = el; - this.dropdownBack = this.el.closest('.dropdown').querySelector('.dropdown-menu-back'); - this.cancelButton = this.el.querySelector('.js-cancel-branch-btn'); - this.newBranchField = this.el.querySelector('#new_branch_name'); - this.newBranchCreateButton = this.el.querySelector('.js-new-branch-btn'); - - this.newBranchCreateButton.setAttribute('disabled', ''); - - this.addBindings(); - this.cleanupWrapper = this.cleanup.bind(this); - document.addEventListener('beforeunload', this.cleanupWrapper); - } - - cleanup() { - this.cleanBindings(); - document.removeEventListener('beforeunload', this.cleanupWrapper); - } - - cleanBindings() { - this.newBranchField.removeEventListener('keyup', this.enableBranchCreateButtonWrapper); - this.newBranchField.removeEventListener('change', this.enableBranchCreateButtonWrapper); - this.newBranchField.removeEventListener('keydown', this.handleNewBranchKeydownWrapper); - this.dropdownBack.removeEventListener('click', this.resetFormWrapper); - this.cancelButton.removeEventListener('click', this.handleCancelClickWrapper); - this.newBranchCreateButton.removeEventListener('click', this.createBranchWrapper); - } - - addBindings() { - this.enableBranchCreateButtonWrapper = this.enableBranchCreateButton.bind(this); - this.handleNewBranchKeydownWrapper = this.handleNewBranchKeydown.bind(this); - this.resetFormWrapper = this.resetForm.bind(this); - this.handleCancelClickWrapper = this.handleCancelClick.bind(this); - this.createBranchWrapper = this.createBranch.bind(this); - - this.newBranchField.addEventListener('keyup', this.enableBranchCreateButtonWrapper); - this.newBranchField.addEventListener('change', this.enableBranchCreateButtonWrapper); - this.newBranchField.addEventListener('keydown', this.handleNewBranchKeydownWrapper); - this.dropdownBack.addEventListener('click', this.resetFormWrapper); - this.cancelButton.addEventListener('click', this.handleCancelClickWrapper); - this.newBranchCreateButton.addEventListener('click', this.createBranchWrapper); - } - - handleCancelClick(e) { - e.preventDefault(); - e.stopPropagation(); - - this.resetForm(); - this.dropdownBack.click(); - } - - handleNewBranchKeydown(e) { - const keyCode = e.which; - const ENTER_KEYCODE = 13; - if (keyCode === ENTER_KEYCODE) { - this.createBranch(e); - } - } - - enableBranchCreateButton() { - if (this.newBranchField.value !== '') { - this.newBranchCreateButton.removeAttribute('disabled'); - } else { - this.newBranchCreateButton.setAttribute('disabled', ''); - } - } - - resetForm() { - this.newBranchField.value = ''; - this.enableBranchCreateButtonWrapper(); - } - - createBranch(e) { - e.preventDefault(); - - if (this.newBranchCreateButton.getAttribute('disabled') === '') { - return; - } - const newBranchName = this.newBranchField.value; - this.targetBranchDropdown.setNewBranch(newBranchName); - this.resetForm(); - } -} - -window.gl = window.gl || {}; -gl.CreateBranchDropdown = CreateBranchDropdown; diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js deleted file mode 100644 index d52d69b1274..00000000000 --- a/app/assets/javascripts/blob/target_branch_dropdown.js +++ /dev/null @@ -1,152 +0,0 @@ -/* eslint-disable class-methods-use-this */ -const SELECT_ITEM_MSG = 'Select'; - -class TargetBranchDropDown { - constructor(dropdown) { - this.dropdown = dropdown; - this.$dropdown = $(dropdown); - this.fieldName = this.dropdown.getAttribute('data-field-name'); - this.form = this.dropdown.closest('form'); - this.createDropdown(); - } - - static bootstrap() { - const dropdowns = document.querySelectorAll('.js-project-branches-dropdown'); - [].forEach.call(dropdowns, dropdown => new TargetBranchDropDown(dropdown)); - } - - createDropdown() { - const self = this; - this.$dropdown.glDropdown({ - selectable: true, - filterable: true, - search: { - fields: ['title'], - }, - data: (term, callback) => $.ajax({ - url: self.dropdown.getAttribute('data-refs-url'), - data: { - ref: self.dropdown.getAttribute('data-ref'), - show_all: true, - }, - dataType: 'json', - }).done(refs => callback(self.dropdownData(refs))), - toggleLabel(item, el) { - if (el.is('.is-active')) { - return item.text; - } - return SELECT_ITEM_MSG; - }, - clicked(options) { - options.e.preventDefault(); - self.onClick.call(self); - }, - fieldName: self.fieldName, - }); - return new gl.CreateBranchDropdown(this.form.querySelector('.dropdown-new-branch'), this); - } - - onClick() { - this.enableSubmit(); - this.$dropdown.trigger('change.branch'); - } - - enableSubmit() { - const submitBtn = this.form.querySelector('[type="submit"]'); - if (this.branchInput && this.branchInput.value) { - submitBtn.removeAttribute('disabled'); - } else { - submitBtn.setAttribute('disabled', ''); - } - } - - dropdownData(refs) { - const branchList = this.dropdownItems(refs); - this.cachedRefs = refs; - this.addDefaultBranch(branchList); - this.addNewBranch(branchList); - return { Branches: branchList }; - } - - dropdownItems(refs) { - return refs.map(this.dropdownItem); - } - - dropdownItem(ref) { - return { id: ref, text: ref, title: ref }; - } - - addDefaultBranch(branchList) { - // when no branch is selected do nothing - if (!this.branchInput) { - return; - } - - const branchInputVal = this.branchInput.value; - const currentBranchIndex = this.searchBranch(branchList, branchInputVal); - - if (currentBranchIndex === -1) { - this.unshiftBranch(branchList, this.dropdownItem(branchInputVal)); - } - } - - addNewBranch(branchList) { - if (this.newBranch) { - this.unshiftBranch(branchList, this.newBranch); - } - } - - searchBranch(branchList, branchName) { - return _.findIndex(branchList, el => branchName === el.id); - } - - unshiftBranch(branchList, branch) { - const branchIndex = this.searchBranch(branchList, branch.id); - - if (branchIndex === -1) { - branchList.unshift(branch); - } - } - - setNewBranch(newBranchName) { - this.newBranch = this.dropdownItem(newBranchName); - this.refreshData(); - this.selectBranch(this.searchBranch(this.glDropdown.fullData.Branches, newBranchName)); - } - - refreshData() { - this.glDropdown.fullData = this.dropdownData(this.cachedRefs); - this.clearFilter(); - } - - clearFilter() { - // apply an empty filter in order to refresh the data - this.glDropdown.filter.filter(''); - this.dropdown.closest('.dropdown').querySelector('.dropdown-page-one .dropdown-input-field').value = ''; - } - - selectBranch(index) { - const branch = this.dropdown.closest('.dropdown').querySelectorAll('li a')[index]; - - if (!branch.classList.contains('is-active')) { - branch.click(); - } else { - this.closeDropdown(); - } - } - - closeDropdown() { - this.dropdown.closest('.dropdown').querySelector('.dropdown-menu-close').click(); - } - - get branchInput() { - return this.form.querySelector(`input[name="${this.fieldName}"]`); - } - - get glDropdown() { - return this.$dropdown.data('glDropdown'); - } -} - -window.gl = window.gl || {}; -gl.TargetBranchDropDown = TargetBranchDropDown; diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 3b2bb6f082f..d80b7f5bd42 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -20,6 +20,7 @@ window.Build = (function () { this.$document = $(document); this.logBytes = 0; this.scrollOffsetPadding = 30; + this.hasBeenScrolled = false; this.updateDropdown = this.updateDropdown.bind(this); this.getBuildTrace = this.getBuildTrace.bind(this); @@ -62,6 +63,15 @@ window.Build = (function () { .off('click') .on('click', this.scrollToBottom.bind(this)); + const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); + + this.$scrollContainer + .off('scroll') + .on('scroll', () => { + this.hasBeenScrolled = true; + scrollThrottled(); + }); + $(window) .off('resize.build') .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100)); @@ -70,25 +80,16 @@ window.Build = (function () { // eslint-disable-next-line this.getBuildTrace() - .then(() => this.makeTraceScrollable()) - .then(() => this.scrollToBottom()); + .then(() => this.toggleScroll()) + .then(() => { + if (!this.hasBeenScrolled) { + this.scrollToBottom(); + } + }); this.verifyTopPosition(); } - Build.prototype.makeTraceScrollable = function () { - this.$scrollContainer.niceScroll({ - cursorcolor: '#fff', - cursoropacitymin: 1, - cursorwidth: '7px', - railpadding: { top: 5, bottom: 5, right: 5 }, - }); - - this.$scrollContainer.on('scroll', _.throttle(this.toggleScroll.bind(this), 100)); - - this.toggleScroll(); - }; - Build.prototype.canScroll = function () { return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height(); }; @@ -104,12 +105,11 @@ window.Build = (function () { * */ Build.prototype.toggleScroll = function () { - const bottomScroll = this.$scrollContainer.scrollTop() + - this.scrollOffsetPadding + - this.$scrollContainer.height(); + const currentPosition = this.$scrollContainer.scrollTop(); + const bottomScroll = currentPosition + this.$scrollContainer.innerHeight(); if (this.canScroll()) { - if (this.$scrollContainer.scrollTop() === 0) { + if (currentPosition === 0) { this.toggleDisableButton(this.$scrollTopBtn, true); this.toggleDisableButton(this.$scrollBottomBtn, false); } else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) { @@ -123,12 +123,14 @@ window.Build = (function () { }; Build.prototype.scrollToTop = function () { - this.$scrollContainer.getNiceScroll(0).doScrollTop(0); + this.hasBeenScrolled = true; + this.$scrollContainer.scrollTop(0); this.toggleScroll(); }; Build.prototype.scrollToBottom = function () { - this.$scrollContainer.getNiceScroll(0).doScrollTo(this.$scrollContainer.prop('scrollHeight')); + this.hasBeenScrolled = true; + this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight')); this.toggleScroll(); }; @@ -216,7 +218,11 @@ window.Build = (function () { Build.timeout = setTimeout(() => { //eslint-disable-next-line this.getBuildTrace() - .then(() => this.scrollToBottom()); + .then(() => { + if (!this.hasBeenScrolled) { + this.scrollToBottom(); + } + }); }, 4000); } else { this.$buildRefreshAnimation.remove(); @@ -253,7 +259,7 @@ window.Build = (function () { this.verifyTopPosition(); - if (this.$scrollContainer.getNiceScroll(0)) { + if (this.canScroll()) { this.toggleScroll(); } }; diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index cb054a2a197..bc3e741f524 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -1,5 +1,6 @@ // ECMAScript polyfills import 'core-js/fn/array/find'; +import 'core-js/fn/array/find-index'; import 'core-js/fn/array/from'; import 'core-js/fn/array/includes'; import 'core-js/fn/object/assign'; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 51cc8c085b2..ca90729c791 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -329,25 +329,14 @@ import initSettingsPanels from './settings_panels'; shortcut_handler = new ShortcutsNavigation(); new TreeView(); new BlobViewer(); - gl.TargetBranchDropDown.bootstrap(); break; case 'projects:find_file:show': shortcut_handler = true; break; - case 'projects:blob:new': - gl.TargetBranchDropDown.bootstrap(); - break; - case 'projects:blob:create': - gl.TargetBranchDropDown.bootstrap(); - break; case 'projects:blob:show': new BlobViewer(); - gl.TargetBranchDropDown.bootstrap(); initBlob(); break; - case 'projects:blob:edit': - gl.TargetBranchDropDown.bootstrap(); - break; case 'projects:blame:show': initBlob(); break; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index fe367d0c42a..ed7629948ca 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -39,10 +39,6 @@ import './shortcuts_network'; // behaviors import './behaviors/'; -// blob -import './blob/create_branch_dropdown'; -import './blob/target_branch_dropdown'; - // templates import './templates/issuable_template_selector'; import './templates/issuable_template_selectors'; diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 658879607e2..04073ef7270 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -1,23 +1,20 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */ (function() { this.NewCommitForm = (function() { - function NewCommitForm(form, targetBranchName = 'target_branch') { + function NewCommitForm(form) { this.form = form; - this.targetBranchName = targetBranchName; this.renderDestination = this.renderDestination.bind(this); - this.targetBranchDropdown = form.find('button.js-target-branch'); + this.branchName = form.find('.js-branch-name'); this.originalBranch = form.find('.js-original-branch'); this.createMergeRequest = form.find('.js-create-merge-request'); this.createMergeRequestContainer = form.find('.js-create-merge-request-container'); - this.targetBranchDropdown.on('change.branch', this.renderDestination); + this.branchName.keyup(this.renderDestination); this.renderDestination(); } NewCommitForm.prototype.renderDestination = function() { var different; - var targetBranch = this.form.find(`input[name="${this.targetBranchName}"]`); - - different = targetBranch.val() !== this.originalBranch.val(); + different = this.branchName.val() !== this.originalBranch.val(); if (different) { this.createMergeRequestContainer.show(); if (!this.wasDifferent) { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index e35558ad8e8..d931a78e112 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -71,7 +71,9 @@ height: 35px; display: flex; justify-content: flex-end; - border-bottom: 1px outset $white-light; + background: $gray-light; + border: 1px solid $gray-normal; + color: $gl-text-color; .truncated-info { margin: 0 auto; @@ -82,7 +84,7 @@ } .raw-link { - color: inherit; + color: $gl-text-color; margin-left: 5px; text-decoration: underline; } @@ -93,17 +95,25 @@ display: flex; align-self: center; font-size: 15px; + margin-bottom: 4px; svg { height: 15px; display: block; - fill: $white-light; + fill: $gl-text-color; } - a, + .controllers-buttons, .btn-scroll { - margin: 0 8px; - color: $white-light; + color: $gl-text-color; + height: 15px; + vertical-align: middle; + padding: 0; + width: 12px; + } + + .controllers-buttons { + margin: 1px 10px; } .btn-scroll.animate { @@ -137,9 +147,9 @@ top: 35px; left: 10px; bottom: 0; - overflow-y: hidden; - padding-bottom: 20px; - padding-right: 20px; + overflow-y: scroll; + overflow-x: hidden; + padding: 10px 20px 20px 5px; } .environment-information { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index a2f781a6a6e..062665bc634 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -769,8 +769,7 @@ pre.light-well { } .project-refs-form .dropdown-menu, -.dropdown-menu-projects, -.dropdown-menu-branches { +.dropdown-menu-projects { width: 300px; @media (min-width: $screen-sm-min) { diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 183eb00ef67..36ad307a93b 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -1,11 +1,6 @@ module CreatesCommit extend ActiveSupport::Concern - def set_start_branch_to_branch_name - branch_exists = @repository.find_branch(@branch_name) - @start_branch = @branch_name if branch_exists - end - def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) if can?(current_user, :push_code, @project) @project_to_commit_into = @project diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb index 3e2a0fe4f8b..b2536a1c949 100644 --- a/app/controllers/concerns/milestone_actions.rb +++ b/app/controllers/concerns/milestone_actions.rb @@ -46,8 +46,10 @@ module MilestoneActions def milestone_redirect_path if @project namespace_project_milestone_path(@project.namespace, @project, @milestone) - else + elsif @group group_milestone_path(@group, @milestone.safe_title, title: @milestone.title) + else + dashboard_milestone_path(@milestone.safe_title, title: @milestone.title) end end end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index df528d10f6e..751dbbd8e96 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -1,6 +1,8 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController + include MilestoneActions + before_action :projects - before_action :milestone, only: [:show] + before_action :milestone, only: [:show, :merge_requests, :participants, :labels] def index respond_to do |format| diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index c585d26df77..11db164b3fa 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -39,7 +39,7 @@ class JwtController < ApplicationController errors: [ { code: 'UNAUTHORIZED', message: "HTTP Basic: Access denied\n" \ - "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ + "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}" } ] }, status: 401 diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index d8d14ea1fed..66e6a9a451c 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -26,8 +26,6 @@ class Projects::BlobController < Projects::ApplicationController end def create - set_start_branch_to_branch_name - create_commit(Files::CreateService, success_notice: "The file has been successfully created.", success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) }, failure_view: :new, diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index d8ed470e461..70b06cfd9b4 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -10,10 +10,10 @@ class Projects::BranchesController < Projects::ApplicationController def index @sort = params[:sort].presence || sort_value_name @branches = BranchesFinder.new(@repository, params).execute + @branches = Kaminari.paginate_array(@branches).page(params[:page]) respond_to do |format| format.html do - paginate_branches @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @max_commits = @branches.reduce(0) do |memo, branch| @@ -22,7 +22,6 @@ class Projects::BranchesController < Projects::ApplicationController end end format.json do - paginate_branches unless params[:show_all] render json: @branches.map(&:name) end end @@ -106,10 +105,6 @@ class Projects::BranchesController < Projects::ApplicationController end end - def paginate_branches - @branches = Kaminari.paginate_array(@branches).page(params[:page]) - end - def url_to_autodeploy_setup(project, branch_name) namespace_project_new_blob_path( project.namespace, diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 7f3205a8001..928f17e6a8e 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -104,7 +104,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController def render_missing_personal_token render plain: "HTTP Basic: Access denied\n" \ - "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ + "You must use a personal access token with 'api' scope for Git over HTTP.\n" \ "You can generate one at #{profile_personal_access_tokens_url}", status: 401 end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index f8eb8e00a5d..266a15c1cf9 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -36,7 +36,6 @@ class Projects::TreeController < Projects::ApplicationController def create_dir return render_404 unless @commit_params.values.all? - set_start_branch_to_branch_name create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)), failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index c515774140c..a230db22fa2 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -121,6 +121,8 @@ module MilestonesHelper merge_requests_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json) elsif @group merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json) + else + merge_requests_dashboard_milestone_path(milestone, title: milestone.title, format: :json) end end @@ -129,6 +131,8 @@ module MilestonesHelper participants_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json) elsif @group participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json) + else + participants_dashboard_milestone_path(milestone, title: milestone.title, format: :json) end end @@ -137,6 +141,8 @@ module MilestonesHelper labels_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json) elsif @group labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json) + else + labels_dashboard_milestone_path(milestone, title: milestone.title, format: :json) end end end diff --git a/app/models/blob.rb b/app/models/blob.rb index 3869e79ba75..954d4e4d779 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -191,9 +191,12 @@ class Blob < SimpleDelegator rendered_as_text? && rich_viewer end + def expanded? + !!@expanded + end + def expand! - simple_viewer&.expanded = true - rich_viewer&.expanded = true + @expanded = true end private diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index d2aa33d994e..35965d01692 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -6,15 +6,15 @@ module BlobViewer self.loading_partial_name = 'loading' - delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class + delegate :partial_path, :loading_partial_path, :rich?, :simple?, :load_async?, :text?, :binary?, to: :class attr_reader :blob - attr_accessor :expanded delegate :project, to: :blob def initialize(blob) @blob = blob + @initially_binary = blob.binary? end def self.partial_path @@ -57,14 +57,10 @@ module BlobViewer false end - def load_async? - self.class.load_async? && render_error.nil? - end - def collapsed? return @collapsed if defined?(@collapsed) - @collapsed = !expanded && collapse_limit && blob.raw_size > collapse_limit + @collapsed = !blob.expanded? && collapse_limit && blob.raw_size > collapse_limit end def too_large? @@ -73,6 +69,10 @@ module BlobViewer @too_large = size_limit && blob.raw_size > size_limit end + def binary_detected_after_load? + !@initially_binary && blob.binary? + end + # This method is used on the server side to check whether we can attempt to # render the blob at all. Human-readable error messages are found in the # `BlobHelper#blob_render_error_reason` helper. diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 0db9e31031c..8bf35953d29 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -110,12 +110,24 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated end def closing_issues_links - markdown issues_sentence(project, closing_issues), pipeline: :gfm, author: author, project: project + markdown( + issues_sentence(project, closing_issues), + pipeline: :gfm, + author: author, + project: project, + issuable_state_filter_enabled: true + ) end def mentioned_issues_links mentioned_issues = issues_mentioned_but_not_closing(current_user) - markdown issues_sentence(project, mentioned_issues), pipeline: :gfm, author: author, project: project + markdown( + issues_sentence(project, mentioned_issues), + pipeline: :gfm, + author: author, + project: project, + issuable_state_filter_enabled: true + ) end def assign_to_closing_issues_link diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index b20e3a22133..31d0e589c26 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -4,7 +4,7 @@ Community Edition - if user_signed_in? %span= Gitlab::VERSION - %small= Gitlab::REVISION + %small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION) = version_status_badge %p.slead GitLab is open source software to collaborate on code. diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index db6662a95ac..c8ca0406213 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -6,7 +6,7 @@ %h3.page-title Delete #{@blob.name} .modal-body - = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-quick-submit js-requires-input' do + = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}" .form-group @@ -15,4 +15,4 @@ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" :javascript - new NewCommitForm($('.js-replace-blob-form')) + new NewCommitForm($('.js-delete-blob-form')) diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml index 4252f27d007..013f1c267c8 100644 --- a/app/views/projects/blob/_viewer.html.haml +++ b/app/views/projects/blob/_viewer.html.haml @@ -1,13 +1,19 @@ - hidden = local_assigns.fetch(:hidden, false) - render_error = viewer.render_error -- load_async = local_assigns.fetch(:load_async, viewer.load_async?) +- load_async = local_assigns.fetch(:load_async, viewer.load_async? && render_error.nil?) - viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async .blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) } - - if load_async - = render viewer.loading_partial_path, viewer: viewer - - elsif render_error + - if render_error = render 'projects/blob/render_error', viewer: viewer + - elsif load_async + = render viewer.loading_partial_path, viewer: viewer - else - viewer.prepare! + + -# In the rare case where the first kilobyte of the file looks like text, + -# but the file turns out to actually be binary after loading all data, + -# we fall back on the binary Download viewer. + - viewer = BlobViewer::Download.new(viewer.blob) if viewer.binary_detected_after_load? + = render viewer.partial_path, viewer: viewer diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index b5f67cae341..281d823da52 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -18,14 +18,13 @@ = label_tag 'start_branch', branch_label, class: 'control-label' .col-sm-10 = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch' - = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) + = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) - if can?(current_user, :push_code, @project) - .js-create-merge-request-container - .checkbox - = label_tag do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil - Start a <strong>new merge request</strong> with these changes + .checkbox + = label_tag do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil + Start a <strong>new merge request</strong> with these changes - else = hidden_field_tag 'create_merge_request', 1, id: nil .form-actions @@ -35,6 +34,3 @@ - unless can?(current_user, :push_code, @project) .inline.prepend-left-10 = commit_in_fork_help - -:javascript - new NewCommitForm($('.js-#{type}-form'), 'start_branch') diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 0d10dfcef70..987068dc18e 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -66,29 +66,24 @@ .controllers - if @build.has_trace? = link_to raw_namespace_project_job_path(@project.namespace, @project, @build), - title: 'Open raw trace', + title: 'Show complete raw', data: { placement: 'top', container: 'body' }, - class: 'js-raw-link-controller has-tooltip' do - = icon('download') + class: 'js-raw-link-controller has-tooltip controllers-buttons' do + = icon('file-text-o') - if can?(current_user, :update_build, @project) && @build.erasable? = link_to erase_namespace_project_job_path(@project.namespace, @project, @build), method: :post, data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, - title: 'Erase Build', - class: 'has-tooltip js-erase-link' do + title: 'Erase job log', + class: 'has-tooltip js-erase-link controllers-buttons' do = icon('trash') - - %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank.has-tooltip{ type: 'button', - disabled: true, - title: 'Scroll Up', - data: { placement: 'top', container: 'body'} } - = custom_icon('scroll_up') - %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank.has-tooltip{ type: 'button', - disabled: true, - title: 'Scroll Down', - data: { placement: 'top', container: 'body'} } - = custom_icon('scroll_down') + .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} } + %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } + = custom_icon('scroll_up') + .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } + %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } + = custom_icon('scroll_down') .bash.sticky.js-scroll-container %code.js-build-output .build-loader-animation.js-build-refresh diff --git a/app/views/shared/_branch_switcher.html.haml b/app/views/shared/_branch_switcher.html.haml deleted file mode 100644 index 69e3f3042a9..00000000000 --- a/app/views/shared/_branch_switcher.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -- dropdown_toggle_text = @branch_name || tree_edit_branch -= hidden_field_tag 'branch_name', dropdown_toggle_text - -.dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: 'dropdown', selected: dropdown_toggle_text, field_name: 'branch_name', form_id: '.js-edit-blob-form', refs_url: namespace_project_branches_path(@project.namespace, @project) }, { toggle_class: 'js-project-branches-dropdown js-target-branch' } - .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging.dropdown-menu-branches - = render partial: 'shared/projects/blob/branch_page_default' - = render partial: 'shared/projects/blob/branch_page_create' diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml index 0b37fe3013b..25a56f84ec5 100644 --- a/app/views/shared/_new_commit_form.html.haml +++ b/app/views/shared/_new_commit_form.html.haml @@ -7,7 +7,7 @@ .form-group.branch = label_tag 'branch_name', 'Target branch', class: 'control-label' .col-sm-10 - = render 'shared/branch_switcher' + = text_field_tag 'branch_name', @branch_name || tree_edit_branch, required: true, class: "form-control js-branch-name ref-name" .js-create-merge-request-container .checkbox diff --git a/app/views/shared/projects/blob/_branch_page_create.html.haml b/app/views/shared/projects/blob/_branch_page_create.html.haml deleted file mode 100644 index c279a0d8846..00000000000 --- a/app/views/shared/projects/blob/_branch_page_create.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.dropdown-page-two.dropdown-new-branch - = dropdown_title('Create new branch', back: true) - = dropdown_content do - %input#new_branch_name.default-dropdown-input.append-bottom-10{ type: "text", placeholder: "Name new branch" } - %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" } - Create - %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" } - Cancel diff --git a/app/views/shared/projects/blob/_branch_page_default.html.haml b/app/views/shared/projects/blob/_branch_page_default.html.haml deleted file mode 100644 index 9bf78d10878..00000000000 --- a/app/views/shared/projects/blob/_branch_page_default.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -.dropdown-page-one - = dropdown_title "Select branch" - = dropdown_filter "Search branches" - = dropdown_content - = dropdown_loading - = dropdown_footer do - %ul.dropdown-footer-list - %li - %a.create-new-branch.dropdown-toggle-page{ href: "#" } - Create new branch diff --git a/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml b/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml new file mode 100644 index 00000000000..4a7b02fec94 --- /dev/null +++ b/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml @@ -0,0 +1,4 @@ +--- +title: Display issue state in issue links section of merge request widget +merge_request: 12021 +author: diff --git a/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml b/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml new file mode 100644 index 00000000000..357a623e0e8 --- /dev/null +++ b/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml @@ -0,0 +1,4 @@ +--- +title: Fixed dashboard milestone tabs not loading +merge_request: +author: diff --git a/changelogs/unreleased/dm-blob-binaryness-change.yml b/changelogs/unreleased/dm-blob-binaryness-change.yml new file mode 100644 index 00000000000..f3e3af26f12 --- /dev/null +++ b/changelogs/unreleased/dm-blob-binaryness-change.yml @@ -0,0 +1,5 @@ +--- +title: Detect if file that appears to be text in the first 1024 bytes is actually + binary afer loading all data +merge_request: +author: diff --git a/changelogs/unreleased/dm-revert-mr-8427.yml b/changelogs/unreleased/dm-revert-mr-8427.yml new file mode 100644 index 00000000000..a91cff2e9cd --- /dev/null +++ b/changelogs/unreleased/dm-revert-mr-8427.yml @@ -0,0 +1,4 @@ +--- +title: Revert 'New file from interface on existing branch' +merge_request: +author: diff --git a/changelogs/unreleased/pat-msg-on-auth-failure.yml b/changelogs/unreleased/pat-msg-on-auth-failure.yml new file mode 100644 index 00000000000..c1b1528bb7a --- /dev/null +++ b/changelogs/unreleased/pat-msg-on-auth-failure.yml @@ -0,0 +1,4 @@ +--- +title: Instruct user to use personal access token for Git over HTTP +merge_request: 11986 +author: Robin Bobbitt diff --git a/changelogs/unreleased/tc-link-to-commit-on-help-page.yml b/changelogs/unreleased/tc-link-to-commit-on-help-page.yml new file mode 100644 index 00000000000..3d11ba43d1f --- /dev/null +++ b/changelogs/unreleased/tc-link-to-commit-on-help-page.yml @@ -0,0 +1,4 @@ +--- +title: Make the revision on the `/help` page clickable +merge_request: 12016 +author: diff --git a/config/boot.rb b/config/boot.rb index f2830ae3166..17a71148370 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -4,3 +4,15 @@ require 'rubygems' ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +# Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage +require 'bootsnap' +Bootsnap.setup( + cache_dir: 'tmp/cache', + development_mode: ENV['RAILS_ENV'] == 'development', + load_path_cache: true, + autoload_paths_cache: true, + disable_trace: false, + compile_cache_iseq: true, + compile_cache_yaml: true +) diff --git a/config/locales/de.yml b/config/locales/de.yml index 533663a2704..38c3711c6c7 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -62,6 +62,43 @@ de: - :month - :year datetime: + # used in a custom scope that has been created to fix https://gitlab.com/gitlab-org/gitlab-ce/issues/32747 + time_ago_in_words: + half_a_minute: vor einer halben Minute + less_than_x_seconds: + one: vor weniger als einer Sekunde + other: "vor weniger als %{count} Sekunden" + x_seconds: + one: vor einer Sekunde + other: "vor %{count} Sekunden" + less_than_x_minutes: + one: vor weniger als einer Minute + other: vor weniger als %{count} Minuten + x_minutes: + one: vor einer Minute + other: "vor %{count} Minuten" + about_x_hours: + one: vor etwa einer Stunde + other: "vor etwa %{count} Stunden" + x_days: + one: vor einem Tag + other: "vor %{count} Tagen" + about_x_months: + one: vor etwa einem Monat + other: "vor etwa %{count} Monaten" + x_months: + one: vor einem Monat + other: "vor %{count} Monaten" + about_x_years: + one: vor etwa einem Jahr + other: "vor etwa %{count} Jahren" + over_x_years: + one: vor mehr als einem Jahr + other: "vor mehr als %{count} Jahren" + almost_x_years: + one: vor fast einem Jahr + other: "vor fast %{count} Jahren" + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() distance_in_words: about_x_hours: one: etwa eine Stunde diff --git a/config/locales/es.yml b/config/locales/es.yml index 0f9dc39535d..d71c6eb5047 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -61,6 +61,7 @@ es: - :month - :year datetime: + # used in a custom scope that has been created to fix https://gitlab.com/gitlab-org/gitlab-ce/issues/32747 time_ago_in_words: half_a_minute: "hace medio minuto" less_than_x_seconds: @@ -96,6 +97,7 @@ es: almost_x_years: one: "hace casi 1 año" other: "hace casi %{count} años" + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() distance_in_words: about_x_hours: one: alrededor de 1 hora diff --git a/config/routes/dashboard.rb b/config/routes/dashboard.rb index 8e380a0b0ac..d2437285cdf 100644 --- a/config/routes/dashboard.rb +++ b/config/routes/dashboard.rb @@ -4,7 +4,13 @@ resource :dashboard, controller: 'dashboard', only: [] do get :activity scope module: :dashboard do - resources :milestones, only: [:index, :show] + resources :milestones, only: [:index, :show] do + member do + get :merge_requests + get :participants + get :labels + end + end resources :labels, only: [:index] resources :groups, only: [:index] diff --git a/doc/api/projects.md b/doc/api/projects.md index bf21aa0e179..716486022b0 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -2,10 +2,10 @@ ### Project visibility level -Project in GitLab has be either private, internal or public. -You can determine it by `visibility` field in project. +Project in GitLab can be either private, internal or public. +This is determined by the `visibility` field in the project. -Constants for project visibility levels are next: +Values for the project visibility level are: * `private`: Project access must be granted explicitly for each user. @@ -18,7 +18,7 @@ Constants for project visibility levels are next: ## List projects -Get a list of visible projects for authenticated user. When being accessed without authentication, all public projects are returned. +Get a list of visible projects for authenticated user. When accessed without authentication, only public projects are returned. ``` GET /projects diff --git a/doc/integration/google.md b/doc/integration/google.md index 1e7ad90c5a8..d5b523e6dc0 100644 --- a/doc/integration/google.md +++ b/doc/integration/google.md @@ -72,6 +72,21 @@ To enable the Google OAuth2 OmniAuth provider you must register your application 1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10. +1. Make sure that you configure GitLab to use an FQDN as Google will not accept raw IP addresses. + + For Omnibus packages: + + ```ruby + external_url 'https://gitlab.example.com' + ``` + + For installations from source: + + ```yaml + gitlab: + host: https://gitlab.example.com + ``` + 1. Save the configuration file. 1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md index 591d1524061..9544de41b9a 100644 --- a/doc/university/glossary/README.md +++ b/doc/university/glossary/README.md @@ -1,4 +1,3 @@ - ## What is the Glossary This contains a simplified list and definitions of some of the terms that you will encounter in your day to day activities when working with GitLab. @@ -10,7 +9,7 @@ User authentication by combination of 2 different steps during login. This allow ### Access Levels -Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](../../permissions/permissions.md +Process of selective restriction to create, view, modify or delete a resource based on a set of assigned permissions. See [GitLab's Permission Guidelines](../../user/permissions.md) ### Active Directory (AD) diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md index 10c281448a3..75ea911b9bc 100644 --- a/doc/user/project/container_registry.md +++ b/doc/user/project/container_registry.md @@ -39,6 +39,14 @@ You can read more about Docker Registry at https://docs.docker.com/registry/intr ## Build and push images +>**Notes:** +- Moving or renaming existing container registry repositories is not supported +once you have pushed images because the images are signed, and the +signature includes the repository name. +- To move or rename a repository with a container registry you will have to +delete all existing images. + + If you visit the **Registry** link under your project's menu, you can see the explicit instructions to login to the Container Registry using your GitLab credentials. diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 1cb3c940f00..1645e7e8d65 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -23,9 +23,10 @@ You can use the 'New project' button to add a project to the new group. ## Transferring an existing project into a group -You can transfer an existing project into a group you own from the project settings page. The option to transfer a project is only available if you are the Owner of the project. +You can transfer an existing project into a group you have at least Master access in from the project settings page. +The option to transfer a project is only available if you are the Owner of the project. First scroll down to the 'Dangerous settings' and click 'Show them to me'. -Now you can pick any of the groups you manage as the new namespace for the group. +Now you can pick any of the groups you have at least Master access in as the new namespace for the group. ![Transfer a project to a new namespace](groups/transfer_project.png) diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index d099d7af167..80aa3a047a0 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -89,10 +89,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I fill the new branch name' do - first('button.js-target-branch', visible: true).click - find('.create-new-branch', visible: true).click - find('#new_branch_name', visible: true).set('new_branch_name') - find('.js-new-branch-btn', visible: true).click + fill_in :branch_name, with: 'new_branch_name', visible: true end step 'I fill the new file name with an illegal name' do diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index 0fee158d590..cf31e61437e 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -90,6 +90,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps click_link "api" end + wait_for_requests + page.within '.tree-table' do click_link "README.md" end diff --git a/lib/gitlab.rb b/lib/gitlab.rb index c3064163e07..11f7c8b9510 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -1,9 +1,11 @@ require_dependency 'gitlab/git' module Gitlab + COM_URL = 'https://gitlab.com'.freeze + def self.com? # Check `staging?` as well to keep parity with gitlab.com - Gitlab.config.gitlab.url == 'https://gitlab.com' || staging? + Gitlab.config.gitlab.url == COM_URL || staging? end def self.staging? diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index da07ba2f2a3..3933c3b04dd 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -37,7 +37,11 @@ module Gitlab rate_limit!(ip, success: result.success?, login: login) Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor) - result + return result if result.success? || current_application_settings.signin_enabled? || Gitlab::LDAP::Config.enabled? + + # If sign-in is disabled and LDAP is not configured, recommend a + # personal access token on failed auth attempts + raise Gitlab::Auth::MissingPersonalTokenError end def find_with_user_password(login, password) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index d60e607b02b..33a7624e303 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -123,6 +123,7 @@ module Gitlab @loaded_all_data = true @data = repository.lookup(id).content @loaded_size = @data.bytesize + @binary = nil end def name diff --git a/scripts/trigger-build b/scripts/trigger-build index e4603533872..dcda70d7ed8 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -9,7 +9,7 @@ params = { "token" => ENV["BUILD_TRIGGER_TOKEN"], "variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"], "variables[ALTERNATIVE_SOURCES]" => true, - "variables[ee]" => ENV["EE_PACKAGE"] + "variables[ee]" => ENV["EE_PACKAGE"] || "false" } Dir.glob("*_VERSION").each do |version_file| @@ -19,4 +19,9 @@ end res = Net::HTTP.post_form(uri, params) pipeline_id = JSON.parse(res.body)['id'] -puts "Triggered pipeline can be found at https://gitlab.com/gitlab-org/omnibus-gitlab/pipelines/#{pipeline_id}" +unless pipeline_id.nil? + puts "Triggered pipeline can be found at https://gitlab.com/gitlab-org/omnibus-gitlab/pipelines/#{pipeline_id}" +else + puts "Trigger failed. The response from trigger is: " + puts res.body +end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb new file mode 100644 index 00000000000..424f39fd3b8 --- /dev/null +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe Dashboard::MilestonesController do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let(:project_milestone) { create(:milestone, project: project) } + let(:milestone) do + DashboardMilestone.build( + [project], + project_milestone.title + ) + end + let(:issue) { create(:issue, project: project, milestone: project_milestone) } + let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: project_milestone) } + let(:milestone_path) { dashboard_milestone_path(milestone.safe_title, title: milestone.title) } + + before do + sign_in(user) + project.team << [user, :master] + end + + it_behaves_like 'milestone tabs' + + describe "#show" do + render_views + + def view_milestone + get :show, id: milestone.safe_title, title: milestone.title + end + + it 'shows milestone page' do + view_milestone + + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index f285e5333d6..f9e21f9d8f6 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -367,19 +367,5 @@ describe Projects::BranchesController do expect(parsed_response.first).to eq 'master' end end - - context 'show_all = true' do - it 'returns all the branches name' do - get :index, - namespace_id: project.namespace, - project_id: project, - format: :json, - show_all: true - - parsed_response = JSON.parse(response.body) - - expect(parsed_response.length).to eq(project.repository.branches.count) - end - end end end diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb new file mode 100644 index 00000000000..0c7b992c500 --- /dev/null +++ b/spec/features/dashboard/milestone_tabs_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'Dashboard milestone tabs', :js, :feature do + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let!(:label) { create(:label, project: project) } + let(:project_milestone) { create(:milestone, project: project) } + let(:milestone) do + DashboardMilestone.build( + [project], + project_milestone.title + ) + end + let!(:merge_request) { create(:labeled_merge_request, source_project: project, target_project: project, milestone: project_milestone, labels: [label]) } + + before do + project.add_master(user) + login_as(user) + + visit dashboard_milestone_path(milestone.safe_title, title: milestone.title) + end + + it 'loads merge requests async' do + click_link 'Merge Requests' + + expect(page).to have_selector('.merge_requests-sortable-list') + end + + it 'loads participants async' do + click_link 'Participants' + + expect(page).to have_selector('#tab-participants .bordered-list') + end + + it 'loads labels async' do + click_link 'Labels' + + expect(page).to have_selector('#tab-labels .bordered-list') + end +end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 82cfbfda157..45fdb36e506 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' feature 'File blob', :js, feature: true do let(:project) { create(:project, :public) } - def visit_blob(path, fragment = nil) - visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment) + def visit_blob(path, anchor: nil, ref: 'master') + visit namespace_project_blob_path(project.namespace, project, File.join(ref, path), anchor: anchor) wait_for_requests end @@ -101,7 +101,7 @@ feature 'File blob', :js, feature: true do context 'visiting with a line number anchor' do before do - visit_blob('files/markdown/ruby-style-guide.md', 'L1') + visit_blob('files/markdown/ruby-style-guide.md', anchor: 'L1') end it 'displays the blob using the simple viewer' do @@ -352,6 +352,37 @@ feature 'File blob', :js, feature: true do end end + context 'binary file that appears to be text in the first 1024 bytes' do + before do + visit_blob('encoding/binary-1.bin', ref: 'binary-encoding') + end + + it 'displays the blob' do + aggregate_failures do + # shows a download link + expect(page).to have_link('Download (23.8 KB)') + + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') + + # The specs below verify an arguably incorrect result, but since we only + # learn that the file is not actually text once the text viewer content + # is loaded asynchronously, there is no straightforward way to get these + # synchronously loaded elements to display correctly. + # + # Clicking the copy button will result in nothing being copied. + # Clicking the raw button will result in the binary file being downloaded, + # as expected. + + # shows an enabled copy button, incorrectly + expect(page).to have_selector('.js-copy-blob-source-btn:not(.disabled)') + + # shows a raw button, incorrectly + expect(page).to have_link('Open raw') + end + end + end + context '.gitlab-ci.yml' do before do project.add_master(project.creator) diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index 1a38997450d..d04c3248ead 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -102,7 +102,7 @@ feature 'Editing file blob', feature: true, js: true do it 'shows blob editor with same branch' do expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))) - expect(find('.js-target-branch .dropdown-toggle-text').text).to eq(branch) + expect(find('.js-branch-name').value).to eq(branch) end end @@ -112,7 +112,7 @@ feature 'Editing file blob', feature: true, js: true do end it 'shows blob editor with patch branch' do - expect(find('.js-target-branch .dropdown-toggle-text').text).to eq('patch-1') + expect(find('.js-branch-name').value).to eq('patch-1') end end end @@ -128,7 +128,7 @@ feature 'Editing file blob', feature: true, js: true do it 'shows blob editor with same branch' do expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))) - expect(find('.js-target-branch .dropdown-toggle-text').text).to eq(branch) + expect(find('.js-branch-name').value).to eq(branch) end end end diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb deleted file mode 100644 index 4b6c55f5f44..00000000000 --- a/spec/features/projects/blobs/user_create_spec.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'spec_helper' - -feature 'New blob creation', feature: true, js: true do - include TargetBranchHelpers - - given(:user) { create(:user) } - given(:role) { :developer } - given(:project) { create(:project) } - given(:content) { 'class NextFeature\nend\n' } - - background do - login_as(user) - project.team << [user, role] - visit namespace_project_new_blob_path(project.namespace, project, 'master') - end - - def edit_file - wait_for_requests - fill_in 'file_name', with: 'feature.rb' - execute_script("ace.edit('editor').setValue('#{content}')") - end - - def commit_file - click_button 'Commit changes' - end - - context 'with default target branch' do - background do - edit_file - commit_file - end - - scenario 'creates the blob in the default branch' do - expect(page).to have_content 'master' - expect(page).to have_content 'successfully created' - expect(page).to have_content 'NextFeature' - end - end - - context 'with different target branch' do - background do - edit_file - select_branch('feature') - commit_file - end - - scenario 'creates the blob in the different branch' do - expect(page).to have_content 'feature' - expect(page).to have_content 'successfully created' - end - end - - context 'with a new target branch' do - given(:new_branch_name) { 'new-feature' } - - background do - edit_file - create_new_branch(new_branch_name) - commit_file - end - - scenario 'creates the blob in the new branch' do - expect(page).to have_content new_branch_name - expect(page).to have_content 'successfully created' - end - scenario 'returns you to the mr' do - expect(page).to have_content 'New Merge Request' - expect(page).to have_content "From #{new_branch_name} into master" - expect(page).to have_content 'Add new file' - end - end - - context 'the file already exist in the source branch' do - background do - Files::CreateService.new( - project, - user, - start_branch: 'master', - branch_name: 'master', - commit_message: 'Create file', - file_path: 'feature.rb', - file_content: content - ).execute - edit_file - commit_file - end - - scenario 'shows error message' do - expect(page).to have_content('A file with this name already exists') - expect(page).to have_content('New file') - expect(page).to have_content('NextFeature') - end - end -end diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb index 5dfdc465d7d..aeb7e0b7c33 100644 --- a/spec/features/projects/user_create_dir_spec.rb +++ b/spec/features/projects/user_create_dir_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' feature 'New directory creation', feature: true, js: true do - include TargetBranchHelpers - given(:user) { create(:user) } given(:role) { :developer } given(:project) { create(:project) } @@ -36,23 +34,11 @@ feature 'New directory creation', feature: true, js: true do end end - context 'with different target branch' do - background do - select_branch('feature') - create_directory - end - - scenario 'creates the directory in the different branch' do - expect(page).to have_content 'feature' - expect(page).to have_content 'The directory has been successfully created' - end - end - context 'with a new target branch' do given(:new_branch_name) { 'new-feature' } background do - create_new_branch(new_branch_name) + fill_in :branch_name, with: new_branch_name create_directory end diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index 49d7ef09e64..94f6bb16730 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -14,11 +14,12 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t background do project.team << [user, :master] + WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute + login_as(user) visit namespace_project_path(project.namespace, project) find('.shortcuts-wiki').trigger('click') - WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute end context "while creating a new wiki page" do diff --git a/spec/javascripts/blob/create_branch_dropdown_spec.js b/spec/javascripts/blob/create_branch_dropdown_spec.js deleted file mode 100644 index 6dbaa47c544..00000000000 --- a/spec/javascripts/blob/create_branch_dropdown_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import '~/gl_dropdown'; -import '~/blob/create_branch_dropdown'; -import '~/blob/target_branch_dropdown'; - -describe('CreateBranchDropdown', () => { - const fixtureTemplate = 'static/target_branch_dropdown.html.raw'; - // selectors - const createBranchSel = '.js-new-branch-btn'; - const backBtnSel = '.dropdown-menu-back'; - const cancelBtnSel = '.js-cancel-branch-btn'; - const branchNameSel = '#new_branch_name'; - const branchName = 'new_name'; - let dropdown; - - function createDropdown() { - const dropdownEl = document.querySelector('.js-project-branches-dropdown'); - const projectBranches = getJSONFixture('project_branches.json'); - dropdown = new gl.TargetBranchDropDown(dropdownEl); - dropdown.cachedRefs = projectBranches; - return dropdown; - } - - function createBranchBtn() { - return document.querySelector(createBranchSel); - } - - function backBtn() { - return document.querySelector(backBtnSel); - } - - function cancelBtn() { - return document.querySelector(cancelBtnSel); - } - - function branchNameEl() { - return document.querySelector(branchNameSel); - } - - function changeBranchName(text) { - branchNameEl().value = text; - branchNameEl().dispatchEvent(new Event('change')); - } - - preloadFixtures(fixtureTemplate); - - beforeEach(() => { - loadFixtures(fixtureTemplate); - createDropdown(); - }); - - it('disable submit when branch name is empty', () => { - expect(createBranchBtn()).toBeDisabled(); - }); - - it('enable submit when branch name is present', () => { - changeBranchName(branchName); - - expect(createBranchBtn()).not.toBeDisabled(); - }); - - it('resets the form when cancel btn is clicked and triggers dropdownback', () => { - const spyBackEvent = spyOnEvent(backBtnSel, 'click'); - changeBranchName(branchName); - - cancelBtn().click(); - - expect(branchNameEl()).toHaveValue(''); - expect(spyBackEvent).toHaveBeenTriggered(); - }); - - it('resets the form when back btn is clicked', () => { - changeBranchName(branchName); - - backBtn().click(); - - expect(branchNameEl()).toHaveValue(''); - }); - - describe('new branch creation', () => { - beforeEach(() => { - changeBranchName(branchName); - }); - it('sets the new branch name and updates the dropdown', () => { - spyOn(dropdown, 'setNewBranch'); - - createBranchBtn().click(); - - expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName); - }); - - it('resets the form', () => { - createBranchBtn().click(); - - expect(branchNameEl()).toHaveValue(''); - }); - - it('is triggered with enter keypress', () => { - spyOn(dropdown, 'setNewBranch'); - const enterEvent = new Event('keydown'); - enterEvent.which = 13; - branchNameEl().dispatchEvent(enterEvent); - - expect(dropdown.setNewBranch).toHaveBeenCalledWith(branchName); - }); - }); -}); diff --git a/spec/javascripts/blob/target_branch_dropdown_spec.js b/spec/javascripts/blob/target_branch_dropdown_spec.js deleted file mode 100644 index 99c9537d2ec..00000000000 --- a/spec/javascripts/blob/target_branch_dropdown_spec.js +++ /dev/null @@ -1,118 +0,0 @@ -import '~/gl_dropdown'; -import '~/blob/create_branch_dropdown'; -import '~/blob/target_branch_dropdown'; - -describe('TargetBranchDropdown', () => { - const fixtureTemplate = 'static/target_branch_dropdown.html.raw'; - let dropdown; - - function createDropdown() { - const projectBranches = getJSONFixture('project_branches.json'); - const dropdownEl = document.querySelector('.js-project-branches-dropdown'); - dropdown = new gl.TargetBranchDropDown(dropdownEl); - dropdown.cachedRefs = projectBranches; - dropdown.refreshData(); - return dropdown; - } - - function submitBtn() { - return document.querySelector('button[type="submit"]'); - } - - function searchField() { - return document.querySelector('.dropdown-page-one .dropdown-input-field'); - } - - function element() { - return document.querySelectorAll('div.dropdown-content li a'); - } - - function elementAtIndex(index) { - return element()[index]; - } - - function clickElementAtIndex(index) { - elementAtIndex(index).click(); - } - - preloadFixtures(fixtureTemplate); - - beforeEach(() => { - loadFixtures(fixtureTemplate); - createDropdown(); - }); - - it('disable submit when branch is not selected', () => { - document.querySelector('input[name="target_branch"]').value = null; - clickElementAtIndex(1); - - expect(submitBtn().getAttribute('disabled')).toEqual(''); - }); - - it('enable submit when a branch is selected', () => { - clickElementAtIndex(1); - - expect(submitBtn().getAttribute('disabled')).toBe(null); - }); - - it('triggers change.branch event on a branch click', () => { - spyOnEvent(dropdown.$dropdown, 'change.branch'); - clickElementAtIndex(0); - - expect('change.branch').toHaveBeenTriggeredOn(dropdown.$dropdown); - }); - - describe('dropdownData', () => { - it('cache the refs', () => { - const refs = dropdown.cachedRefs; - dropdown.cachedRefs = null; - - dropdown.dropdownData(refs); - - expect(dropdown.cachedRefs).toEqual(refs); - }); - - it('returns the Branches with the newBranch and defaultBranch', () => { - const refs = dropdown.cachedRefs; - dropdown.branchInput.value = 'master'; - dropdown.newBranch = { id: 'new_branch', text: 'new_branch', title: 'new_branch' }; - - const branches = dropdown.dropdownData(refs).Branches; - - expect(branches.length).toEqual(4); - expect(branches[0]).toEqual(dropdown.newBranch); - expect(branches[1]).toEqual({ id: 'master', text: 'master', title: 'master' }); - expect(branches[2]).toEqual({ id: 'development', text: 'development', title: 'development' }); - expect(branches[3]).toEqual({ id: 'staging', text: 'staging', title: 'staging' }); - }); - }); - - describe('setNewBranch', () => { - it('adds the new branch and select it', () => { - const branchName = 'new_branch'; - - dropdown.setNewBranch(branchName); - - expect(elementAtIndex(0)).toHaveClass('is-active'); - expect(elementAtIndex(0)).toContainHtml(branchName); - }); - - it("doesn't add a new branch if already exists in the list", () => { - const branchName = elementAtIndex(0).text; - const initialLength = element().length; - - dropdown.setNewBranch(branchName); - - expect(element().length).toEqual(initialLength); - }); - - it('clears the search filter', () => { - const branchName = elementAtIndex(0).text; - searchField().value = 'searching'; - - dropdown.setNewBranch(branchName); - - expect(searchField().value).toEqual(''); - }); - }); -}); diff --git a/spec/javascripts/fixtures/project_branches.json b/spec/javascripts/fixtures/project_branches.json deleted file mode 100644 index a96a4c0c095..00000000000 --- a/spec/javascripts/fixtures/project_branches.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "master", - "development", - "staging" -] diff --git a/spec/javascripts/fixtures/target_branch_dropdown.html.haml b/spec/javascripts/fixtures/target_branch_dropdown.html.haml deleted file mode 100644 index 821fb7940a0..00000000000 --- a/spec/javascripts/fixtures/target_branch_dropdown.html.haml +++ /dev/null @@ -1,28 +0,0 @@ -%form.js-edit-blob-form - %input{type: 'hidden', name: 'target_branch', value: 'master'} - %div - .dropdown - %button.dropdown-menu-toggle.js-project-branches-dropdown.js-target-branch{type: 'button', data: {toggle: 'dropdown', selected: 'master', field_name: 'target_branch', form_id: '.js-edit-blob-form'}} - .dropdown-menu.dropdown-menu-selectable.dropdown-menu-paging - .dropdown-page-one - .dropdown-title 'Select branch' - .dropdown-input - %input.dropdown-input-field{type: 'search', value: ''} - %i.fa.fa-search.dropdown-input-search - %i.fa.fa-times-dropdown-input-clear.js-dropdown-input-clear{role: 'button'} - .dropdown-content - .dropdown-footer - %ul.dropdown-footer-list - %li - %a.create-new-branch.dropdown-toggle-page{href: "#"} - Create new branch - .dropdown-page-two.dropdown-new-branch - %button.dropdown-title-button.dropdown-menu-back{type: 'button'} - .dropdown_title 'Create new branch' - .dropdown_content - %input#new_branch_name.default-dropdown-input{ type: "text", placeholder: "Name new branch" } - %button.btn.btn-primary.pull-left.js-new-branch-btn{ type: "button" } - Create - %button.btn.btn-default.pull-right.js-cancel-branch-btn{ type: "button" } - Cancel - %button{type: 'submit'} diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index d6006eab0c9..d09da951869 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -204,6 +204,12 @@ describe Gitlab::Auth, lib: true do expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: login) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new) end + + it 'throws an error suggesting user create a PAT when internal auth is disabled' do + allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + + expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalTokenError) + end end describe 'find_with_user_password' do diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb index d56379eb59d..574438838d8 100644 --- a/spec/models/blob_viewer/base_spec.rb +++ b/spec/models/blob_viewer/base_spec.rb @@ -106,9 +106,9 @@ describe BlobViewer::Base, model: true do end describe '#render_error' do - context 'when expanded' do + context 'when the blob is expanded' do before do - viewer.expanded = true + blob.expand! end context 'when the blob size is larger than the size limit' do diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 44720fc4448..f5a14b1d04d 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -132,6 +132,11 @@ describe MergeRequestPresenter do it 'does not present related issues links' do is_expected.not_to match("#{project.full_path}/issues/#{issue_b.iid}") end + + it 'appends status when closing issue is already closed' do + issue_a.close + is_expected.to match('(closed)') + end end describe '#mentioned_issues_links' do @@ -147,6 +152,11 @@ describe MergeRequestPresenter do it 'does not present closing issues links' do is_expected.not_to match("#{project.full_path}/issues/#{issue_a.iid}") end + + it 'appends status when mentioned issue is already closed' do + issue_b.close + is_expected.to match('(closed)') + end end describe '#assign_to_closing_issues_link' do diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c09be0ce1b9..6a83024d0d5 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -418,17 +418,17 @@ describe 'Git HTTP requests', lib: true do end context 'when username and password are provided' do - it 'rejects pulls with 2FA error message' do + it 'rejects pulls with personal access token error message' do download(path, user: user.username, password: user.password) do |response| expect(response).to have_http_status(:unauthorized) - expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') + expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end - it 'rejects the push attempt' do + it 'rejects the push attempt with personal access token error message' do upload(path, user: user.username, password: user.password) do |response| expect(response).to have_http_status(:unauthorized) - expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') + expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end end @@ -441,6 +441,41 @@ describe 'Git HTTP requests', lib: true do end end + context 'when internal auth is disabled' do + before do + allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + end + + it 'rejects pulls with personal access token error message' do + download(path, user: 'foo', password: 'bar') do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') + end + end + + it 'rejects pushes with personal access token error message' do + upload(path, user: 'foo', password: 'bar') do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') + end + end + + context 'when LDAP is configured' do + before do + allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) + allow_any_instance_of(Gitlab::LDAP::Authentication). + to receive(:login).and_return(nil) + end + + it 'does not display the personal access token error message' do + upload(path, user: 'foo', password: 'bar') do |response| + expect(response).to have_http_status(:unauthorized) + expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP') + end + end + end + end + context "when blank password attempts follow a valid login" do def attempt_login(include_password) password = include_password ? user.password : "" diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index e056353fa6f..54d7cf5f10d 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -70,7 +70,7 @@ describe JwtController do context 'without personal token' do it 'rejects the authorization attempt' do expect(response).to have_http_status(401) - expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') + expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') end end @@ -88,9 +88,24 @@ describe JwtController do context 'using invalid login' do let(:headers) { { authorization: credentials('invalid', 'password') } } - subject! { get '/jwt/auth', parameters, headers } + context 'when internal auth is enabled' do + it 'rejects the authorization attempt' do + get '/jwt/auth', parameters, headers + + expect(response).to have_http_status(401) + expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP') + end + end - it { expect(response).to have_http_status(401) } + context 'when internal auth is disabled' do + it 'rejects the authorization attempt with personal access token message' do + allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false } + get '/jwt/auth', parameters, headers + + expect(response).to have_http_status(401) + expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP') + end + end end end diff --git a/spec/support/milestone_tabs_examples.rb b/spec/support/milestone_tabs_examples.rb index 4ad8b0a16e1..7cfc1e06975 100644 --- a/spec/support/milestone_tabs_examples.rb +++ b/spec/support/milestone_tabs_examples.rb @@ -1,10 +1,14 @@ shared_examples 'milestone tabs' do def go(path, extra_params = {}) - params = if milestone.is_a?(GlobalMilestone) - { group_id: group.to_param, id: milestone.safe_title, title: milestone.title } - else - { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } - end + params = + case milestone + when DashboardMilestone + { id: milestone.safe_title, title: milestone.title } + when GroupMilestone + { group_id: group.to_param, id: milestone.safe_title, title: milestone.title } + else + { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid } + end get path, params.merge(extra_params) end diff --git a/spec/support/target_branch_helpers.rb b/spec/support/target_branch_helpers.rb deleted file mode 100644 index 01d1c53fe6c..00000000000 --- a/spec/support/target_branch_helpers.rb +++ /dev/null @@ -1,16 +0,0 @@ -module TargetBranchHelpers - def select_branch(name) - first('button.js-target-branch').click - wait_for_requests - all('a[data-group="Branches"]').find do |el| - el.text == name - end.click - end - - def create_new_branch(name) - first('button.js-target-branch').click - click_link 'Create new branch' - fill_in 'new_branch_name', with: name - click_button 'Create' - end -end diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index 6b07fcfc987..1f8261cc46b 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -21,7 +21,7 @@ describe 'help/index' do render expect(rendered).to match '8.0.2' - expect(rendered).to match 'abcdefg' + expect(rendered).to have_link('abcdefg', 'https://gitlab.com/gitlab-org/gitlab-ce/commits/abcdefg') end end |