diff options
64 files changed, 853 insertions, 316 deletions
diff --git a/CHANGELOG b/CHANGELOG index 5f081236c10..b7e8822fdd6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,8 @@ v 8.5.0 (unreleased) - Don't vendor minified JS - Display 404 error on group not found - Track project import failure + - Support Two-factor Authentication for LDAP users + - Display database type and version in Administration dashboard - Fix visibility level text in admin area (Zeger-Jan van de Weg) - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Update the ExternalIssue regex pattern (Blake Hitchcock) @@ -24,9 +26,13 @@ v 8.5.0 (unreleased) - Fix API to keep request parameters in Link header (Michael Potthoff) - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead + - Prevent parse error when name of project ends with .atom and prevent path issues - Mark inline difference between old and new paths when a file is renamed - Support Akismet spam checking for creation of issues via API (Stan Hu) - Improve UI consistency between projects and groups lists + - Add sort dropdown to dashboard projects page + - Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg) + - In seach autocomplete show only groups and projects you are member of v 8.4.3 - Increase lfs_objects size column to 8-byte integer to allow files larger @@ -179,6 +185,7 @@ v 8.3.0 - Handle and report SSL errors in Web hook test (Stan Hu) - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) + - WIP identifier on merge requests no longer requires trailing space - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - Fix 500 error when update group member permission - Fix: As an admin, cannot add oneself as a member to a group/project @@ -303,8 +303,6 @@ group :production do gem "gitlab_meta", '7.0' end -gem "newrelic_rpm", '~> 3.14' - gem 'octokit', '~> 3.8.0' gem "mail_room", "~> 0.6.1" diff --git a/Gemfile.lock b/Gemfile.lock index bd767016108..a7a5db29e35 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -479,7 +479,6 @@ GEM net-ldap (0.12.1) net-ssh (3.0.1) netrc (0.11.0) - newrelic_rpm (3.14.1.311) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) nprogress-rails (0.1.6.7) @@ -960,7 +959,6 @@ DEPENDENCIES mysql2 (~> 0.3.16) nested_form (~> 0.3.2) net-ssh (~> 3.0.1) - newrelic_rpm (~> 3.14) nokogiri (= 1.6.7.2) nprogress-rails (~> 0.1.6.7) oauth2 (~> 1.0.0) diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index 746fa3cea87..3e0fdb3f795 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -47,7 +47,7 @@ callback(namespaces) # Return projects list. Filtered by query - projects: (query, callback) -> + projects: (query, order, callback) -> url = Api.buildUrl(Api.projects_path) $.ajax( @@ -55,6 +55,7 @@ data: private_token: gon.api_token search: query + order_by: order per_page: 20 dataType: "json" ).done (projects) -> diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index d5e6ff0717a..e54bfce058a 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -215,4 +215,76 @@ $ -> $this = $(this) $this.attr 'value', $this.val() + $(document).on 'breakpoint:change', (e, breakpoint) -> + if breakpoint is 'sm' or breakpoint is 'xs' + $gutterIcon = $('.gutter-toggle').find('i') + if $gutterIcon.hasClass('fa-angle-double-right') + $gutterIcon.closest('a').trigger('click') + + + $(document).on 'click', 'aside .gutter-toggle', (e) -> + e.preventDefault() + $this = $(this) + $thisIcon = $this.find 'i' + if $thisIcon.hasClass('fa-angle-double-right') + $thisIcon.removeClass('fa-angle-double-right') + .addClass('fa-angle-double-left') + $this + .closest('aside') + .removeClass('right-sidebar-expanded') + .addClass('right-sidebar-collapsed') + $('.page-with-sidebar') + .removeClass('right-sidebar-expanded') + .addClass('right-sidebar-collapsed') + else + $thisIcon.removeClass('fa-angle-double-left') + .addClass('fa-angle-double-right') + $this + .closest('aside') + .removeClass('right-sidebar-collapsed') + .addClass('right-sidebar-expanded') + $('.page-with-sidebar') + .removeClass('right-sidebar-collapsed') + .addClass('right-sidebar-expanded') + $.cookie("collapsed_gutter", + $('.right-sidebar') + .hasClass('right-sidebar-collapsed'), { path: '/' }) + + bootstrapBreakpoint = undefined; + checkBootstrapBreakpoints = -> + if $('.device-xs').is(':visible') + bootstrapBreakpoint = "xs" + else if $('.device-sm').is(':visible') + bootstrapBreakpoint = "sm" + else if $('.device-md').is(':visible') + bootstrapBreakpoint = "md" + else if $('.device-lg').is(':visible') + bootstrapBreakpoint = "lg" + + setBootstrapBreakpoints = -> + if $('.device-xs').length + return + + $("body") + .append('<div class="device-xs visible-xs"></div>'+ + '<div class="device-sm visible-sm"></div>'+ + '<div class="device-md visible-md"></div>'+ + '<div class="device-lg visible-lg"></div>') + checkBootstrapBreakpoints() + + fitSidebarForSize = -> + oldBootstrapBreakpoint = bootstrapBreakpoint + checkBootstrapBreakpoints() + if bootstrapBreakpoint != oldBootstrapBreakpoint + $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + + checkInitialSidebarSize = -> + if bootstrapBreakpoint is "xs" or "sm" + $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + + $(window).on "resize", (e) -> + fitSidebarForSize() + + setBootstrapBreakpoints() + checkInitialSidebarSize() new Aside() diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee index 02232698bc2..d17b1123418 100644 --- a/app/assets/javascripts/issuable_context.js.coffee +++ b/app/assets/javascripts/issuable_context.js.coffee @@ -10,19 +10,7 @@ class @IssuableContext $(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(this).submit() - $('.issuable-details').waitForImages -> - $('.issuable-affix').on 'affix.bs.affix', -> - $(@).width($(@).outerWidth()) - .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> - $(@).width('') - - $('.issuable-affix').affix offset: - top: -> - @top = ($('.issuable-affix').offset().top - 70) - bottom: -> - @bottom = $('.footer').outerHeight(true) - - $(".edit-link").click (e) -> + $(document).on "click",".edit-link", (e) -> block = $(@).parents('.block') block.find('.selectbox').show() block.find('.value').hide() diff --git a/app/assets/javascripts/project_select.js.coffee b/app/assets/javascripts/project_select.js.coffee index 0ae274f3363..be8ab9b428d 100644 --- a/app/assets/javascripts/project_select.js.coffee +++ b/app/assets/javascripts/project_select.js.coffee @@ -3,6 +3,7 @@ class @ProjectSelect $('.ajax-project-select').each (i, select) -> @groupId = $(select).data('group-id') @includeGroups = $(select).data('include-groups') + @orderBy = $(select).data('order-by') || 'id' placeholder = "Search for project" placeholder += " or group" if @includeGroups @@ -28,7 +29,7 @@ class @ProjectSelect if @groupId Api.groupProjects @groupId, query.term, projectsCallback else - Api.projects query.term, projectsCallback + Api.projects query.term, @orderBy, projectsCallback id: (project) -> project.web_url diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 11df4c24056..5f193fa7434 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -2,7 +2,7 @@ @include border-radius(3px); font-size: $gl-font-size; font-weight: 500; - padding: $gl-vert-padding $gl-padding; + padding: $gl-vert-padding $gl-btn-padding; &:focus, &:active { diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index e93dbab0c42..08dcb563dce 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -9,7 +9,7 @@ display: block; float: left; - padding: 0 $gl-padding; + padding: 0 $gl-btn-padding; font-weight: normal; margin-right: 10px; font-size: $gl-font-size; diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 0997dfc287c..3bfac2ad9b5 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -116,7 +116,7 @@ display: none; } - aside { + aside:not(.right-sidebar){ display: none; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 540d0b03163..b7f532c0771 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -200,6 +200,14 @@ } } +@mixin expanded-gutter { + padding-right: $gutter_width; +} + +@mixin collapsed-gutter { + padding-right: $sidebar_collapsed_width; +} + @mixin collapsed-sidebar { padding-left: $sidebar_collapsed_width; @@ -266,6 +274,7 @@ background: #f2f6f7; } +// page is small enough @media (max-width: $screen-md-max) { .page-sidebar-collapsed { @include collapsed-sidebar; @@ -275,12 +284,32 @@ @include collapsed-sidebar; } + .page-gutter { + &.right-sidebar-collapsed { + @include collapsed-gutter; + } + &.right-sidebar-expanded { + @include expanded-gutter; + } + } + .collapse-nav { display: none; } } +// page is large enough @media(min-width: $screen-md-max) { + + .page-gutter { + &.right-sidebar-collapsed { + @include collapsed-gutter; + } + &.right-sidebar-expanded { + @include expanded-gutter; + } + } + .page-sidebar-collapsed { @include collapsed-sidebar; } @@ -288,4 +317,4 @@ .page-sidebar-expanded { @include expanded-sidebar; } -} +}
\ No newline at end of file diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3ec48da9a41..44d3d7715d2 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,6 +12,9 @@ $gl-font-size: 15px; $list-font-size: 15px; $sidebar_collapsed_width: 62px; $sidebar_width: 230px; +$gutter_collapsed_width: 62px; +$gutter_width: 312px; +$gutter_inner_width: 280px; $avatar_radius: 50%; $code_font_size: 13px; $code_line_height: 1.5; @@ -22,6 +25,7 @@ $header-height: 58px; $fixed-layout-width: 1280px; $gl-gray: #5a5a5a; $gl-padding: 16px; +$gl-btn-padding: 10px; $gl-vert-padding: 6px; $gl-padding-top:10px; $gl-avatar-size: 46px; @@ -36,11 +40,12 @@ $white-light: #FFFFFF; $white-normal: #ededed; $white-dark: #ededed; -$gray-light: #f7f7f7; -$gray-normal: #ededed; +$gray-light: #faf9f9; +$gray-normal: #f5f5f5; $gray-dark: #ededed; +$gray-darkest: #c9c9c9; -$green-light: #31AF64; +$green-light: #38ae67; $green-normal: #2FAA60; $green-dark: #2CA05B; @@ -52,7 +57,7 @@ $blue-medium-light: #3498CB; $blue-medium: #2F8EBF; $blue-medium-dark: #2D86B4; -$orange-light: #FC6443; +$orange-light: rgba(252, 109, 38, 0.80); $orange-normal: #E75E40; $orange-dark: #CE5237; @@ -64,8 +69,8 @@ $border-white-light: #F1F2F4; $border-white-normal: #D6DAE2; $border-white-dark: #C6CACF; -$border-gray-light: #d1d1d1; -$border-gray-normal: #D6DAE2; +$border-gray-light: rgba(0, 0, 0, 0.06); +$border-gray-normal: rgba(0, 0, 0, 0.10);; $border-gray-dark: #C6CACF; $border-green-light: #2FAA60; @@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8; $border-blue-normal: #2897CE; $border-blue-dark: #258DC1; -$border-orange-light: #ED5C3D; +$border-orange-light: #fc6d26; $border-orange-normal: #CE5237; $border-orange-dark: #C14E35; diff --git a/app/assets/stylesheets/pages/explore.scss b/app/assets/stylesheets/pages/explore.scss index da06fe9954e..9b92128624c 100644 --- a/app/assets/stylesheets/pages/explore.scss +++ b/app/assets/stylesheets/pages/explore.scss @@ -6,11 +6,3 @@ font-size: 30px; } } - -.explore-trending-block { - .lead { - line-height: 32px; - font-size: 18px; - margin-top: 10px; - } -} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 977ada0ff38..3bfbd9e17b7 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -42,8 +42,6 @@ .issuable-details { section { - border-right: 1px solid $border-white-light; - .issuable-discussion { margin-right: 1px; } @@ -73,11 +71,35 @@ .block { @include clearfix; padding: $gl-padding 0; - border-bottom: 1px solid #F0F0F0; + border-bottom: 1px solid $border-gray-light; + // This prevents the mess when resizing the sidebar + // of elements repositioning themselves.. + width: $gutter_inner_width; + overflow-x: hidden; + // -- + + &:first-child { + padding-top: 5px; + } &:last-child { border: none; } + + span { + margin-top: 7px; + display: inline-block; + } + + .issuable-count { + + } + + .gutter-toggle { + margin-left: 20px; + border-left: 1px solid $border-gray-light; + padding-left: 10px; + } } .title { @@ -133,3 +155,92 @@ margin-right: 2px; } } + + +.right-sidebar { + position: fixed; + top: 58px; + right: 0; + height: 100%; + transition-duration: .3s; + background: $gray-light; + overflow: scroll; + padding: 10px 20px; + + &.right-sidebar-expanded { + width: $gutter_width; + + hr { + display: none; + } + } + + .subscribe-button { + span { + margin-top: 0; + } + } + + &.right-sidebar-collapsed { + width: $sidebar_collapsed_width; + padding-top: 0; + overflow-x: hidden; + + hr { + margin: 0; + color: $gray-normal; + border-color: $gray-normal; + width: 62px; + margin-left: -20px + } + + .block { + border-bottom: none; + padding: 15px 0 0 0; + } + } + + .btn { + background: $gray-normal; + border: 1px solid $border-gray-normal; + } + + &.right-sidebar-collapsed { + .issuable-count, + .issuable-nav, + .assignee > *, + .milestone > *, + .labels > *, + .participants > *, + .light > *, + .project-reference > * { + display: none; + } + + .gutter-toggle { + margin-left: -$gutter_inner_width + 4; + } + + .sidebar-collapsed-icon { + display: block; + float: left; + width: 62px; + text-align: center; + margin-left: -19px; + padding-bottom: 10px; + color: #999999; + + span { + display: block; + margin-top: 0; + } + } + + } + + &.right-sidebar-expanded { + .sidebar-collapsed-icon { + display: none; + } + } +}
\ No newline at end of file diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb index 0b7fcdf5e9e..721e2a6bcbd 100644 --- a/app/controllers/dashboard/projects_controller.rb +++ b/app/controllers/dashboard/projects_controller.rb @@ -3,6 +3,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController def index @projects = current_user.authorized_projects.sorted_by_activity.non_archived + @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.includes(:namespace) @last_push = current_user.recent_push diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 4c13228fce9..9cf76521a0d 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,5 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController + include AuthenticatesWithTwoFactor protect_from_forgery except: [:kerberos, :saml, :cas3] @@ -29,8 +30,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Do additional LDAP checks for the user filter and EE features if ldap_user.allowed? - log_audit_event(@user, with: :ldap) - sign_in_and_redirect(@user) + if @user.two_factor_enabled? + prompt_for_two_factor(@user) + else + log_audit_event(@user, with: :ldap) + sign_in_and_redirect(@user) + end else flash[:alert] = "Access denied for your LDAP account." redirect_to new_user_session_path diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a2458ad3be0..14f098d8355 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -293,6 +293,76 @@ module ApplicationHelper end end + def issuable_link_next(project,issuable) + if project.nil? + nil + elsif current_controller?(:issues) + namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid)) + elsif current_controller?(:merge_requests) + namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid)) + end + end + + def issuable_link_prev(project,issuable) + if project.nil? + nil + elsif current_controller?(:issues) + namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid)) + elsif current_controller?(:merge_requests) + namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid)) + end + end + + def issuable_count(entity, project) + if project.nil? + 0 + elsif current_controller?(:issues) + project.issues.send(entity).count + elsif current_controller?(:merge_requests) + project.merge_requests.send(entity).count + end + end + + def next_issuable_for(project, id) + if project.nil? + nil + elsif current_controller?(:issues) + project.issues.where("id > ?", id).last + elsif current_controller?(:merge_requests) + project.merge_requests.where("id > ?", id).last + end + end + + def has_next_issuable?(project, id) + if project.nil? + nil + elsif current_controller?(:issues) + project.issues.where("id > ?", id).last + elsif current_controller?(:merge_requests) + project.merge_requests.where("id > ?", id).last + end + end + + def prev_issuable_for(project, id) + if project.nil? + nil + elsif current_controller?(:issues) + project.issues.where("id < ?", id).first + elsif current_controller?(:merge_requests) + project.merge_requests.where("id < ?", id).first + end + end + + def has_prev_issuable?(project, id) + if project.nil? + nil + elsif current_controller?(:issues) + project.issues.where("id < ?", id).first + elsif current_controller?(:merge_requests) + project.merge_requests.where("id < ?", id).first + end + end + def state_filters_text_for(entity, project) titles = { opened: "Open" diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb index 0d291f9a87e..3648757428b 100644 --- a/app/helpers/explore_helper.rb +++ b/app/helpers/explore_helper.rb @@ -10,8 +10,19 @@ module ExploreHelper options = exist_opts.merge(options) - path = explore_projects_path + path = if explore_controller? + explore_projects_path + elsif current_action?(:starred) + starred_dashboard_projects_path + else + dashboard_projects_path + end + path << "?#{options.to_param}" path end + + def explore_controller? + controller.class.name.split("::").first == "Explore" + end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index e6fb8670e57..2c299d1d794 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -3,6 +3,18 @@ module NavHelper cookies[:collapsed_nav] == 'true' end + def sidebar_gutter_collapsed_class + if cookies[:collapsed_gutter] == 'true' + "right-sidebar-collapsed" + else + "right-sidebar-expanded" + end + end + + def sidebar_gutter_collapsed? + cookies[:collapsed_gutter] == 'true' + end + def nav_sidebar_class if nav_menu_collapsed? "sidebar-collapsed" @@ -19,6 +31,17 @@ module NavHelper end end + def page_gutter_class + + if current_path?('merge_requests#show') || current_path?('issues#show') + if cookies[:collapsed_gutter] == 'true' + "page-gutter right-sidebar-collapsed" + else + "page-gutter right-sidebar-expanded" + end + end + end + def nav_header_class if nav_menu_collapsed? "header-collapsed" diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index e7e472cbb5b..2e9741a8622 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -20,6 +20,12 @@ module ProjectsHelper end end + def link_to_member_avatar(author, opts = {}) + default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } + opts = default_opts.merge(opts) + image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar] + end + def link_to_member(project, author, opts = {}) default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } opts = default_opts.merge(opts) @@ -57,7 +63,11 @@ module ProjectsHelper link_output = simple_sanitize(project.name) link_output += content_tag :span, nil, { class: "fa fa-chevron-down dropdown-toggle-caret" } if current_user - link_output += project_select_tag :project_path, class: "project-item-select js-projects-dropdown", data: { include_groups: false } if current_user + if current_user + link_output += project_select_tag :project_path, + class: "project-item-select js-projects-dropdown", + data: { include_groups: false, order_by: 'last_activity_at' } + end link_output end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index d4f78258626..1eb790b1796 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -70,7 +70,7 @@ module SearchHelper # Autocomplete results for the current user's groups def groups_autocomplete(term, limit = 5) - Group.search(term).limit(limit).map do |group| + current_user.authorized_groups.search(term).limit(limit).map do |group| { label: "group: #{search_result_sanitize(group.name)}", url: group_path(group) @@ -80,7 +80,7 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete(term, limit = 5) - ProjectsFinder.new.execute(current_user).search_by_title(term). + current_user.authorized_projects.search_by_title(term). sorted_by_stars.non_archived.limit(limit).map do |p| { label: "project: #{search_result_sanitize(p.name_with_namespace)}", diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 89b6c49b362..ddc476447ca 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -258,7 +258,7 @@ class MergeRequest < ActiveRecord::Base end def work_in_progress? - !!(title =~ /\A\[?WIP\]?:? /i) + !!(title =~ /\A\[?WIP(\]|:| )/i) end def mergeable? @@ -284,7 +284,8 @@ class MergeRequest < ActiveRecord::Base def can_remove_source_branch?(current_user) !source_project.protected_branch?(source_branch) && !source_project.root_ref?(source_branch) && - Ability.abilities.allowed?(current_user, :push_code, source_project) + Ability.abilities.allowed?(current_user, :push_code, source_project) && + last_commit == source_project.commit(source_branch) end def mr_and_commit_notes diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index cc389c3ae08..3274ba5377b 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -92,6 +92,11 @@ Rails %span.pull-right #{Rails::VERSION::STRING} + + %p + = Gitlab::Database.adapter_name + %span.pull-right + = Gitlab::Database.version %hr .row .col-sm-4 diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 726f669b1d2..d865a2c6fae 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -13,7 +13,8 @@ Explore Projects .nav-controls - = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs input-short', spellcheck: false + = render 'explore/projects/dropdown' - if current_user.can_create_project? = link_to new_project_path, class: 'btn btn-new' do = icon('plus') diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index eb2979fc13e..917bfbd47e9 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -1,12 +1,12 @@ - page_title "Milestones" - header_title "Milestones", dashboard_milestones_path -.project-issuable-filter - .controls - = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true - +.top-area = render 'shared/milestones_filter' + .nav-controls + = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true + .milestones %ul.content-list - if @milestones.blank? diff --git a/app/views/explore/projects/_dropdown.html.haml b/app/views/explore/projects/_dropdown.html.haml index a988d4c8154..87c556adc7d 100644 --- a/app/views/explore/projects/_dropdown.html.haml +++ b/app/views/explore/projects/_dropdown.html.haml @@ -3,19 +3,13 @@ %span.light - if @sort.present? = sort_options_hash[@sort] - - elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path) - Trending projects - - elsif current_page?(starred_explore_projects_path) - Most stars - else - = sort_title_recently_created + = sort_title_recently_updated %b.caret %ul.dropdown-menu %li - = link_to trending_explore_projects_path do - Trending projects - = link_to starred_explore_projects_path do - Most stars + = link_to explore_projects_filter_path(sort: sort_value_name) do + = sort_title_name = link_to explore_projects_filter_path(sort: sort_value_recently_created) do = sort_title_recently_created = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 28b12c8dca8..66a4b535ae5 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -2,6 +2,7 @@ = 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", id: "projects_search", spellcheck: false + = hidden_field_tag :sort, @sort .form-group = button_tag 'Search', class: "btn" @@ -46,4 +47,3 @@ = link_to explore_projects_filter_path(tag: tag.name) do %i.fa.fa-tag = tag.name - = render 'explore/projects/dropdown' diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml new file mode 100644 index 00000000000..614b5431779 --- /dev/null +++ b/app/views/explore/projects/_nav.html.haml @@ -0,0 +1,10 @@ +%ul.nav-links + = nav_link(page: [trending_explore_projects_path, explore_root_path]) do + = link_to trending_explore_projects_path do + Trending + = nav_link(page: starred_explore_projects_path) do + = link_to starred_explore_projects_path do + Most stars + = nav_link(page: explore_projects_path) do + = link_to explore_projects_path do + All diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index b9a958fbe7b..bee8518d57a 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -6,7 +6,11 @@ - else = render 'explore/head' -.gray-content-block.clearfix.second-block +.top-area + = render 'explore/projects/nav' + +.gray-content-block.second-block.clearfix = render 'filter' + = render 'projects', projects: @projects = paginate @projects, theme: "gitlab" diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index 95d46e331f8..16f52f7a530 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -6,12 +6,6 @@ - else = render 'explore/head' -.explore-trending-block - .gray-content-block.second-block - .pull-right - = render 'explore/projects/dropdown' - .oneline - %i.fa.fa-star - See most starred projects - = render 'projects', projects: @starred_projects - = paginate @starred_projects, theme: 'gitlab' += render 'explore/projects/nav' += render 'projects', projects: @starred_projects += paginate @starred_projects, theme: 'gitlab' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index fa0b718e48b..adcda810061 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -6,11 +6,5 @@ - else = render 'explore/head' -.explore-trending-block - .gray-content-block.second-block - .pull-right - = render 'explore/projects/dropdown' - .oneline - %i.fa.fa-comments-o - See most discussed projects for last month - = render 'projects', projects: @trending_projects += render 'explore/projects/nav' += render 'projects', projects: @trending_projects diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index b221d3a89a4..ab307708b75 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,17 +1,15 @@ - page_title "Milestones" - header_title group_title(@group, "Milestones", group_milestones_path(@group)) -.project-issuable-filter - .controls - - if can?(current_user, :admin_milestones, @group) - .pull-right - %span.pull-right.hidden-xs - = link_to new_group_milestone_path(@group), class: "btn btn-new" do - = icon('plus') - New Milestone - +.top-area = render 'shared/milestones_filter' + .nav-controls + - if can?(current_user, :admin_milestones, @group) + = link_to new_group_milestone_path(@group), class: "btn btn-new" do + = icon('plus') + New Milestone + .gray-content-block Only milestones from %strong #{@group.name} diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 7b45bd09050..746386cab58 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -139,7 +139,31 @@ %h2#navs Navigation %h4 + %code .top-area + %p Holder for top page navigation. Includes navigation, search field, sorting and button + + .example + .top-area + %ul.nav-links + %li.active + %a Open + %li + %a Closed + .nav-controls + = text_field_tag 'sample', nil, class: 'form-control' + .dropdown + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span Sort by name + %b.caret + %ul.dropdown-menu + %li + %a Sort by date + + = link_to 'New issue', '#', class: 'btn btn-new' + + %h4 %code .nav-links + %p Only nav links without button and search .example %ul.nav-links %li.active diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 26159989777..0c1b5eec95a 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,4 +1,4 @@ -.page-with-sidebar{ class: page_sidebar_class } +.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } = render "layouts/broadcast" .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .header-logo diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 52bfc595fda..9fa96084f94 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -31,34 +31,33 @@ - else = f.submit 'Generate', class: "btn btn-default" - - unless current_user.ldap_user? - .panel.panel-default - .panel-heading - Two-factor Authentication - .panel-body - - if current_user.two_factor_enabled? - .pull-right - = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', - data: { confirm: 'Are you sure?' } - %p.text-success - %strong - Two-factor Authentication is enabled - %p - If you lose your recovery codes you can - %strong - = succeed ',' do - = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } - invalidating all previous codes. + .panel.panel-default + .panel-heading + Two-factor Authentication + .panel-body + - if current_user.two_factor_enabled? + .pull-right + = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', + data: { confirm: 'Are you sure?' } + %p.text-success + %strong + Two-factor Authentication is enabled + %p + If you lose your recovery codes you can + %strong + = succeed ',' do + = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } + invalidating all previous codes. - - else - %p - Increase your account's security by enabling two-factor authentication (2FA). - %p - Each time you log in you’ll be required to provide your username and - password as usual, plus a randomly-generated code from your phone. + - else + %p + Increase your account's security by enabling two-factor authentication (2FA). + %p + Each time you log in you’ll be required to provide your username and + password as usual, plus a randomly-generated code from your phone. - .form-actions - = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' + .form-actions + = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' - if button_based_providers.any? .panel.panel-default diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index bbb6944a65a..630d0286f25 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -1,18 +1,7 @@ - page_title "Builds" = render "header_title" -.project-issuable-filter - .controls - - if can?(current_user, :manage_builds, @project) - .pull-left.hidden-xs - - if @all_builds.running_or_pending.any? - = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), - data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - - = link_to ci_lint_path, class: 'btn btn-default' do - = icon('wrench') - %span CI Lint - +.top-area %ul.nav-links %li{class: ('active' if @scope.nil?)} = link_to project_builds_path(@project) do @@ -32,6 +21,16 @@ %span.badge.js-running-count = number_with_delimiter(@all_builds.finished.count(:id)) + .nav-controls + - if can?(current_user, :manage_builds, @project) + - if @all_builds.running_or_pending.any? + = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), + data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post + + = link_to ci_lint_path, class: 'btn btn-default' do + = icon('wrench') + %span CI Lint + .gray-content-block #{(@scope || 'running').capitalize} builds from this project diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 51dcca7a1ab..030f4a2e644 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -54,11 +54,8 @@ = render 'votes/votes_block', votable: @issue .row - %section.col-md-9 + %section.col-md-12 .issuable-discussion = render 'projects/issues/discussion' - %aside.col-md-3 - = render 'shared/issuable/sidebar', issuable: @issue - - = render 'shared/show_aside' += render 'shared/issuable/sidebar', issuable: @issue
\ No newline at end of file diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index 2f0f3fcfb06..a54733883b4 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -1,3 +1,3 @@ -$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"); -$('.issuable-sidebar').parent().effect('highlight') -new Issue(); +$('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"; +$('aside.right-sidebar').effect('highlight'); +new Issue();
\ No newline at end of file diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 9081bcfe9b3..cc41130a9dc 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,13 +1,14 @@ - page_title "Labels" = render "header_title" -.gray-content-block.top-block - - if can? current_user, :admin_label, @project - = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do - = icon('plus') - New label - .oneline +.top-area + .nav-text Labels can be applied to issues and merge requests. + .nav-controls + - if can? current_user, :admin_label, @project + = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do + = icon('plus') + New label .labels - if @labels.present? diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 8641c3d8b4b..da67645bc2b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -70,12 +70,9 @@ = render 'votes/votes_block', votable: @merge_request .row - %section.col-md-9 + %section.col-md-12 .issuable-discussion = render "projects/merge_requests/discussion" - %aside.col-md-3 - = render 'shared/issuable/sidebar', issuable: @merge_request - = render 'shared/show_aside' #commits.commits.tab-pane - # This tab is always loaded via AJAX @@ -87,6 +84,8 @@ .mr-loading-status = spinner += render 'shared/issuable/sidebar', issuable: @merge_request + :javascript var merge_request; diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml index 93db65ddf79..ce5157d69a2 100644 --- a/app/views/projects/merge_requests/update.js.haml +++ b/app/views/projects/merge_requests/update.js.haml @@ -1,3 +1,3 @@ -$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"); -$('.issuable-sidebar').parent().effect('highlight') +$('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"; +$('aside.right-sidebar').effect('highlight') merge_request = new MergeRequest(); diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index aa185126b56..abe567af1dd 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -2,15 +2,15 @@ = render "header_title" -.project-issuable-filter - .controls +.top-area + = render 'shared/milestones_filter' + + .nav-controls - if can?(current_user, :admin_milestone, @project) - = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do - %i.fa.fa-plus + = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do + = icon('plus') New Milestone - = render 'shared/milestones_filter' - .milestones %ul.content-list = render @milestones diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 69ba301e231..56a53ffff2a 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,12 +1,4 @@ -.project-issuable-filter - .controls - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - %i.fa.fa-plus - New Page - - = render 'projects/wikis/new' - +.top-area %ul.nav-links = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) @@ -17,3 +9,11 @@ = nav_link(path: 'wikis#git_access') do = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do Git Access + + .nav-controls + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + = icon('plus') + New Page + + = render 'projects/wikis/new' diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index f77feeb79cd..cf16c203f9c 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,11 +1,10 @@ -.milestones-filters - %ul.nav-links - %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} - = link_to milestones_filter_path(state: 'opened') do - Open - %li{class: ("active" if params[:state] == 'closed')} - = link_to milestones_filter_path(state: 'closed') do - Closed - %li{class: ("active" if params[:state] == 'all')} - = link_to milestones_filter_path(state: 'all') do - All +%ul.nav-links + %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} + = link_to milestones_filter_path(state: 'opened') do + Open + %li{class: ("active" if params[:state] == 'closed')} + = link_to milestones_filter_path(state: 'closed') do + Closed + %li{class: ("active" if params[:state] == 'all')} + = link_to milestones_filter_path(state: 'all') do + All diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml index 46095912821..1c58345278a 100644 --- a/app/views/shared/_new_project_item_select.html.haml +++ b/app/views/shared/_new_project_item_select.html.haml @@ -1,6 +1,6 @@ - if @projects.any? .prepend-left-10.project-item-select-holder - = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups] } + = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' } %a.btn.btn-new.new-project-item-select-button = icon('plus') = local_assigns[:label] diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml index da6bacbb74a..ea61935487c 100644 --- a/app/views/shared/issuable/_participants.html.haml +++ b/app/views/shared/issuable/_participants.html.haml @@ -1,4 +1,8 @@ .block.participants + .sidebar-collapsed-icon + = icon('users') + %span + = participants.count .title = pluralize participants.count, "participant" - participants.each do |participant| diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 3092ff54242..cab500d7244 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -1,88 +1,132 @@ -.issuable-sidebar.issuable-affix - = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| - .block.assignee - .title - %label - Assignee - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - .pull-right - = link_to 'Edit', '#', class: 'edit-link' - .value - - if issuable.assignee - %strong= link_to_member(@project, issuable.assignee, size: 24) - - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) - %a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'} - = icon('exclamation-triangle') +%aside.right-sidebar{ class: sidebar_gutter_collapsed_class } + .issuable-sidebar + .block + %span.issuable-count.pull-left + = issuable.iid + of + = issuable_count(:all, @project) + %span.pull-right + %a.gutter-toggle{href: '#'} + - if sidebar_gutter_collapsed? + = icon('angle-double-left') + - else + = icon('angle-double-right') + .issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'} + - if has_prev_issuable?(@project, issuable.id) + = link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default' - else - .light None + %a.btn.btn-default.disabled{href: '#'} + Prev + - if has_next_issuable?(@project, issuable.id) + = link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default' + - else + %a.btn.btn-default.disabled{href: '#'} + Next - .selectbox - = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true) + = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| + .block.assignee + .sidebar-collapsed-icon + - if issuable.assignee + = link_to_member_avatar(issuable.assignee, size: 24) + - else + = icon('user') + .title + %label + Assignee + - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + .pull-right + = link_to 'Edit', '#', class: 'edit-link' + .value + - if issuable.assignee + %strong= link_to_member(@project, issuable.assignee, size: 24) + - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee) + %a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'} + = icon('exclamation-triangle') + - else + .light None - .block.milestone - .title - %label - Milestone - - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - .pull-right - = link_to 'Edit', '#', class: 'edit-link' - .value - - if issuable.milestone - %span.back-to-milestone - = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do - %strong - = icon('clock-o') - = issuable.milestone.title - - else - .light None - .selectbox - = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }}) - = hidden_field_tag :issuable_context - = f.submit class: 'btn hide' + .selectbox + = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true) - - if issuable.project.labels.any? - .block + .block.milestone + .sidebar-collapsed-icon + = icon('balance-scale') + %span + - if issuable.milestone + = issuable.milestone.title + - else + No .title - %label Labels + %label + Milestone - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .pull-right = link_to 'Edit', '#', class: 'edit-link' - .value.issuable-show-labels - - if issuable.labels.any? - - issuable.labels.each do |label| - = link_to_label(label) + .value + - if issuable.milestone + %span.back-to-milestone + = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do + %strong + = icon('clock-o') + = issuable.milestone.title - else .light None .selectbox - = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, - { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" } + = f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }}) + = hidden_field_tag :issuable_context + = f.submit class: 'btn hide' - = render "shared/issuable/participants", participants: issuable.participants(current_user) + - if issuable.project.labels.any? + .block.labels + .sidebar-collapsed-icon + = icon('tags') + %span + = issuable.labels.count + .title + %label Labels + - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + .pull-right + = link_to 'Edit', '#', class: 'edit-link' + .value.issuable-show-labels + - if issuable.labels.any? + - issuable.labels.each do |label| + = link_to_label(label) + - else + .light None + .selectbox + = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, + { selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" } - - if current_user - - subscribed = issuable.subscribed?(current_user) - .block.light - .title - %label.light Notifications - - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'} - %span= subscribed ? 'Unsubscribe' : 'Subscribe' - .subscription-status{data: {status: subscribtion_status}} - .unsubscribed{class: ( 'hidden' if subscribed )} - You're not receiving notifications from this thread. - .subscribed{class: ( 'hidden' unless subscribed )} - You're receiving notifications because you're subscribed to this thread. + = render "shared/issuable/participants", participants: issuable.participants(current_user) + %hr + - if current_user + - subscribed = issuable.subscribed?(current_user) + .block.light + .sidebar-collapsed-icon + = icon('rss') + .title + %label.light Notifications + - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' + %button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'} + %span= subscribed ? 'Unsubscribe' : 'Subscribe' + .subscription-status{data: {status: subscribtion_status}} + .unsubscribed{class: ( 'hidden' if subscribed )} + You're not receiving notifications from this thread. + .subscribed{class: ( 'hidden' unless subscribed )} + You're receiving notifications because you're subscribed to this thread. - - project_ref = cross_project_reference(@project, issuable) - .block - .title - .cross-project-reference - %span - Reference: - %cite{title: project_ref} - = project_ref - = clipboard_button(clipboard_text: project_ref) + - project_ref = cross_project_reference(@project, issuable) + .block.project-reference + .sidebar-collapsed-icon + = icon('clipboard') + .title + .cross-project-reference + %span + Reference: + %cite{title: project_ref} + = project_ref + = clipboard_button(clipboard_text: project_ref) - :javascript - new Subscription("#{toggle_subscription_path(issuable)}"); - new IssuableContext();
\ No newline at end of file + :javascript + new Subscription("#{toggle_subscription_path(issuable)}"); + new IssuableContext();
\ No newline at end of file diff --git a/config/newrelic.yml b/config/newrelic.yml deleted file mode 100644 index 9ef922a38d9..00000000000 --- a/config/newrelic.yml +++ /dev/null @@ -1,16 +0,0 @@ -# New Relic configuration file -# -# This file is here to make sure the New Relic gem stays -# quiet by default. -# -# To enable and configure New Relic, please use -# environment variables, e.g. NEW_RELIC_ENABLED=true - -production: - enabled: false - -development: - enabled: false - -test: - enabled: false diff --git a/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb b/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb new file mode 100644 index 00000000000..091de54978b --- /dev/null +++ b/db/migrate/20160129135155_remove_dot_atom_path_ending_of_projects.rb @@ -0,0 +1,80 @@ +class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration + include Gitlab::ShellAdapter + + class ProjectPath + attr_reader :old_path, :id, :namespace_path + + def initialize(old_path, id, namespace_path, namespace_id) + @old_path = old_path + @id = id + @namespace_path = namespace_path + @namespace_id = namespace_id + end + + def clean_path + @_clean_path ||= PathCleaner.clean(@old_path, @namespace_id) + end + end + + class PathCleaner + def initialize(path, namespace_id) + @namespace_id = namespace_id + @path = path + end + + def self.clean(*args) + new(*args).clean + end + + def clean + path = cleaned_path + count = 0 + while path_exists?(path) + path = "#{cleaned_path}#{count}" + count += 1 + end + path + end + + private + + def cleaned_path + @_cleaned_path ||= @path.gsub(/\.atom\z/, '-atom') + end + + def path_exists?(path) + Project.find_by_path_and_namespace_id(path, @namespace_id) + end + end + + def projects_with_dot_atom + select_all("SELECT p.id, p.path, n.path as namespace_path, n.id as namespace_id FROM projects p inner join namespaces n on n.id = p.namespace_id WHERE lower(p.path) LIKE '%.atom'") + end + + def up + projects_with_dot_atom.each do |project| + project_path = ProjectPath.new(project['path'], project['id'], project['namespace_path'], project['namespace_id']) + clean_path(project_path) if rename_project_repo(project_path) + end + end + + private + + def clean_path(project_path) + execute "UPDATE projects SET path = #{sanitize(project_path.clean_path)} WHERE id = #{project_path.id}" + end + + def rename_project_repo(project_path) + old_path_with_namespace = File.join(project_path.namespace_path, project_path.old_path) + new_path_with_namespace = File.join(project_path.namespace_path, project_path.clean_path) + + gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") + gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace) + rescue + false + end + + def sanitize(value) + ActiveRecord::Base.connection.quote(value) + end +end diff --git a/db/schema.rb b/db/schema.rb index d546e06cd8a..d4710346b82 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160128233227) do +ActiveRecord::Schema.define(version: 20160129135155) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md index 00edfc97ed9..194b8e00299 100644 --- a/doc/customization/issue_closing.md +++ b/doc/customization/issue_closing.md @@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that For example: ``` -git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23." +git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23." ``` will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages. diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb index 036bc0a499e..4bc670fdfcb 100644 --- a/features/steps/project/builds/summary.rb +++ b/features/steps/project/builds/summary.rb @@ -13,7 +13,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps end step 'I see button to CI Lint' do - page.within('.controls') do + page.within('.nav-controls') do ci_lint_tool_link = page.find_link('CI Lint') expect(ci_lint_tool_link[:href]).to eq ci_lint_path end diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index d1e11eedec3..04ddfe53ed6 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -8,14 +8,7 @@ module Banzai # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. class SanitizationFilter < HTML::Pipeline::SanitizationFilter def whitelist - # Descriptions are more heavily sanitized, allowing only a few elements. - # See http://git.io/vkuAN - if context[:inline_sanitization] - whitelist = LIMITED - whitelist[:elements] -= %w(pre code img ol ul li) - else - whitelist = super - end + whitelist = super customize_whitelist(whitelist) diff --git a/lib/banzai/pipeline/description_pipeline.rb b/lib/banzai/pipeline/description_pipeline.rb index 20e24ace352..f2395867658 100644 --- a/lib/banzai/pipeline/description_pipeline.rb +++ b/lib/banzai/pipeline/description_pipeline.rb @@ -4,9 +4,20 @@ module Banzai def self.transform_context(context) super(context).merge( # SanitizationFilter - inline_sanitization: true + whitelist: whitelist ) end + + private + + def self.whitelist + # Descriptions are more heavily sanitized, allowing only a few elements. + # See http://git.io/vkuAN + whitelist = Banzai::Filter::SanitizationFilter::LIMITED + whitelist[:elements] -= %w(pre code img ol ul li) + + whitelist + end end end end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 4c15d58d680..f751458ac66 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -47,7 +47,7 @@ module Gitlab # new_path - new project path with namespace # # Ex. - # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") + # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new") # def mv_repository(path, new_path) Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index de77a6fbff1..6ebb8027553 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -1,16 +1,23 @@ module Gitlab module Database + def self.adapter_name + connection.adapter_name + end + def self.mysql? - ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2' + adapter_name.downcase == 'mysql2' end def self.postgresql? - ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql' + adapter_name.downcase == 'postgresql' + end + + def self.version + database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1] end def true_value - case ActiveRecord::Base.connection.adapter_name.downcase - when 'postgresql' + if self.class.postgresql? "'t'" else 1 @@ -18,12 +25,31 @@ module Gitlab end def false_value - case ActiveRecord::Base.connection.adapter_name.downcase - when 'postgresql' + if self.class.postgresql? "'f'" else 0 end end + + private + + def self.connection + ActiveRecord::Base.connection + end + + def self.database_version + row = connection.execute("SELECT VERSION()").first + + if postgresql? + row['version'] + else + row.first + end + end + + def connection + self.class.connection + end end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 5c35c5b1450..ace906a6f59 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -34,12 +34,12 @@ module Gitlab def project_path_regex - @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze + @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze end def project_path_regex_message "can contain only letters, digits, '_', '-' and '.'. " \ - "Cannot start with '-' or end in '.git'" \ + "Cannot start with '-', end in '.git' or end in '.atom'" \ end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 665526fde93..6eee4dfe229 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -86,6 +86,14 @@ describe ProjectsController do end end end + + context "when the url contains .atom" do + let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') } + + it 'expect an error creating the project' do + expect(public_project_with_dot_atom).not_to be_valid + end + end end describe "#destroy" do diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index d37bd103714..5b6f3cb3f15 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -18,7 +18,7 @@ describe "Builds" do visit namespace_project_builds_path(@project.namespace, @project, scope: :running) end - it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') } + it { expect(page).to have_selector('.nav-links li.active', text: 'Running') } it { expect(page).to have_link 'Cancel running' } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } @@ -31,7 +31,7 @@ describe "Builds" do visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) end - it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') } + it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') } it { expect(page).to have_content 'No builds to show' } it { expect(page).to have_link 'Cancel running' } end @@ -42,7 +42,7 @@ describe "Builds" do visit namespace_project_builds_path(@project.namespace, @project) end - it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } + it { expect(page).to have_selector('.nav-links li.active', text: 'All') } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } @@ -57,7 +57,7 @@ describe "Builds" do click_link "Cancel running" end - it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } + it { expect(page).to have_selector('.nav-links li.active', text: 'All') } it { expect(page).to have_content 'canceled' } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index f0d553f5f1d..601b6915e27 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -42,9 +42,9 @@ describe SearchHelper do expect(search_autocomplete_opts(project.name).size).to eq(1) end - it "includes the public group" do + it "should not include the public group" do group = create(:group) - expect(search_autocomplete_opts(group.name).size).to eq(1) + expect(search_autocomplete_opts(group.name).size).to eq(0) end context "with a current project" do diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 9c63d227044..e14a6dbf922 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -177,26 +177,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do expect(act.to_html).to eq exp end end - - context 'when inline_sanitization is true' do - it 'uses a stricter whitelist' do - doc = filter('<h1>Description</h1>', inline_sanitization: true) - expect(doc.to_html.strip).to eq 'Description' - end - - %w(pre code img ol ul li).each do |elem| - it "removes '#{elem}' elements" do - act = "<#{elem}>Description</#{elem}>" - expect(filter(act, inline_sanitization: true).to_html.strip). - to eq 'Description' - end - end - - %w(b i strong em a ins del sup sub p).each do |elem| - it "still allows '#{elem}' elements" do - exp = act = "<#{elem}>Description</#{elem}>" - expect(filter(act, inline_sanitization: true).to_html).to eq exp - end - end - end end diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb new file mode 100644 index 00000000000..76f42071810 --- /dev/null +++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +describe Banzai::Pipeline::DescriptionPipeline do + def parse(html) + # When we pass HTML to Redcarpet, it gets wrapped in `p` tags... + # ...except when we pass it pre-wrapped text. Rabble rabble. + unwrap = !html.start_with?('<p>') + + output = described_class.to_html(html, project: spy) + + output.gsub!(%r{\A<p>(.*)</p>(.*)\z}, '\1\2') if unwrap + + output + end + + it 'uses a limited whitelist' do + doc = parse('# Description') + + expect(doc.strip).to eq 'Description' + end + + %w(pre code img ol ul li).each do |elem| + it "removes '#{elem}' elements" do + act = "<#{elem}>Description</#{elem}>" + + expect(parse(act).strip).to eq 'Description' + end + end + + %w(b i strong em a ins del sup sub p).each do |elem| + it "still allows '#{elem}' elements" do + exp = act = "<#{elem}>Description</#{elem}>" + + expect(parse(act).strip).to eq exp + end + end +end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 8461e8ce50d..bd8688fefa1 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do it { is_expected.to satisfy { |val| val == true || val == false } } end + + describe '.version' do + context "on mysql" do + it "extracts the version number" do + allow(described_class).to receive(:database_version). + and_return("5.7.12-standard") + + expect(described_class.version).to eq '5.7.12-standard' + end + end + + context "on postgresql" do + it "extracts the version number" do + allow(described_class).to receive(:database_version). + and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0") + + expect(described_class.version).to eq '9.4.4' + end + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 46f2f20b986..c61ddf01118 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -188,6 +188,11 @@ describe MergeRequest, models: true do expect(subject).to be_work_in_progress end + it "detects the '[WIP]' prefix" do + subject.title = "[WIP]#{subject.title}" + expect(subject).to be_work_in_progress + end + it "doesn't detect WIP for words starting with WIP" do subject.title = "Wipwap #{subject.title}" expect(subject).not_to be_work_in_progress @@ -226,9 +231,15 @@ describe MergeRequest, models: true do expect(subject.can_remove_source_branch?(user2)).to be_falsey end - it "is can be removed in all other cases" do + it "can be removed if the last commit is the head of the source branch" do + allow(subject.source_project).to receive(:commit).and_return(subject.last_commit) + expect(subject.can_remove_source_branch?(user)).to be_truthy end + + it "cannot be removed if the last commit is not also the head of the source branch" do + expect(subject.can_remove_source_branch?(user)).to be_falsey + end end describe "#reset_merge_when_build_succeeds" do |