diff options
78 files changed, 612 insertions, 396 deletions
diff --git a/CHANGELOG b/CHANGELOG index 0ece39a78b6..4857682d027 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,11 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.12.0 (unreleased) + - Fix timeout when rendering file with thousands of lines. - Add "Remember me" checkbox to LDAP signin form. - Don't notify users mentioned in code blocks or blockquotes. - Omit link to generate labels if user does not have access to create them (Stan Hu) + - Show warning when a comment will add 10 or more people to the discussion. - Disable changing of the source branch in merge request update API (Stan Hu) - Shorten merge request WIP text. - Add option to disallow users from registering any application to use GitLab as an OAuth provider @@ -34,7 +34,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.2' +gem "gitlab_git", '~> 7.2.3' # Ruby/Rack Git Smart-HTTP Server Handler # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) diff --git a/Gemfile.lock b/Gemfile.lock index a341a5df409..9d87de7d4e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,7 +225,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.2.2) + gitlab_git (7.2.3) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -713,7 +713,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.2) + gitlab_git (~> 7.2.3) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index fca2a290e2d..a7476146010 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -10,12 +10,17 @@ class @DropzoneInput iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>" btnAlert = "<button type=\"button\"" + alertAttr + ">×</button>" project_uploads_path = window.project_uploads_path or null + markdown_preview_path = window.markdown_preview_path or null max_file_size = gon.max_file_size or 10 form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "<div class=\"div-dropzone\"></div>" - form_textarea.bind 'paste', (event) => + form_textarea.on 'paste', (event) => handlePaste(event) + form_textarea.on "input", -> + hideReferencedUsers() + form_textarea.on "blur", -> + renderMarkdown() form_dropzone = $(form).find('.div-dropzone') form_dropzone.parent().addClass "div-dropzone-wrapper" @@ -45,16 +50,7 @@ class @DropzoneInput form.find(".md-write-holder").hide() form.find(".md-preview-holder").show() - preview = form.find(".js-md-preview") - mdText = form.find(".markdown-area").val() - if mdText.trim().length is 0 - preview.text "Nothing to preview." - else - preview.text "Loading..." - $.post($(this).data("url"), - md_text: mdText - ).success (previewData) -> - preview.html previewData + renderMarkdown() # Write button $(document).off "click", ".js-md-write-button" @@ -133,6 +129,40 @@ class @DropzoneInput child = $(dropzone[0]).children("textarea") + hideReferencedUsers = -> + referencedUsers = form.find(".referenced-users") + referencedUsers.hide() + + renderReferencedUsers = (users) -> + referencedUsers = form.find(".referenced-users") + + if referencedUsers.length + if users.length >= 10 + referencedUsers.show() + referencedUsers.find(".js-referenced-users-count").text users.length + else + referencedUsers.hide() + + renderMarkdown = -> + preview = form.find(".js-md-preview") + mdText = form.find(".markdown-area").val() + if mdText.trim().length is 0 + preview.text "Nothing to preview." + hideReferencedUsers() + else + preview.text "Loading..." + $.ajax( + type: "POST", + url: markdown_preview_path, + data: { + text: mdText + }, + dataType: "json" + ).success (data) -> + preview.html data.body + + renderReferencedUsers data.references.users + formatLink = (link) -> text = "[#{link.alt}](#{link.url})" text = "!#{text}" if link.is_image diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 4eb3f3c03f3..7967892f856 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -10,7 +10,7 @@ GitLab.GfmAutoComplete = # Team Members Members: - template: '<li>${username} <small>${name}</small></li>' + template: '<li>${username} <small>${title}</small></li>' # Issues and MergeRequests Issues: @@ -34,7 +34,13 @@ GitLab.GfmAutoComplete = searchKey: 'search' callbacks: beforeSave: (members) -> - $.map members, (m) -> name: m.name, username: m.username, search: "#{m.username} #{m.name}" + $.map members, (m) -> + title = m.name + title += " (#{m.count})" if m.count + + username: m.username + title: sanitize(title) + search: sanitize("#{m.username} #{m.name}") input.atwho at: '#' @@ -44,7 +50,10 @@ GitLab.GfmAutoComplete = insertTpl: '${atwho-at}${id}' callbacks: beforeSave: (issues) -> - $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}" + $.map issues, (i) -> + id: i.iid + title: sanitize(i.title) + search: "#{i.iid} #{i.title}" input.atwho at: '!' @@ -54,7 +63,10 @@ GitLab.GfmAutoComplete = insertTpl: '${atwho-at}${id}' callbacks: beforeSave: (merges) -> - $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}" + $.map merges, (m) -> + id: m.iid + title: sanitize(m.title) + search: "#{m.iid} #{m.title}" input.one 'focus', => $.getJSON(@dataSource).done (data) -> diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss index 62c11b06368..690d89a5c16 100644 --- a/app/assets/stylesheets/base/layout.scss +++ b/app/assets/stylesheets/base/layout.scss @@ -4,7 +4,7 @@ html { &.touch .tooltip { display: none !important; } body { - padding-top: 46px; + padding-top: $header-height; } } diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index 3d7868fb7d2..08f153dfbc9 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -12,6 +12,8 @@ $code_font_size: 13px; $code_line_height: 1.5; $border-color: #E5E5E5; $background-color: #f5f5f5; +$header-height: 50px; + /* * State colors: diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index 7e070b4f386..4282832e2bf 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -49,14 +49,6 @@ label { width: 250px; } -.input-mx-250 { - max-width: 250px; -} - -.input-mn-300 { - min-width: 300px; -} - .custom-form-control { width: 150px; } diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 3e738929a78..5e8701830e7 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -2,8 +2,6 @@ * Application Header * */ -$header-height: 46px; - header { &.navbar-empty { background: #FFF; @@ -18,7 +16,7 @@ header { &.navbar-gitlab { z-index: 100; margin-bottom: 0; - min-height: 40px; + min-height: $header-height; border: none; width: 100%; @@ -31,15 +29,14 @@ header { .nav > li > a { color: #888; font-size: 14px; - line-height: 19px; padding: 0; background-color: #f5f5f5; - margin: 9px 0; + margin: ($header-height - 28) / 2 0; margin-left: 10px; border-radius: 40px; - height: 26px; - width: 26px; - line-height: 26px; + height: 28px; + width: 28px; + line-height: 28px; text-align: center; &:hover, &:focus, &:active { @@ -71,15 +68,15 @@ header { float: left; height: $header-height; width: 100%; - padding: 5px 8px; + padding: ($header-height - 36 ) / 2 8px; h3 { width: 158px; float: left; margin: 0; - margin-left: 20px; + margin-left: 14px; font-size: 18px; - line-height: 34px; + line-height: $header-height - 14; font-weight: normal; } @@ -106,7 +103,7 @@ header { margin: 0; margin-left: 35px; font-size: 18px; - line-height: 44px; + line-height: $header-height; font-weight: bold; color: #444; @@ -124,7 +121,7 @@ header { .search { margin-right: 10px; margin-left: 10px; - margin-top: 8px; + margin-top: ($header-height - 28) / 2; form { margin: 0; @@ -205,9 +202,17 @@ header { } } - li { - display: table-cell; - width: 1%; + .navbar-collapse { + padding-left: 5px; + + li { + display: table-cell; + width: 1%; + + a { + margin-left: 8px !important; + } + } } } } diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index eb39b6bb7e9..f94677d1925 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -52,6 +52,22 @@ transition: opacity 200ms ease-in-out; } +.md-area { + position: relative; +} + +.md-header ul { + float: left; +} + +.referenced-users { + padding: 10px 0; + color: #999; + margin-left: 10px; + margin-top: 1px; + margin-right: 130px; +} + .md-preview-holder { background: #FFF; border: 1px solid #ddd; diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index 74108c1f086..f04c8eef904 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -19,6 +19,10 @@ } } + .referenced-users { + margin-right: 0; + } + .issues-filters, .dash-projects-filters, .check-all-holder { @@ -60,6 +64,21 @@ margin-left: 15px !important; max-width: 70% !important; } + + .issue-info, .merge-request-info { + display: none; + } + + .issue-details { + .creator, + .page-title .btn-close { + display: none; + } + } + + %ul.notes .note-role, .note-actions { + display: none; + } } @media (max-width: $screen-sm-max) { diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index 69bddc6f59e..65e06e14c73 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -86,7 +86,7 @@ .nav-sidebar { margin-top: 29px; position: fixed; - top: 45px; + top: $header-height; width: $sidebar_width; } } @@ -105,7 +105,7 @@ .nav-sidebar { margin-top: 29px; position: fixed; - top: 45px; + top: $header-height; width: $sidebar_collapsed_width; li a { @@ -130,21 +130,13 @@ .username { display: none; } - - .avatar { - margin-bottom: 10px; - } - - .logout-holder { - text-align: center; - } } } } .collapse-nav a { position: fixed; - top: 46px; + top: $header-height; left: 198px; font-size: 13px; background: transparent; @@ -188,9 +180,8 @@ bottom: 0; width: 100%; padding: 10px; - color: #fff; - .avatar { + .username { margin-top: 5px; } } diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss index bcb8bbe3134..7ab01187a02 100644 --- a/app/assets/stylesheets/generic/zen.scss +++ b/app/assets/stylesheets/generic/zen.scss @@ -1,6 +1,4 @@ .zennable { - position: relative; - .zen-toggle-comment { display: none; } @@ -8,8 +6,9 @@ .zen-enter-link { color: #888; position: absolute; - top: -26px; + top: 0px; right: 4px; + line-height: 40px; } .zen-leave-link { diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index af9c83e5dc8..09e8d57a100 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -29,7 +29,7 @@ line-height: 24px; .str-truncated { - max-width: 72%; + max-width: 76%; } a { diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3572f33e91f..ed938f86b35 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -145,3 +145,9 @@ h2.issue-title { .issue-form .select2-container { width: 250px !important; } + +.issues-holder { + .issue-info { + margin-left: 20px; + } +} diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index a0522030785..203f9374cee 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -39,11 +39,8 @@ .new_note, .edit_note { .buttons { - float: left; margin-top: 8px; - } - .clearfix { - margin-bottom: 0; + margin-bottom: 3px; } .note-preview-holder { @@ -82,7 +79,6 @@ .note-form-actions { background: #F9F9F9; - height: 45px; .note-form-option { margin-top: 8px; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index b93ea0f020e..e19b2eafa43 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -15,18 +15,16 @@ } .project-home-panel { - margin-bottom: 20px; + margin-top: 10px; + margin-bottom: 15px; position: relative; padding-left: 65px; - border-bottom: 1px solid #DDD; - padding-bottom: 10px; - padding-top: 5px; min-height: 50px; .project-identicon-holder { position: absolute; left: 0; - top: -10px; + top: -14px; .avatar { width: 50px; diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 1b06b4aa925..10fcaf18fa9 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -30,8 +30,12 @@ border-right: 1px solid $color-darker; .sidebar-user { - a { - color: $color-light; + color: $color-light; + + &:hover { + background-color: $color-dark; + color: #FFF; + text-decoration: none; } } } diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index b762518d377..100d3d3b317 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -13,27 +13,20 @@ class Projects::BlobController < Projects::ApplicationController before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] before_action :from_merge_request, only: [:edit, :update] - before_action :after_edit_path, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] + before_action :editor_variables, except: [:show, :preview, :diff] + before_action :after_edit_path, only: [:edit, :update] def new commit unless @repository.empty? end def create - file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateService.new( - @project, - current_user, - params.merge(new_branch: sanitized_new_branch_name), - @ref, - file_path - ).execute + result = Files::CreateService.new(@project, current_user, @commit_params).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - ref = sanitized_new_branch_name.presence || @ref - redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path)) + redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) else flash[:alert] = result[:message] render :new @@ -48,22 +41,10 @@ class Projects::BlobController < Projects::ApplicationController end def update - result = Files::UpdateService. - new( - @project, - current_user, - params.merge(new_branch: sanitized_new_branch_name), - @ref, - @path - ).execute + result = Files::UpdateService.new(@project, current_user, @commit_params).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - - if from_merge_request - from_merge_request.reload_code - end - redirect_to after_edit_path else flash[:alert] = result[:message] @@ -80,12 +61,11 @@ class Projects::BlobController < Projects::ApplicationController end def destroy - result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute + result = Files::DeleteService.new(@project, current_user, @commit_params).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_tree_path(@project.namespace, @project, - @ref) + redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch) else flash[:alert] = result[:message] render :show @@ -135,7 +115,6 @@ class Projects::BlobController < Projects::ApplicationController @id = params[:id] @ref, @path = extract_ref(@id) - rescue InvalidPathError not_found! end @@ -145,8 +124,8 @@ class Projects::BlobController < Projects::ApplicationController if from_merge_request diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + "#file-path-#{hexdigest(@path)}" - elsif sanitized_new_branch_name.present? - namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path)) + elsif @target_branch.present? + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) else namespace_project_blob_path(@project.namespace, @project, @id) end @@ -160,4 +139,25 @@ class Projects::BlobController < Projects::ApplicationController def sanitized_new_branch_name @new_branch ||= sanitize(strip_tags(params[:new_branch])) end + + def editor_variables + @current_branch = @ref + @target_branch = (sanitized_new_branch_name || @ref) + + @file_path = + if action_name.to_s == 'create' + File.join(@path, File.basename(params[:file_name])) + else + @path + end + + @commit_params = { + file_path: @file_path, + current_branch: @current_branch, + target_branch: @target_branch, + commit_message: params[:commit_message], + file_content: params[:content], + file_content_encoding: params[:encoding] + } + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 4ca5fc65459..be5968cd7b0 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -151,7 +151,17 @@ class ProjectsController < ApplicationController end def markdown_preview - render text: view_context.markdown(params[:md_text]) + text = params[:text] + + ext = Gitlab::ReferenceExtractor.new(@project, current_user) + ext.analyze(text) + + render json: { + body: view_context.markdown(text), + references: { + users: ext.users.map(&:username) + } + } end private diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 6f9f54d08cc..10c39cb1ece 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -67,7 +67,13 @@ module Mentionable # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. def create_cross_references!(p = project, a = author, without = []) - refs = references(p) - without + refs = references(p) + + # We're using this method instead of Array diffing because that requires + # both of the object's `hash` values to be the same, which may not be the + # case for otherwise identical Commit objects. + refs.reject! { |ref| without.include?(ref) } + refs.each do |ref| Note.create_cross_reference_note(ref, local_reference, a) end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 4d02752454e..f587ee266da 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -1,11 +1,34 @@ module Files class BaseService < ::BaseService - attr_reader :ref, :path + class ValidationError < StandardError; end - def initialize(project, user, params, ref, path = nil) - @project, @current_user, @params = project, user, params.dup - @ref = ref - @path = path + def execute + @current_branch = params[:current_branch] + @target_branch = params[:target_branch] + @commit_message = params[:commit_message] + @file_path = params[:file_path] + @file_content = if params[:file_content_encoding] == 'base64' + Base64.decode64(params[:file_content]) + else + params[:file_content] + end + + # Validate parameters + validate + + # Create new branch if it different from current_branch + if @target_branch != @current_branch + create_target_branch + end + + if sha = commit + after_commit(sha, @target_branch) + success + else + error("Something went wrong. Your changes were not committed") + end + rescue ValidationError => ex + error(ex.message) end private @@ -14,11 +37,51 @@ module Files project.repository end - def after_commit(sha) + def after_commit(sha, branch) commit = repository.commit(sha) - full_ref = 'refs/heads/' + (params[:new_branch] || ref) + full_ref = 'refs/heads/' + branch old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA GitPushService.new.execute(project, current_user, old_sha, sha, full_ref) end + + def current_branch + @current_branch ||= params[:current_branch] + end + + def target_branch + @target_branch ||= params[:target_branch] + end + + def raise_error(message) + raise ValidationError.new(message) + end + + def validate + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + + unless allowed + raise_error("You are not allowed to push into this branch") + end + + unless project.empty_repo? + unless repository.branch_names.include?(@current_branch) + raise_error("You can only create files if you are on top of a branch") + end + + if @current_branch != @target_branch + if repository.branch_names.include?(@target_branch) + raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") + end + end + end + end + + def create_target_branch + result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch) + + unless result[:status] == :success + raise_error("Something went wrong when we tried to create #{@target_branch} for you") + end + end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 0a80455bc6b..91d715b2d63 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -2,58 +2,28 @@ require_relative "base_service" module Files class CreateService < Files::BaseService - def execute - allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) + def commit + repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch) + end - unless allowed - return error("You are not allowed to create file in this branch") - end + def validate + super - file_name = File.basename(path) - file_path = path + file_name = File.basename(@file_path) unless file_name =~ Gitlab::Regex.file_name_regex - return error( + raise_error( 'Your changes could not be committed, because the file name ' + Gitlab::Regex.file_name_regex_message ) end - if project.empty_repo? - # everything is ok because repo does not have a commits yet - else - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - - blob = repository.blob_at_branch(ref, file_path) + unless project.empty_repo? + blob = repository.blob_at_branch(@current_branch, @file_path) if blob - return error("Your changes could not be committed, because file with such name exists") - end - end - - content = - if params[:encoding] == 'base64' - Base64.decode64(params[:content]) - else - params[:content] + raise_error("Your changes could not be committed, because file with such name exists") end - - sha = repository.commit_file( - current_user, - file_path, - content, - params[:commit_message], - params[:new_branch] || ref - ) - - - if sha - after_commit(sha) - success - else - error("Your changes could not be committed, because the file has been changed") end end end diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index 2281777604c..27c881c3430 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -2,36 +2,8 @@ require_relative "base_service" module Files class DeleteService < Files::BaseService - def execute - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) - - unless allowed - return error("You are not allowed to push into this branch") - end - - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - - blob = repository.blob_at_branch(ref, path) - - unless blob - return error("You can only edit text files") - end - - sha = repository.remove_file( - current_user, - path, - params[:commit_message], - ref - ) - - if sha - after_commit(sha) - success - else - error("Your changes could not be committed, because the file has been changed") - end + def commit + repository.remove_file(current_user, @file_path, @commit_message, @target_branch) end end end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 013cc1ee322..a20903c6f02 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -2,46 +2,8 @@ require_relative "base_service" module Files class UpdateService < Files::BaseService - def execute - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) - - unless allowed - return error("You are not allowed to push into this branch") - end - - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - - blob = repository.blob_at_branch(ref, path) - - unless blob - return error("You can only edit text files") - end - - content = - if params[:encoding] == 'base64' - Base64.decode64(params[:content]) - else - params[:content] - end - - sha = repository.commit_file( - current_user, - path, - content, - params[:commit_message], - params[:new_branch] || ref - ) - - after_commit(sha) - success - rescue Gitlab::Satellite::CheckoutFailed => ex - error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400) - rescue Gitlab::Satellite::CommitFailed => ex - error("Your changes could not be committed. Maybe there was nothing to commit?", 409) - rescue Gitlab::Satellite::PushFailed => ex - error("Your changes could not be committed. Maybe the file was changed by another process?", 409) + def commit + repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch) end end end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index b91590a1a90..0004a399f47 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -38,13 +38,13 @@ module Projects def groups current_user.authorized_groups.sort_by(&:path).map do |group| count = group.users.count - { username: group.path, name: "#{group.name} (#{count})" } + { username: group.path, name: group.name, count: count } end end def all_members count = project.team.members.flatten.count - [{ username: "all", name: "All Project and Group Members (#{count})" }] + [{ username: "all", name: "All Project and Group Members", count: count }] end end end diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index 267c9a52921..17dffebd360 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -22,11 +22,11 @@ .form-group.js-toggle-colors-container.hide = f.label :color, "Background Color", class: 'control-label' .col-sm-10 - = f.color_field :color, value: "#AA33EE", class: "form-control" + = f.color_field :color, value: "#eb9532", class: "form-control" .form-group.js-toggle-colors-container.hide = f.label :font, "Font Color", class: 'control-label' .col-sm-10 - = f.color_field :font, value: "#224466", class: "form-control" + = f.color_field :font, value: "#FFFFFF", class: "form-control" .form-group = f.label :starts_at, class: 'control-label' .col-sm-10.datetime-controls diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index e00b23ad99f..5ce7cdf2f8d 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -11,7 +11,7 @@ = form_tag admin_groups_path, method: :get, class: 'form-inline' do = hidden_field_tag :sort, @sort .form-group - = text_field_tag :name, params[:name], class: "form-control input-mn-300" + = text_field_tag :name, params[:name], class: "form-control" = button_tag "Search", class: "btn submit btn-primary" .pull-right diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index cfb386e131f..0a354373b9b 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -2,7 +2,7 @@ %h3.page-title Group Membership - if current_user.can_create_group? - %span.pull-right + %span.pull-right.hidden-xs = link_to new_group_path, class: "btn btn-new" do %i.fa.fa-plus New Group @@ -17,7 +17,7 @@ - @group_members.each do |group_member| - group = group_member.group %li - .pull-right + .pull-right.hidden-xs - if can?(current_user, :admin_group, group) = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do %i.fa.fa-cogs @@ -27,7 +27,7 @@ %i.fa.fa-sign-out Leave - = image_tag group_icon(group), class: "avatar s40 avatar-tile" + = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs" = link_to group, class: 'group-name' do %strong= group.name diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index c05d45e0100..f3f0b778539 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -4,7 +4,7 @@ = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| = hidden_field_tag :sort, @sort .form-group - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search" + = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search" .form-group = button_tag 'Search', class: "btn btn-primary wide" diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index b3963a9d901..82622a58ed2 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,7 +1,7 @@ .pull-left = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f| .form-group - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" + = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search" .form-group = button_tag 'Search', class: "btn btn-primary wide" diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 903ca877218..a70d1ff0697 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -14,7 +14,7 @@ .clearfix.js-toggle-container = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } + = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' } = button_tag 'Search', class: 'btn' - if current_user && current_user.can?(:admin_group, @group) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index b1a57d9824e..dbc68c39bf1 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -15,7 +15,7 @@ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'theme-color', content: '#474D57'} - = yield(:meta_tags) + = yield :meta_tags = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index c1283734d25..f17f6fdd91c 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -8,19 +8,13 @@ .collapse-nav = render partial: 'layouts/collapse_button' - if current_user - .sidebar-user - = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'top'} do - = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32' + = link_to current_user, class: 'sidebar-user' do + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32' .username = current_user.username - .logout-holder - = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'top'} do - = icon('sign-out') .content-wrapper .container-fluid .content = render "layouts/flash" .clearfix = yield - -= yield :embedded_scripts diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 155825cc4c2..173033f7eab 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -2,9 +2,14 @@ %html{ lang: "en"} = render "layouts/head" %body{class: "#{app_theme}", :'data-page' => body_data_page} + / Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body. + = yield :scripts_body_top + - if current_user = render "layouts/header/default", title: header_title - else = render "layouts/header/public", title: header_title = render 'layouts/page', sidebar: sidebar + + = yield :scripts_body diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 2970af377f5..8b4510d6516 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -19,7 +19,7 @@ %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('search') - %li + %li.hidden-xs = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('question-circle fw') %li @@ -33,11 +33,14 @@ = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('wrench fw') - if current_user.can_create_project? - %li + %li.hidden-xs = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('plus fw') %li = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('cog fw') + %li + = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('sign-out') = render 'shared/outdated_browser' diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 4aeb9d397d2..44afa33dfe5 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -2,7 +2,13 @@ - header_title project_title(@project) - sidebar "project" unless sidebar -- content_for :embedded_scripts do +- content_for :scripts_body_top do + - if current_user + :javascript + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; + window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}"; + +- content_for :scripts_body do = render "layouts/init_auto_complete" if current_user = render template: "layouts/application" diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 2c5b24b8130..491e2107da4 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -24,7 +24,7 @@ = f.label :description, 'Description', class: 'control-label' .col-sm-10 - = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .col-sm-12.hint diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index b869fd6e12a..a831481cf80 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,13 +1,24 @@ -%ul.nav.nav-tabs - %li.active - = link_to '#md-write-holder', class: 'js-md-write-button' do - Write - %li - = link_to '#md-preview-holder', class: 'js-md-preview-button', - data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do - Preview -%div - .md-write-holder - = yield - .md.md-preview-holder.hide - .js-md-preview{class: (preview_class if defined?(preview_class))} +.md-area + .md-header.clearfix + %ul.nav.nav-tabs + %li.active + = link_to '#md-write-holder', class: 'js-md-write-button' do + Write + %li + = link_to '#md-preview-holder', class: 'js-md-preview-button' do + Preview + + - if defined?(referenced_users) && referenced_users + %span.referenced-users.pull-left.hide + = icon('exclamation-triangle') + You are about to add + %strong + %span.js-referenced-users-count 0 + people + to the discussion. Proceed with caution. + + %div + .md-write-holder + = yield + .md.md-preview-holder.hide + .js-md-preview{class: (preview_class if defined?(preview_class))} diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 9b1d03b820e..f7ddf74b4fc 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -6,11 +6,12 @@ = render 'shared/commit_message_container', params: params, placeholder: 'Add new file' - .form-group.branch - = label_tag 'branch', class: 'control-label' do - Branch - .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control" + - unless @project.empty_repo? + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 7d7217eb2a8..8d2564be55e 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -10,5 +10,3 @@ $('#issue_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); - - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index a4e25e5ce88..64d62b45657 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -4,7 +4,7 @@ = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .issue-title - %span.str-truncated + %span.issue-title-text = link_to_gfm issue.title, issue_path(issue), class: "row_title" .issue-labels - issue.labels.each do |label| diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 1c7160bce5f..be73f087449 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -8,5 +8,3 @@ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); - - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 65f5c3d6a19..c16df27ee8f 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,6 +1,6 @@ %li{ class: mr_css_classes(merge_request) } .merge-request-title - %span.str-truncated + %span.merge-request-title-text = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" .merge-request-labels - merge_request.labels.each do |label| diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9a2edbf0a8c..6792104569b 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -51,8 +51,6 @@ e.preventDefault(); }); - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; - :javascript var merge_request merge_request = new MergeRequest({ diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 841d1e1cfe9..fa591b0537e 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -4,9 +4,10 @@ = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - if can? current_user, :write_merge_request, @project - = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do - %i.fa.fa-plus - New Merge Request + .pull-left.hidden-xs + = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new", title: "New Merge Request" do + %i.fa.fa-plus + New Merge Request = render 'shared/issuable_filter', type: :merge_requests .merge-requests-holder = render 'merge_requests' diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 906cc11dc67..bfd4ab6f3d8 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -49,7 +49,7 @@ .automerge_widget.cannot_be_merged.hide %h4 - This pull request contains merge conflicts that must be resolved. + This merge request contains merge conflicts that must be resolved. You can try it manually on the %strong = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" @@ -63,14 +63,14 @@ .automerge_widget.work_in_progress.hide %h4 - This request cannot be merged because it is marked as <strong>Work In Progress</strong>. + This merge request cannot be accepted because it is marked as Work In Progress. %p %button.btn.disabled{:type => 'button'} %i.fa.fa-warning Accept Merge Request - When the merge request is ready, remove the "WIP" prefix from the title to allow merging. + When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted. .automerge_widget.unchecked %p diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 95b7070ce5c..5650607f31f 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -50,5 +50,3 @@ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); - - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 2ada6cb6700..f28b3e9b508 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -5,7 +5,7 @@ = f.hidden_field :noteable_id = f.hidden_field :noteable_type - = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do + = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text' @@ -15,10 +15,7 @@ .error-alert .note-form-actions - .buttons + .buttons.clearfix = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button" = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel - -:javascript - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 6edb92acd4d..162583e4b1d 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -11,7 +11,7 @@ .clearfix.js-toggle-container = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } + = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' } = button_tag 'Search', class: 'btn' - if can?(current_user, :admin_project_member, @project) diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 9fbfa0b1aeb..2a8ceaa2844 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -41,6 +41,3 @@ - else = f.submit 'Create page', class: "btn-create btn" = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel" - -:javascript - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml index 47016daf1f0..5ee70be1ad6 100644 --- a/app/views/search/_form.html.haml +++ b/app/views/search/_form.html.haml @@ -5,7 +5,7 @@ = hidden_field_tag :scope, params[:scope] .search-holder.clearfix .form-group - = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input input-mn-300", id: "dashboard_search", autofocus: true + = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true = button_tag 'Search', class: "btn btn-primary" - unless params[:snippets].eql? 'true' .pull-right diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index fba69dd0f3f..86921f0a777 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -4,7 +4,8 @@ - blob.data.lines.to_a.size.times do |index| - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset - = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do + / We're not using `link_to` because it is too slow once we get to thousands of lines. + %a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"} %i.fa.fa-link = i :preserve diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/_issuable_search_form.html.haml index 639d203dcd6..58c3de64b77 100644 --- a/app/views/shared/_issuable_search_form.html.haml +++ b/app/views/shared/_issuable_search_form.html.haml @@ -1,6 +1,6 @@ = form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do .append-right-10.hidden-xs.hidden-sm - = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } + = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input' } = hidden_field_tag :state, params['state'] = hidden_field_tag :scope, params['scope'] = hidden_field_tag :assignee_id, params['assignee_id'] diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml index 722a7f7ce0f..4537f8eec86 100644 --- a/app/views/shared/_project.html.haml +++ b/app/views/shared/_project.html.haml @@ -16,6 +16,3 @@ %span.pull-right.light %i.fa.fa-star = project.star_count - - else - %span.arrow - %i.fa.fa-angle-right diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 6ed45fedfa2..1694818aef6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -13,7 +13,7 @@ %h3 = @user.name - if @user == current_user - .pull-right + .pull-right.hidden-xs = link_to profile_path, class: 'btn btn-sm' do %i.fa.fa-pencil-square-o Edit Profile settings diff --git a/doc/README.md b/doc/README.md index 4e00dceac2b..7a2181edded 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,34 +1,35 @@ -# Documentation - -## User documentation - -- [API](api/README.md) Automate GitLab via a simple and powerful API. -- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. -- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. -- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat. -- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. -- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. -- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. -- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. -- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - -## Administrator documentation - -- [Install](install/README.md) Requirements, directory structures and installation from source. -- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. -- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. -- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. -- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. -- [Security](security/README.md) Learn what you can do to further secure your GitLab instance. -- [Update](update/README.md) Update guides to upgrade your installation. -- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. -- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. -- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. -- [Operations](operations/README.md) Keeping GitLab up and running -- [Log system](logs/logs.md) Log system - -## Contributor documentation - -- [Development](development/README.md) Explains the architecture and the guidelines for shell commands. -- [Legal](legal/README.md) Contributor license agreements. -- [Release](release/README.md) How to make the monthly and security releases. +# Documentation
+
+## User documentation
+
+- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
+- [Importing to GitLab](workflow/importing/README.md).
+- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
+- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
+- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
+- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
+- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
+- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
+- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
+
+## Administrator documentation
+
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [Install](install/README.md) Requirements, directory structures and installation from source.
+- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
+- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Log system](logs/logs.md) Log system.
+- [Operations](operations/README.md) Keeping GitLab up and running
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
+- [Update](update/README.md) Update guides to upgrade your installation.
+- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
+
+## Contributor documentation
+
+- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
+- [Legal](legal/README.md) Contributor license agreements.
+- [Release](release/README.md) How to make the monthly and security releases.
\ No newline at end of file diff --git a/doc/operations/README.md b/doc/operations/README.md index f1456c6c8e2..6a35dab7b6c 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -2,3 +2,4 @@ - [Sidekiq MemoryKiller](sidekiq_memory_killer.md) - [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) +- [Understanding Unicorn and unicorn-worker-killer](unicorn.md) diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md new file mode 100644 index 00000000000..31b432cd411 --- /dev/null +++ b/doc/operations/unicorn.md @@ -0,0 +1,86 @@ +# Understanding Unicorn and unicorn-worker-killer + +## Unicorn + +GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web +server, to handle web requests (web browsers and Git HTTP clients). Unicorn is +a daemon written in Ruby and C that can load and run a Ruby on Rails +application; in our case the Rails application is GitLab Community Edition or +GitLab Enterprise Edition. + +Unicorn has a multi-process architecture to make better use of available CPU +cores (processes can run on different cores) and to have stronger fault +tolerance (most failures stay isolated in only one process and cannot take down +GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby +environment with the GitLab application code, and then spawns 'workers' which +inherit this clean initial environment. The 'master' never handles any +requests, that is left to the workers. The operating system network stack +queues incoming requests and distributes them among the workers. + +In a perfect world, the master would spawn its pool of workers once, and then +the workers handle incoming web requests one after another until the end of +time. In reality, worker processes can crash or time out: if the master notices +that a worker takes too long to handle a request it will terminate the worker +process with SIGKILL ('kill -9'). No matter how the worker process ended, the +master process will replace it with a new 'clean' process again. Unicorn is +designed to be able to replace 'crashed' workers without dropping user +requests. + +This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The +master process has PID 56227 below. + +``` +[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing +[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10 +[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 +[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready +``` + +### Tunables + +The main tunables for Unicorn are the number of worker processes and the +request timeout after which the Unicorn master terminates a worker process. +See the [omnibus-gitlab Unicorn settings +documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) +if you want to adjust these settings. + +## unicorn-worker-killer + +GitLab has memory leaks. These memory leaks manifest themselves in long-running +processes, such as Unicorn workers. (The Unicorn master process is not known to +leak memory, probably because it does not handle user requests.) + +To make these memory leaks manageable, GitLab comes with the +[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This +gem [monkey-patches](http://en.wikipedia.org/wiki/Monkey_patch) the Unicorn +workers to do a memory self-check after every 16 requests. If the memory of the +Unicorn worker exceeds a pre-set limit then the worker process exits. The +Unicorn master then automatically replaces the worker process. + +This is a robust way to handle memory leaks: Unicorn is designed to handle +workers that 'crash' so no user requests will be dropped. The +unicorn-worker-killer gem is designed to only terminate a worker process _in +between requests_, so no user requests are affected. + +This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. +You see that worker 4 (PID 125918) is inspecting itself and decides to exit. +The threshold memory value was 254802235 bytes, about 250MB. With GitLab this +threshold is a random value between 200 and 250 MB. The master process (PID +117565) then reaps the worker process and spawns a new 'worker 4' with PID +127549. + +``` +[2015-06-05T12:07:41.828374 #125918] WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) +[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) +[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4 +[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 +[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready +``` + +One other thing that stands out in the log snippet above, taken from +Gitlab.com, is that 'worker 4' was serving requests for only 23 seconds. This +is a normal value for our current GitLab.com setup and traffic. + +The high frequency of Unicorn memory restarts on some GitLab sites can be a +source of confusion for administrators. Usually they are a [red +herring](http://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 89005e51958..70a8179c8eb 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,18 +1,16 @@ -# Workflow - -- [Feature branch workflow](workflow.md) -- [Project forking workflow](forking_workflow.md) -- [Project Features](project_features.md) -- [Authorization for merge requests](authorization_for_merge_requests.md) -- [Groups](groups.md) -- [Labels](labels.md) -- [GitLab Flow](gitlab_flow.md) -- [Notifications](notifications.md) -- [Migrating from SVN to GitLab](migrating_from_svn.md) -- [Project importing from GitHub to GitLab](import_projects_from_github.md) -- [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md) -- [Two-factor Authentication (2FA)](two_factor_authentication.md) -- [Protected branches](protected_branches.md) -- [Change your time zone](timezone.md) -- [Keyboard shortcuts](shortcuts.md) -- [Web Editor](web_editor.md) +# Workflow
+
+- [Authorization for merge requests](authorization_for_merge_requests.md)
+- [Change your time zone](timezone.md)
+- [Feature branch workflow](workflow.md)
+- [GitLab Flow](gitlab_flow.md)
+- [Groups](groups.md)
+- [Keyboard shortcuts](shortcuts.md)
+- [Labels](labels.md)
+- [Notifications](notifications.md)
+- [Project Features](project_features.md)
+- [Project forking workflow](forking_workflow.md)
+- [Protected branches](protected_branches.md)
+- [Two-factor Authentication (2FA)](two_factor_authentication.md)
+- [Web Editor](web_editor.md)
+- ["Work In Progress" Merge Requests](wip_merge_requests.md)
\ No newline at end of file diff --git a/doc/workflow/bitbucket_importer/bitbucket_import_grant_access.jpg b/doc/workflow/bitbucket_importer/bitbucket_import_grant_access.jpg Binary files differnew file mode 100644 index 00000000000..f3432e923c4 --- /dev/null +++ b/doc/workflow/bitbucket_importer/bitbucket_import_grant_access.jpg diff --git a/doc/workflow/bitbucket_importer/bitbucket_import_new_project.jpg b/doc/workflow/bitbucket_importer/bitbucket_import_new_project.jpg Binary files differnew file mode 100644 index 00000000000..d0befa430b5 --- /dev/null +++ b/doc/workflow/bitbucket_importer/bitbucket_import_new_project.jpg diff --git a/doc/workflow/bitbucket_importer/bitbucket_import_select_bitbucket.jpg b/doc/workflow/bitbucket_importer/bitbucket_import_select_bitbucket.jpg Binary files differnew file mode 100644 index 00000000000..53080e8104d --- /dev/null +++ b/doc/workflow/bitbucket_importer/bitbucket_import_select_bitbucket.jpg diff --git a/doc/workflow/bitbucket_importer/bitbucket_import_select_project.png b/doc/workflow/bitbucket_importer/bitbucket_import_select_project.png Binary files differnew file mode 100644 index 00000000000..8312825eb18 --- /dev/null +++ b/doc/workflow/bitbucket_importer/bitbucket_import_select_project.png diff --git a/doc/workflow/import_projects_from_bitbucket.md b/doc/workflow/import_projects_from_bitbucket.md new file mode 100644 index 00000000000..20d65f1925a --- /dev/null +++ b/doc/workflow/import_projects_from_bitbucket.md @@ -0,0 +1,26 @@ +# Import your project from Bitbucket to GitLab
+
+It takes just a few steps to import your existing Bitbucket projects to GitLab. But keep in mind that it is possible only if Bitbucket support is enabled on your GitLab instance. You can read more about Bitbucket support [here](doc/integration/bitbucket.md).
+
+* Sign in to GitLab.com and go to your dashboard
+
+* Click on "New project"
+
+![New project in GitLab](bitbucket_importer/bitbucket_import_new_project.jpg)
+
+* Click on the "Bitbucket" button
+
+![Bitbucket](bitbucket_importer/bitbucket_import_select_bitbucket.jpg)
+
+* Grant GitLab access to your Bitbucket account
+
+![Grant access](bitbucket_importer/bitbucket_import_grant_access.jpg)
+
+* Click on the projects that you'd like to import or "Import all projects"
+
+![Import projects](bitbucket_importer/bitbucket_import_select_project.png)
+
+A new GitLab project will be created with your imported data.
+
+### Note
+Milestones and wiki pages are not imported from Bitbucket.
\ No newline at end of file diff --git a/doc/workflow/import_projects_from_github.md b/doc/workflow/import_projects_from_github.md index 8644b4ffc73..aad2c63817d 100644 --- a/doc/workflow/import_projects_from_github.md +++ b/doc/workflow/import_projects_from_github.md @@ -1,13 +1,18 @@ -# Project importing from GitHub to GitLab - -You can import your existing GitHub projects to GitLab. But keep in mind that it is possible only if -GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html) -To get to the importer page you need to go to "New project" page. - -![New project page](github_importer/new_project_page.png) - -Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer. - -![Importer page](github_importer/importer.png) - -To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
\ No newline at end of file +# Import your project from GitHub to GitLab
+
+It takes just a couple of steps to import your existing GitHub projects to GitLab. Keep in mind that it is possible only if
+GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+
+* Sign in to GitLab.com and go to your dashboard.
+* To get to the importer page, you need to go to the "New project" page.
+
+![New project page](github_importer/new_project_page.png)
+
+* Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+![Importer page](github_importer/importer.png)
+
+* To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
+
+### Note
+When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future.
\ No newline at end of file diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md new file mode 100644 index 00000000000..3cab541f955 --- /dev/null +++ b/doc/workflow/importing/README.md @@ -0,0 +1,6 @@ +# Migrating projects to a GitLab instance + +1. [Bitbucket](doc/workflow/import_projects_from_bitbucket.md) +2. [GitHub](doc/workflow/import_projects_from_github.md) +3. [GitLab.com](doc/workflow/import_projects_from_gitlab_com.md) +4. [SVN](doc/workflow/migrating_from_svn.md) diff --git a/doc/workflow/two_factor_authentication.md b/doc/workflow/two_factor_authentication.md index 8ac1ca4b351..7c45d23c99d 100644 --- a/doc/workflow/two_factor_authentication.md +++ b/doc/workflow/two_factor_authentication.md @@ -21,7 +21,8 @@ is to know your username and password *and* have access to your phone. **On your phone:** -1. Install a compatible application. We recommend [Google Authenticator]. +1. Install a compatible application. We recommend [Google Authenticator] +\(proprietary\) or [FreeOTP] \(open source\). 1. In the application, add a new entry in one of two ways: * Scan the code with your phone's camera to add the entry automatically. * Enter the details provided to add the entry manually. @@ -63,3 +64,4 @@ your phone's application or a recovery code to log in. 1. Click **Disable Two-factor Authentication**. [Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en +[FreeOTP]: https://fedorahosted.org/freeotp/
\ No newline at end of file diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md new file mode 100644 index 00000000000..46035a5e6b6 --- /dev/null +++ b/doc/workflow/wip_merge_requests.md @@ -0,0 +1,13 @@ +# "Work In Progress" Merge Requests + +To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**. + +![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png) + +To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`. + +![Mark as WIP](wip_merge_requests/mark_as_wip.png) + +To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix. + +![Unark as WIP](wip_merge_requests/unmark_as_wip.png) diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png Binary files differnew file mode 100644 index 00000000000..4791e5de972 --- /dev/null +++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png Binary files differnew file mode 100644 index 00000000000..8fa83a201ac --- /dev/null +++ b/doc/workflow/wip_merge_requests/mark_as_wip.png diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png Binary files differnew file mode 100644 index 00000000000..d45e68f31c5 --- /dev/null +++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png diff --git a/doc_styleguide.md b/doc_styleguide.md index 670af765f3a..db30a737f14 100644 --- a/doc_styleguide.md +++ b/doc_styleguide.md @@ -12,6 +12,7 @@ This styleguide recommends best practices to improve documentation and to keep i * Be brief and clear. +* Whenever it applies, add documents in alphabetical order. ## When adding images to a document diff --git a/docker/README.md b/docker/README.md index a73ccd0dba0..46b21348364 100644 --- a/docker/README.md +++ b/docker/README.md @@ -122,7 +122,7 @@ You can find all available options in [Omnibus GitLab documentation](https://git ### Upgrade GitLab with app and data images -To updgrade GitLab to new versions, stop running container, create new docker image and container from that image. +To upgrade GitLab to new versions, stop running container, create new docker image and container from that image. It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory: @@ -141,10 +141,12 @@ sudo docker rmi gitlab-app:7.8.1 ### Publish images to Dockerhub -Login to Dockerhub with `sudo docker login` and run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name): +- Ensure the containers are running +- Login to Dockerhub with `sudo docker login` +- Run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name): ```bash -sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app:7.10.1 sytse/gitlab-app:7.10.1 +sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1 sudo docker push sytse/gitlab-app:7.10.1 sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab_data sytse/gitlab_data sudo docker push sytse/gitlab_data diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 32888eb17d9..d16e6bbea57 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -168,7 +168,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I click on my profile picture' do - click_link 'profile-pic' + find(:css, '.sidebar-user').click end step 'I should see my user page' do diff --git a/lib/api/files.rb b/lib/api/files.rb index e0ea6d7dd1d..c7b30cf2f07 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -3,6 +3,26 @@ module API class Files < Grape::API before { authenticate! } + helpers do + def commit_params(attrs) + { + file_path: attrs[:file_path], + current_branch: attrs[:branch_name], + target_branch: attrs[:branch_name], + commit_message: attrs[:commit_message], + file_content: attrs[:content], + file_content_encoding: attrs[:encoding] + } + end + + def commit_response(attrs) + { + file_path: attrs[:file_path], + branch_name: attrs[:branch_name], + } + end + end + resource :projects do # Get file from repository # File content is Base64 encoded @@ -73,17 +93,11 @@ module API required_attributes! [:file_path, :branch_name, :content, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] - branch_name = attrs.delete(:branch_name) - file_path = attrs.delete(:file_path) - result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute + result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute if result[:status] == :success status(201) - - { - file_path: file_path, - branch_name: branch_name - } + commit_response(attrs) else render_api_error!(result[:message], 400) end @@ -105,17 +119,11 @@ module API required_attributes! [:file_path, :branch_name, :content, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] - branch_name = attrs.delete(:branch_name) - file_path = attrs.delete(:file_path) - result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute + result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute if result[:status] == :success status(200) - - { - file_path: file_path, - branch_name: branch_name - } + commit_response(attrs) else http_status = result[:http_status] || 400 render_api_error!(result[:message], http_status) @@ -138,17 +146,11 @@ module API required_attributes! [:file_path, :branch_name, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :commit_message] - branch_name = attrs.delete(:branch_name) - file_path = attrs.delete(:file_path) - result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute + result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute if result[:status] == :success status(200) - - { - file_path: file_path, - branch_name: branch_name - } + commit_response(attrs) else render_api_error!(result[:message], 400) end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 0dab7bcfa4d..581a8c20bdb 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -4,11 +4,7 @@ module Gitlab def initialize(project_id, query, repository_ref = nil) @project = Project.find(project_id) - @repository_ref = if repository_ref.present? - repository_ref - else - nil - end + @repository_ref = repository_ref @query = Shellwords.shellescape(query) if query.present? end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index eadb941a3fa..22237f2e9f2 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -1,14 +1,31 @@ require 'spec_helper' describe Issue, "Mentionable" do - describe :mentioned_users do + describe '#mentioned_users' do let!(:user) { create(:user, username: 'stranger') } let!(:user2) { create(:user, username: 'john') } - let!(:issue) { create(:issue, description: '@stranger mentioned') } + let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } subject { issue.mentioned_users } it { is_expected.to include(user) } it { is_expected.not_to include(user2) } end + + describe '#create_cross_references!' do + let(:project) { create(:project) } + let(:author) { double('author') } + let(:commit) { project.commit } + let(:commit2) { project.commit } + + let!(:issue) do + create(:issue, project: project, description: commit.to_reference) + end + + it 'correctly removes already-mentioned Commits' do + expect(Note).not_to receive(:create_cross_reference_note) + + issue.create_cross_references!(project, author, [commit2]) + end + end end |