From 52d2fd49b5d913d40259161e44579b715b4a383c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 31 Aug 2016 15:28:58 +0100 Subject: Updated members UI --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/project_members.js | 10 ---- app/assets/javascripts/project_members.js.es6 | 36 +++++++++++++++ app/assets/stylesheets/framework/selects.scss | 6 ++- app/assets/stylesheets/pages/members.scss | 22 +++++++++ .../project_members/_new_project_member.html.haml | 35 ++++++-------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 23 ++++----- app/views/shared/members/_member.html.haml | 54 ++++++++-------------- 9 files changed, 111 insertions(+), 79 deletions(-) delete mode 100644 app/assets/javascripts/project_members.js create mode 100644 app/assets/javascripts/project_members.js.es6 create mode 100644 app/assets/stylesheets/pages/members.scss diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 99b16f7d59b..c95aaf61443 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -132,7 +132,7 @@ break; case 'projects:project_members:index': new gl.MemberExpirationDate(); - new ProjectMembers(); + new gl.ProjectMembers(); new UsersSelect(); break; case 'groups:new': diff --git a/app/assets/javascripts/project_members.js b/app/assets/javascripts/project_members.js deleted file mode 100644 index 78f7b48bc7d..00000000000 --- a/app/assets/javascripts/project_members.js +++ /dev/null @@ -1,10 +0,0 @@ -(function() { - this.ProjectMembers = (function() { - function ProjectMembers() { - $('li.project_member').bind('ajax:success', function() { - return $(this).fadeOut(); - }); - } - return ProjectMembers; - })(); -}).call(this); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 new file mode 100644 index 00000000000..74cedfd5006 --- /dev/null +++ b/app/assets/javascripts/project_members.js.es6 @@ -0,0 +1,36 @@ +((w) => { + window.gl = window.gl || {}; + + class ProjectMembers { + constructor() { + this.removeListeners(); + this.addListeners(); + } + + removeListeners() { + $('.project_member').off('ajax:success'); + $('.js-member-update-control').off('change'); + } + + addListeners() { + $('.project_member').on('ajax:success', this.removeRow); + $('.js-member-update-control').on('change', function () { + console.log($(this).val()); + }); + } + + removeRow(e) { + const $target = $(e.target); + + if ($target.hasClass('btn-remove')) { + $target.fadeOut(); + } + } + + submitForm() { + + } + } + + gl.ProjectMembers = ProjectMembers; +})(window); diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index c75dacf95d9..746ab89abd2 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -86,7 +86,7 @@ background: none; .select2-search-field input { - padding: $gl-padding / 2; + padding: 5px $gl-padding / 2; font-size: 13px; height: auto; font-family: inherit; @@ -191,6 +191,10 @@ &.input-clamp { max-width: 100%; } + + &.input-full { + width: 100%; + } } .select2-highlighted { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss new file mode 100644 index 00000000000..9583d7c6161 --- /dev/null +++ b/app/assets/stylesheets/pages/members.scss @@ -0,0 +1,22 @@ +.project-members-new { + > h5 { + font-weight: normal; + } +} + +.member { + .controls { + display: flex; + width: 400px; + } + + .form-horizontal { + display: flex; + flex: 1; + margin-top: 3px; + } + + .member-form-control { + width: 50%; + } +} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index fa8cbf71733..c0b187fb460 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,27 +1,22 @@ -= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) - .help-block += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project) do |f| + .row + .col-md-4.col-lg-6 + = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) + .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. - .form-group - = f.label :access_level, "Project Access", class: 'control-label' - .col-sm-10 - = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2" - .help-block - Read more about role permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .col-md-3.col-lg-2 + = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select" + .help-block.append-bottom-10 + = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + about role permissions - .form-group - = f.label :expires_at, 'Access expiration date', class: 'control-label' - .col-sm-10 + .col-md-3.col-lg-2 .clearable-input - = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block + .help-block.append-bottom-10 On this date, the user(s) will automatically lose access to this project. - .form-actions - = f.submit 'Add users to project', class: "btn btn-create" + .col-md-2 + = f.submit "Add to project", class: "btn btn-create btn-block" diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index b0bfdd235f7..db6c1194da7 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading + Users with access to %strong #{@project.name} - project members %span.badge= members.size .controls = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9d063b3081f..9d47e7d725c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,20 +1,21 @@ - page_title "Members" .project-members-page.js-project-members-page.prepend-top-default + %h4 + Members + %hr - if can?(current_user, :admin_project_member, @project) - .panel.panel-default - .panel-heading - Add new user to project - .controls - = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do - Import members - .panel-body - %p.light - Users with access to this project are listed below. - = render "new_project_member" + .project-members-new.append-bottom-default + %h5.clearfix + Add new user to + %strong= @project.name + -# = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right", title: "Import members from another project" + = render "new_project_member" - = render 'shared/members/requests', membership_source: @project, requesters: @requesters + = render 'shared/members/requests', membership_source: @project, requesters: @requesters + %h5.append-bottom-default + Existing users and groups = render 'team', members: @project_members - if @group diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 5f20e4bd42a..fd9b688dc20 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -2,27 +2,28 @@ - show_controls = local_assigns.fetch(:show_controls, true) - user = member.user -%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) } +%li.member{ class: dom_class(member), id: dom_id(member) } - if show_roles .controls - %strong.control-text= member.human_access - if show_controls - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn' - - - if can?(current_user, action_member_permission(:update, member), member) - = button_tag icon('pencil'), - type: 'button', - class: 'btn inline js-toggle-button', - title: 'Edit' - - - if member.request? - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + - if @project.owner != user + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn btn-success', - title: 'Grant access' + class: 'btn' + - else + Owner + + - if member.request? && can?(current_user, action_member_permission(:update, member), member) + = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + method: :post, + class: 'btn btn-success', + title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) - if current_user == user @@ -44,7 +45,7 @@ = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' %strong = link_to user.name, user_path(user) - %span.cgray= user.username + %span.cgray= user.to_reference - if user == current_user %span.label.label-success It's you @@ -73,20 +74,3 @@ by = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - - if show_roles - .edit-member.hide.js-toggle-content - %br - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - .form-group - = label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label' - .col-sm-10 - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}" - .form-group - = label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label' - .col-sm-10 - .clearable-input - = f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}" - %i.clear-icon.js-clear-input - .prepend-top-10 - = f.submit 'Save', class: 'btn btn-save btn-sm' -- cgit v1.2.1 From 87a0501ded0d08ae718b6f3f6feb4ac2c9c6b016 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 11:56:21 +0100 Subject: Updates the member row when values changed --- app/assets/javascripts/member_expiration_date.js | 8 ++++++-- app/assets/javascripts/project_members.js.es6 | 5 +++-- app/views/projects/project_members/update.js.haml | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index 1935af491f7..e1532fd9ec4 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -14,14 +14,18 @@ inputs.datepicker({ dateFormat: 'yy-mm-dd', minDate: 1, - onSelect: toggleClearInput + onSelect: function () { + $(this).trigger('change'); + toggleClearInput.call(this); + } }); inputs.next('.js-clear-input').on('click', function(event) { event.preventDefault(); var input = $(this).closest('.clearable-input').find('.js-access-expiration-date'); - input.datepicker('setDate', null); + input.datepicker('setDate', null) + .trigger('change'); toggleClearInput.call(input); }); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 74cedfd5006..659c57d8b6c 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -15,7 +15,8 @@ addListeners() { $('.project_member').on('ajax:success', this.removeRow); $('.js-member-update-control').on('change', function () { - console.log($(this).val()); + $(this).closest('form') + .trigger("submit.rails"); }); } @@ -28,7 +29,7 @@ } submitForm() { - + } } diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 37e55dc72a3..91927181efb 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,3 @@ :plain - $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}'); - new gl.MemberExpirationDate(); + var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}'); + $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); -- cgit v1.2.1 From 4afd17b2786b5bca075ac7508979fad582c65bc9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 13:48:20 +0100 Subject: Included groups on project_members page --- app/assets/stylesheets/framework/lists.scss | 8 +++ app/assets/stylesheets/pages/groups.scss | 2 - app/assets/stylesheets/pages/members.scss | 9 +++ app/controllers/projects/group_links_controller.rb | 12 ++++ .../projects/project_members_controller.rb | 1 + app/views/projects/group_links/update.js.haml | 3 + .../project_members/_group_members.html.haml | 2 +- .../projects/project_members/_groups.html.haml | 9 +++ app/views/projects/project_members/_team.html.haml | 12 ---- app/views/projects/project_members/index.html.haml | 14 +++-- app/views/shared/members/_group.html.haml | 23 ++++++++ app/views/shared/members/_member.html.haml | 69 ++++++++++------------ config/routes.rb | 2 +- 13 files changed, 108 insertions(+), 58 deletions(-) create mode 100644 app/views/projects/group_links/update.js.haml create mode 100644 app/views/projects/project_members/_groups.html.haml create mode 100644 app/views/shared/members/_group.html.haml diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 965fcc06518..dfdfe4a3c89 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -164,6 +164,14 @@ ul.content-list { } } + .member-controls { + float: none; + + @media (min-width: $screen-md-min) { + float: right; + } + } + // When dragging a list item &.ui-sortable-helper { border-bottom: none; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index b657ca47d38..a27f7a2fd77 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,6 +1,4 @@ .member-search-form { - float: left; - input[type='search'] { width: 225px; vertical-align: bottom; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 9583d7c6161..4bc34ac15df 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -5,6 +5,15 @@ } .member { + .list-item-name { + float: none; + + @media (min-width: $screen-md-min) { + float: left; + width: 50%; + } + } + .controls { display: flex; width: 400px; diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index d0c4550733c..57c54bf625a 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -19,9 +19,21 @@ class Projects::GroupLinksController < Projects::ApplicationController redirect_to namespace_project_group_links_path(project.namespace, project) end + def update + @group_link = @project.project_group_links.find(params[:id]) + + @group_link.update_attributes(group_link_params) + end + def destroy project.project_group_links.find(params[:id]).destroy redirect_to namespace_project_group_links_path(project.namespace, project) end + + protected + + def group_link_params + params.require(:group_link).permit(:group_access, :expires_at) + end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 42a7e5a2c30..d83e95cf097 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,6 +5,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index + @groups = @project.project_group_links.all @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml new file mode 100644 index 00000000000..d3a37847f58 --- /dev/null +++ b/app/views/projects/group_links/update.js.haml @@ -0,0 +1,3 @@ +:plain + var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); + $("##{dom_id(@group_link.group)} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index e783d8c72c5..9738f369a35 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -1,7 +1,7 @@ .panel.panel-default .panel-heading + Group members with access to %strong #{@group.name} - group members %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml new file mode 100644 index 00000000000..79791af7963 --- /dev/null +++ b/app/views/projects/project_members/_groups.html.haml @@ -0,0 +1,9 @@ +.panel.panel-default + .panel-heading + Groups with access to + %strong #{@project.name} + %span.badge= groups.size + %ul.content-list + - @groups.each do |group_link| + - group = group_link.group + = render 'shared/members/group', group_link: group_link, group: group diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index db6c1194da7..6a8b28d3886 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -3,17 +3,5 @@ Users with access to %strong #{@project.name} %span.badge= members.size - .controls - = 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', spellcheck: false } - = button_tag class: 'btn', title: 'Search' do - = icon("search") %ul.content-list = render partial: 'shared/members/member', collection: members, as: :member - -:javascript - $('form.member-search-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9d47e7d725c..db8a060d170 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -14,12 +14,16 @@ = render 'shared/members/requests', membership_source: @project, requesters: @requesters - %h5.append-bottom-default - Existing users and groups + .append-bottom-default.clearfix + %h5.pull-left + Existing users and groups + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form pull-right hidden-xs hidden-sm' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } + = icon("search") + - if @grups + = render 'groups', groups: @groups = render 'team', members: @project_members - if @group = render "group_members", members: @group_members - - - if @project_group_links.any? && @project.allowed_to_share_with_group? - = render "shared_group_members" diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml new file mode 100644 index 00000000000..0502de5210b --- /dev/null +++ b/app/views/shared/members/_group.html.haml @@ -0,0 +1,23 @@ +- group = local_assigns[:group] +- group_link = local_assigns[:group_link] +%li.member{ class: dom_class(group), id: dom_id(group) } + %span{ class: "list-item-name" } + = image_tag group_icon(group), class: "avatar s40", alt: '' + %strong + = link_to group.name, group_path(group) + .cgray + Joined #{time_ago_with_tooltip(group.created_at)} + - if group_link.expires? + · + %span{ class: ('text-warning' if group_link.expires_soon?) } + Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} + .controls.member-controls + = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal' do + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" + .prepend-left-5.append-right-10.clearable-input.member-form-control + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" + %i.clear-icon.js-clear-input + = link_to icon('trash'), namespace_project_group_link_path(@project.namespace, @project, group_link), + remote: true, + method: :delete, + class: 'btn btn-remove' diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index fd9b688dc20..800badc051a 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -3,43 +3,6 @@ - user = member.user %li.member{ class: dom_class(member), id: dom_id(member) } - - if show_roles - .controls - - if show_controls - - if @project.owner != user - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - %i.clear-icon.js-clear-input - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) - = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), - method: :post, - class: 'btn' - - else - Owner - - - if member.request? && can?(current_user, action_member_permission(:update, member), member) - = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), - method: :post, - class: 'btn btn-success', - title: 'Grant access' - - - if can?(current_user, action_member_permission(:destroy, member), member) - - if current_user == user - = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn btn-remove' - - else - = link_to icon('trash'), member, - remote: true, - method: :delete, - data: { confirm: remove_member_message(member) }, - class: 'btn btn-remove', - title: remove_member_title(member) - - %span{ class: ("list-item-name" if show_controls) } - if user = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' @@ -74,3 +37,35 @@ by = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) + - if show_roles + .controls.member-controls + - if show_controls + = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), + method: :post, + class: 'btn' + + - if member.request? && can?(current_user, action_member_permission(:update, member), member) + = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), + method: :post, + class: 'btn btn-success', + title: 'Grant access' + + - if can?(current_user, action_member_permission(:destroy, member), member) + - if current_user == user + = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(member.source) }, + class: 'btn btn-remove' + - else + = link_to icon('trash'), member, + remote: true, + method: :delete, + data: { confirm: remove_member_message(member) }, + class: 'btn btn-remove', + title: remove_member_title(member) diff --git a/config/routes.rb b/config/routes.rb index 068c92d1400..441f7249aa8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -867,7 +867,7 @@ Rails.application.routes.draw do end end - resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do member do -- cgit v1.2.1 From 23993147fbf24e868d33927dc1194b60a106076d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 14:13:49 +0100 Subject: Fixed issue with groups not displaying --- app/controllers/projects/project_members_controller.rb | 14 -------------- app/views/projects/project_members/index.html.haml | 6 ++---- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d83e95cf097..6060ddf025b 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -16,20 +16,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.order('access_level DESC') - @group = @project.group - - if @group - @group_members = @group.group_members - @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group) - - if params[:search].present? - users = @group.users.search(params[:search]).to_a - @group_members = @group_members.where(user_id: users) - end - - @group_members = @group_members.order('access_level DESC') - end - @requesters = @project.requesters if can?(current_user, :admin_project, @project) @project_member = @project.project_members.new diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index db8a060d170..42a23057ff1 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,9 +21,7 @@ .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = icon("search") - - if @grups + - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @project_members - - if @group - = render "group_members", members: @group_members + = render 'team', members: @project_members -- cgit v1.2.1 From e33cda96cb20f47fdde4314f6bb00e43bbf5aeb4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 15:27:42 +0100 Subject: Fixed group members not deleting Combine both group members & project members in project members list --- app/assets/javascripts/project_members.js.es6 | 7 ++++--- app/controllers/projects/project_members_controller.rb | 9 +++++---- app/views/projects/project_members/_team.html.haml | 6 ++++-- app/views/projects/project_members/index.html.haml | 1 + 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 659c57d8b6c..8b5cb17ac2d 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -8,12 +8,12 @@ } removeListeners() { - $('.project_member').off('ajax:success'); + $('.project_member, .group_member').off('ajax:success'); $('.js-member-update-control').off('change'); } addListeners() { - $('.project_member').on('ajax:success', this.removeRow); + $('.project_member, .group_member').on('ajax:success', this.removeRow); $('.js-member-update-control').on('change', function () { $(this).closest('form') .trigger("submit.rails"); @@ -24,7 +24,8 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - $target.fadeOut(); + console.log('a'); + $target.closest('.member').fadeOut(); } } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 6060ddf025b..abb92938211 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,15 +6,16 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links.all - @project_members = @project.project_members + @project_members = @project.team.members.all + @project_members_size = @project_members.size + @group_members = @project.group.group_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? - users = @project.users.search(params[:search]).to_a - @project_members = @project_members.where(user_id: users) + @project_members = @project_members.search(params[:search]) end - @project_members = @project_members.order('access_level DESC') + @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 6a8b28d3886..23c35f91b6b 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,6 +2,8 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= members.size + %span.badge= @project_members_size %ul.content-list - = render partial: 'shared/members/member', collection: members, as: :member + - members.each do |user| + - member = @project.team.find_member(user.id) + = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 42a23057ff1..85e512a75f4 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,3 +25,4 @@ = render 'groups', groups: @groups = render 'team', members: @project_members + = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From 843dd24bdf063bb199c841cd6a08643344ae7598 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 16:22:53 +0100 Subject: Mobile improvements Added group name to members row Fixed saving group member --- app/assets/javascripts/project_members.js.es6 | 7 +++- app/assets/stylesheets/framework/lists.scss | 8 +++-- app/assets/stylesheets/pages/members.scss | 42 ++++++++++++++++++---- app/views/groups/group_members/update.js.haml | 4 +-- app/views/projects/project_members/_team.html.haml | 2 +- app/views/shared/members/_group.html.haml | 7 ++-- app/views/shared/members/_member.html.haml | 28 ++++++++++----- 7 files changed, 74 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index 8b5cb17ac2d..d11467cf6eb 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -1,5 +1,5 @@ ((w) => { - window.gl = window.gl || {}; + w.gl = w.gl || {}; class ProjectMembers { constructor() { @@ -10,6 +10,7 @@ removeListeners() { $('.project_member, .group_member').off('ajax:success'); $('.js-member-update-control').off('change'); + $('.js-edit-member-form').off('ajax:success'); } addListeners() { @@ -17,6 +18,10 @@ $('.js-member-update-control').on('change', function () { $(this).closest('form') .trigger("submit.rails"); + $(this).disable(); + }); + $('.js-edit-member-form').on('ajax:success', function () { + $(this).find('.js-member-update-control').enable(); }); } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index dfdfe4a3c89..272d37763c1 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -128,6 +128,10 @@ ul.content-list { color: $gl-dark-link-color; } + .member-group-link { + color: $blue-normal; + } + .description { p { @include str-truncated; @@ -166,8 +170,8 @@ ul.content-list { .member-controls { float: none; - - @media (min-width: $screen-md-min) { + + @media (min-width: $screen-sm-min) { float: right; } } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 4bc34ac15df..a69af862348 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -8,24 +8,52 @@ .list-item-name { float: none; - @media (min-width: $screen-md-min) { + @media (min-width: $screen-sm-min) { float: left; width: 50%; } } .controls { - display: flex; - width: 400px; + @media (min-width: $screen-sm-min) { + display: flex; + width: 400px; + max-width: 50%; + } } .form-horizontal { - display: flex; - flex: 1; - margin-top: 3px; + margin-top: 5px; + + @media (min-width: $screen-sm-min) { + display: flex; + flex: 1; + margin-top: 3px; + } + } + + .btn-remove { + width: 100%; + + @media (min-width: $screen-sm-min) { + width: auto; + } + } +} + +.member-form-control { + @media (max-width: $screen-xs-max) { + padding: 5px 0; + margin-left: 0; + margin-right: 0; } - .member-form-control { + @media (min-width: $screen-sm-min) { width: 50%; } } + +.member-access-text { + margin-left: auto; + line-height: 43px; +} diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index 3be7ed8432c..de8f53b6b52 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,3 +1,3 @@ :plain - $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}'); - new gl.MemberExpirationDate(); + var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}'); + $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 23c35f91b6b..2af9fe0519c 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -6,4 +6,4 @@ %ul.content-list - members.each do |user| - member = @project.team.find_member(user.id) - = render 'shared/members/member', member: member + = render 'shared/members/member', member: member, user: user diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 0502de5210b..5d54195646c 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -17,7 +17,10 @@ .prepend-left-5.append-right-10.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" %i.clear-icon.js-clear-input - = link_to icon('trash'), namespace_project_group_link_path(@project.namespace, @project, group_link), + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, - class: 'btn btn-remove' + class: 'btn btn-remove' do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 800badc051a..4518e84fe34 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -1,6 +1,6 @@ - show_roles = local_assigns.fetch(:show_roles, true) - show_controls = local_assigns.fetch(:show_controls, true) -- user = member.user +- user = local_assigns.fetch(:user, member.user) %li.member{ class: dom_class(member), id: dom_id(member) } %span{ class: ("list-item-name" if show_controls) } @@ -11,12 +11,16 @@ %span.cgray= user.to_reference - if user == current_user - %span.label.label-success It's you + %span.label.label-success.prepend-left-5 It's you - if user.blocked? %label.label.label-danger %strong Blocked + - if member.respond_to?(:group) && !@group + = link_to member.group, class: "member-group-link prepend-left-5" do + = "· #{member.group.name}" + .cgray - if member.request? Requested @@ -40,11 +44,14 @@ - if show_roles .controls.member-controls - if show_controls - = form_for member, remote: true, html: { class: 'form-horizontal' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - %i.clear-icon.js-clear-input + - if user != current_user + = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + .prepend-left-5.append-right-10.clearable-input.member-form-control + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + %i.clear-icon.js-clear-input + - else + %span.member-access-text= member.human_access - if !user && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, @@ -63,9 +70,12 @@ data: { confirm: leave_confirmation_message(member.source) }, class: 'btn btn-remove' - else - = link_to icon('trash'), member, + = link_to member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, class: 'btn btn-remove', - title: remove_member_title(member) + title: remove_member_title(member) do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') -- cgit v1.2.1 From 49a31e64b76b351c1bad91459991a69f0e0fb296 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Sep 2016 17:04:46 +0100 Subject: Removed console log Hides time on mobile --- app/assets/javascripts/project_members.js.es6 | 1 - app/views/shared/members/_member.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index d11467cf6eb..f525bdbb1ea 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -29,7 +29,6 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - console.log('a'); $target.closest('.member').fadeOut(); } } diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 4518e84fe34..ab4f1f1382b 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -21,7 +21,7 @@ = link_to member.group, class: "member-group-link prepend-left-5" do = "· #{member.group.name}" - .cgray + .hidden-xs.cgray - if member.request? Requested = time_ago_with_tooltip(member.requested_at) -- cgit v1.2.1 From 15a3111a6663894d952103e7395f2f56408f88ce Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:04:28 +0100 Subject: Mobile spacing improvements --- app/assets/stylesheets/framework/forms.scss | 4 --- app/assets/stylesheets/pages/groups.scss | 12 -------- app/assets/stylesheets/pages/members.scss | 36 ++++++++++++++++++++++ app/views/projects/project_members/index.html.haml | 7 +++-- app/views/shared/members/_group.html.haml | 2 +- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 37ff7e22ed1..d1f1a372c06 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -125,7 +125,3 @@ label { border-right: 0; } } - -.help-block { - margin-bottom: 0; -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index a27f7a2fd77..cc1c0249df3 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,15 +1,3 @@ -.member-search-form { - input[type='search'] { - width: 225px; - vertical-align: bottom; - - @media (max-width: $screen-xs-max) { - width: 100px; - vertical-align: bottom; - } - } -} - .milestone-row { @include str-truncated(90%); } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index a69af862348..a7f1324f69a 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -57,3 +57,39 @@ margin-left: auto; line-height: 43px; } + +.member.existing-title { + @media (min-width: $screen-sm-min) { + float: left; + } +} + +.member-search-form { + position: relative; + + @media (min-width: $screen-sm-min) { + float: right; + } + + .form-control { + width: 100%; + padding-right: 35px; + + @media (min-width: $screen-sm-min) { + width: 350px; + } + } +} + +.member-search-btn { + position: absolute; + right: 0; + top: 0; + height: 35px; + padding-left: 10px; + padding-right: 10px; + color: $gray-darkest; + background: transparent; + border: 0; + outline: 0; +} diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 85e512a75f4..abe10433387 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -15,12 +15,13 @@ = render 'shared/members/requests', membership_source: @project, requesters: @requesters .append-bottom-default.clearfix - %h5.pull-left + %h5.member.existing-title Existing users and groups - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form pull-right hidden-xs hidden-sm' do + = 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 members by name', class: 'form-control', spellcheck: false } - = icon("search") + %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } + = icon("search") - if @groups.size > 0 = render 'groups', groups: @groups diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 5d54195646c..e545aec80a9 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -12,7 +12,7 @@ %span{ class: ('text-warning' if group_link.expires_soon?) } Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls - = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal' do + = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" .prepend-left-5.append-right-10.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" -- cgit v1.2.1 From e477ad44565dbe69e3f0200f4f4f7bebbd48cb15 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:25:53 +0100 Subject: Removes row from dom when deleting Fixed spacing with buttons Disables group form when user doesnt have correct permissions --- app/assets/javascripts/project_members.js.es6 | 5 ++++- .../projects/project_members_controller.rb | 7 ++----- app/models/project_team.rb | 8 ++++---- app/views/shared/members/_group.html.haml | 21 +++++++++++---------- app/views/shared/members/_member.html.haml | 10 +++++----- app/views/shared/members/_requests.html.haml | 2 +- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 index f525bdbb1ea..56bc98d1076 100644 --- a/app/assets/javascripts/project_members.js.es6 +++ b/app/assets/javascripts/project_members.js.es6 @@ -29,7 +29,10 @@ const $target = $(e.target); if ($target.hasClass('btn-remove')) { - $target.closest('.member').fadeOut(); + $target.closest('.member') + .fadeOut(function () { + $(this).remove(); + }); } } diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index abb92938211..cd31653698c 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,11 +5,9 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - @groups = @project.project_group_links.all - @project_members = @project.team.members.all + @groups = @project.project_group_links + @project_members = @project.team.members(can?(current_user, :admin_project, @project)) @project_members_size = @project_members.size - @group_members = @project.group.group_members - @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? @project_members = @project_members.search(params[:search]) @@ -20,7 +18,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @requesters = @project.requesters if can?(current_user, :admin_project, @project) @project_member = @project.project_members.new - @project_group_links = @project.project_group_links end def create diff --git a/app/models/project_team.rb b/app/models/project_team.rb index ab6ea2aae36..57925a0861a 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,8 +52,8 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members - @members ||= fetch_members + def members(non_invite) + @members ||= fetch_members(nil, non_invite) end alias_method :users, :members @@ -197,7 +197,7 @@ class ProjectTeam access.each { |key, value| access[key] = [value, capped_access_level].min } end - def fetch_members(level = nil) + def fetch_members(level = nil, non_invite = false) project_members = project.members group_members = group ? group.members : [] invited_members = [] @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && !non_invite user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index e545aec80a9..19b58ef20ae 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -13,14 +13,15 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}" - .prepend-left-5.append-right-10.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}" + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + .prepend-left-5.clearable-input.member-form-control + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) %i.clear-icon.js-clear-input - = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), - remote: true, - method: :delete, - class: 'btn btn-remove' do - %span.visible-xs-block - Delete - = icon('trash', class: 'hidden-xs') + - if can?(current_user, action_member_permission(:admin, group), group) + = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), + remote: true, + method: :delete, + class: 'btn btn-remove prepend-left-10' do + %span.visible-xs-block + Delete + = icon('trash', class: 'hidden-xs') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index ab4f1f1382b..2d4853eef92 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -47,7 +47,7 @@ - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) - .prepend-left-5.append-right-10.clearable-input.member-form-control + .prepend-left-5.clearable-input.member-form-control = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) %i.clear-icon.js-clear-input - else @@ -55,12 +55,12 @@ - if !user && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn' + class: 'btn btn-default prepend-left-10' - if member.request? && can?(current_user, action_member_permission(:update, member), member) = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, - class: 'btn btn-success', + class: 'btn btn-success prepend-left-10', title: 'Grant access' - if can?(current_user, action_member_permission(:destroy, member), member) @@ -68,13 +68,13 @@ = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), method: :delete, data: { confirm: leave_confirmation_message(member.source) }, - class: 'btn btn-remove' + class: 'btn btn-remove prepend-left-10' - else = link_to member, remote: true, method: :delete, data: { confirm: remove_member_message(member) }, - class: 'btn btn-remove', + class: 'btn btn-remove prepend-left-10', title: remove_member_title(member) do %span.visible-xs-block Delete diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index 40b39e850b0..10050adfda5 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -1,8 +1,8 @@ - if requesters.any? .panel.panel-default .panel-heading + Users requesting access to %strong= membership_source.name - access requests %span.badge= requesters.size %ul.content-list = render partial: 'shared/members/member', collection: requesters, as: :member -- cgit v1.2.1 From b3d75ac5135130522f253d4b09f72a7c0a8e2f80 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 09:28:25 +0100 Subject: Return 403 if user can't update group --- app/controllers/projects/group_links_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 57c54bf625a..b5e314dced3 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,6 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) + return render_403 unless can?(current_user, action_member_permission(:admin, @group_link.group), @group_link.group) @group_link.update_attributes(group_link_params) end -- cgit v1.2.1 From cdc55db3452ca82f0dbdcdb631a1fc48abdf1f84 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:13:49 +0100 Subject: Fixed members error --- app/controllers/projects/project_members_controller.rb | 2 +- app/models/project_team.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index cd31653698c..617dd9823b9 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - @project_members = @project.team.members(can?(current_user, :admin_project, @project)) + @project_members = @project.team.members(!can?(current_user, :admin_project, @project)) @project_members_size = @project_members.size if params[:search].present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 57925a0861a..a58c56288dd 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,7 +52,7 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members(non_invite) + def members(non_invite = false) @members ||= fetch_members(nil, non_invite) end alias_method :users, :members @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && !non_invite + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && non_invite user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) -- cgit v1.2.1 From 3e19f1976f9a13fc1b13ec49b3ce31c3e114a454 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:50:06 +0100 Subject: Fixed issue with invited users not showing up --- app/controllers/projects/project_members_controller.rb | 14 +++++++++++++- app/models/project_team.rb | 8 ++++---- app/views/projects/project_members/_team.html.haml | 5 ++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 617dd9823b9..2175a5d8dcb 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -6,7 +6,19 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - @project_members = @project.team.members(!can?(current_user, :admin_project, @project)) + + members = [] + project_members = @project.project_members + project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + members << project_members.pluck(:id) + + if @project.group + group_members = @project.group.group_members + group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + members << group_members.pluck(:id) + end + + @project_members = Member.where(id: members) @project_members_size = @project_members.size if params[:search].present? diff --git a/app/models/project_team.rb b/app/models/project_team.rb index a58c56288dd..ab6ea2aae36 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -52,8 +52,8 @@ class ProjectTeam ProjectMember.truncate_team(project) end - def members(non_invite = false) - @members ||= fetch_members(nil, non_invite) + def members + @members ||= fetch_members end alias_method :users, :members @@ -197,7 +197,7 @@ class ProjectTeam access.each { |key, value| access[key] = [value, capped_access_level].min } end - def fetch_members(level = nil, non_invite = false) + def fetch_members(level = nil) project_members = project.members group_members = group ? group.members : [] invited_members = [] @@ -236,7 +236,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? && non_invite + user_ids.push(*invited_members.map(&:user_id)) if invited_members.any? user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 2af9fe0519c..867cb2b97e4 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -4,6 +4,5 @@ %strong #{@project.name} %span.badge= @project_members_size %ul.content-list - - members.each do |user| - - member = @project.team.find_member(user.id) - = render 'shared/members/member', member: member, user: user + - members.each do |member| + = render 'shared/members/member', member: member -- cgit v1.2.1 From 931d09f481d5e174a984c6f874e67273ba2864f0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 10:52:17 +0100 Subject: Fixed search --- .../projects/project_members_controller.rb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 2175a5d8dcb..7581833eacc 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,21 +10,30 @@ class Projects::ProjectMembersController < Projects::ApplicationController members = [] project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @project.users.search(params[:search]).to_a + project_members = project_members.where(user_id: users) + end + members << project_members.pluck(:id) - if @project.group - group_members = @project.group.group_members + @group = @project.group + if @group + group_members = @group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @group.users.search(params[:search]).to_a + group_members = group_members.where(user_id: users) + end + members << group_members.pluck(:id) end @project_members = Member.where(id: members) @project_members_size = @project_members.size - if params[:search].present? - @project_members = @project_members.search(params[:search]) - end - @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) -- cgit v1.2.1 From 999f18480511d81b1499b502cbc89a5b34e54544 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 13:35:43 +0100 Subject: Tests update --- app/views/projects/project_members/_groups.html.haml | 2 +- .../project_members/_new_project_member.html.haml | 2 +- app/views/shared/members/_member.html.haml | 2 ++ features/steps/admin/projects.rb | 2 +- features/steps/group/members.rb | 4 ++-- features/steps/project/team_management.rb | 15 +++++++-------- .../groups/members/owner_manages_access_requests_spec.rb | 2 +- .../members/master_manages_access_requests_spec.rb | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 79791af7963..340e4cd06b8 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -1,4 +1,4 @@ -.panel.panel-default +.panel.panel-default.project-members-groups .panel-heading Groups with access to %strong #{@project.name} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index c0b187fb460..26e06a14c07 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,4 +1,4 @@ -= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project) do |f| += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| .row .col-md-4.col-lg-6 = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2d4853eef92..2f98eeff658 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -79,3 +79,5 @@ %span.visible-xs-block Delete = icon('trash', class: 'hidden-xs') + - else + %span.member-access-text= member.human_access diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index d77945a6b9c..2b8cd030ace 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -70,7 +70,7 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps select "Developer", from: "access_level" end - click_button "Add users to project" + click_button "Add to project" end step 'I should see current user as "Developer"' do diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index e9b45823c67..4aec3d03ef6 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -1,4 +1,5 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps + include WaitForAjax include SharedAuthentication include SharedPaths include SharedGroup @@ -116,9 +117,8 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps member = mary_jane_member page.within "#group_member_#{member.id}" do - click_button 'Edit' select 'Developer', from: "member_access_level_#{member.id}" - click_on 'Save' + wait_for_ajax end end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index e920f5a706b..49821b85922 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -22,7 +22,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps select2(user.id, from: "#user_ids", multiple: true) select "Reporter", from: "access_level" end - click_button "Add users to project" + click_button "Add to project" end step 'I should see "Mike" in team list as "Reporter"' do @@ -36,10 +36,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps step 'I select "sjobs@apple.com" as "Reporter"' do page.within ".users-project-form" do - select2("sjobs@apple.com", from: "#user_ids", multiple: true) + find('#user_ids', visible: false).set('sjobs@apple.com') select "Reporter", from: "access_level" end - click_button "Add users to project" + click_button "Add to project" end step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do @@ -65,9 +65,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps user = User.find_by(name: 'Dmitriy') project_member = project.project_members.find_by(user_id: user.id) page.within "#project_member_#{project_member.id}" do - click_button 'Edit' select "Reporter", from: "member_access_level_#{project_member.id}" - click_button "Save" end end @@ -144,8 +142,9 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I should see "Opensource" group user listing' do - expect(page).to have_content("Shared with OpenSource group, members with Master role (2)") - expect(page).to have_content(@os_user1.name) - expect(page).to have_content(@os_user2.name) + page.within '.project-members-groups' do + expect(page).to have_content('OpenSource') + expect(find('select').value).to eq('40') + end end end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 10d3713f19f..d811b05b0c3 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do def expect_visible_access_request(group, user) expect(group.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{group.name} access requests 1" + expect(page).to have_content "Users requesting access to #{group.name} 1" expect(page).to have_content user.name end end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index f7fcd9b6731..d15376931c3 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do def expect_visible_access_request(project, user) expect(project.requesters.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{project.name} access requests 1" + expect(page).to have_content "Users requesting access to #{project.name} 1" expect(page).to have_content user.name end end -- cgit v1.2.1 From a56216c8bd1ef82c09c7ce39596e4b6436ebb7fa Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:06:20 +0100 Subject: Added import button back in --- app/views/projects/project_members/index.html.haml | 4 ++-- features/steps/project/team_management.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index abe10433387..d289d75454d 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,15 +1,15 @@ - page_title "Members" .project-members-page.js-project-members-page.prepend-top-default - %h4 + %h4.clearfix Members + = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default %h5.clearfix Add new user to %strong= @project.name - -# = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right", title: "Import members from another project" = render "new_project_member" = render 'shared/members/requests', membership_source: @project, requesters: @requesters diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index 49821b85922..b21d0849ad1 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -110,7 +110,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps end step 'I click link "Import team from another project"' do - click_link "Import members from another project" + click_link "Import" end When 'I submit "Website" project for import team' do -- cgit v1.2.1 From ccf76831da422298242ce3d8d11f72ab50454c85 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:19:11 +0100 Subject: Updated groups member UI to match --- app/assets/javascripts/dispatcher.js | 4 +- app/assets/javascripts/groups.js | 13 ------- app/assets/javascripts/members.js.es6 | 45 ++++++++++++++++++++++ app/assets/javascripts/project_members.js.es6 | 45 ---------------------- .../group_members/_new_group_member.html.haml | 35 ++++++++--------- app/views/groups/group_members/index.html.haml | 40 +++++++++---------- app/views/projects/project_members/index.html.haml | 2 +- 7 files changed, 81 insertions(+), 103 deletions(-) delete mode 100644 app/assets/javascripts/groups.js create mode 100644 app/assets/javascripts/members.js.es6 delete mode 100644 app/assets/javascripts/project_members.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index c95aaf61443..da3757d8992 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -127,12 +127,12 @@ break; case 'groups:group_members:index': new gl.MemberExpirationDate(); - new GroupMembers(); + new gl.Members(); new UsersSelect(); break; case 'projects:project_members:index': new gl.MemberExpirationDate(); - new gl.ProjectMembers(); + new gl.Members(); new UsersSelect(); break; case 'groups:new': diff --git a/app/assets/javascripts/groups.js b/app/assets/javascripts/groups.js deleted file mode 100644 index 4382dd6860f..00000000000 --- a/app/assets/javascripts/groups.js +++ /dev/null @@ -1,13 +0,0 @@ -(function() { - this.GroupMembers = (function() { - function GroupMembers() { - $('li.group_member').bind('ajax:success', function() { - return $(this).fadeOut(); - }); - } - - return GroupMembers; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 new file mode 100644 index 00000000000..fa259520810 --- /dev/null +++ b/app/assets/javascripts/members.js.es6 @@ -0,0 +1,45 @@ +((w) => { + w.gl = w.gl || {}; + + class Members { + constructor() { + this.removeListeners(); + this.addListeners(); + } + + removeListeners() { + $('.project_member, .group_member').off('ajax:success'); + $('.js-member-update-control').off('change'); + $('.js-edit-member-form').off('ajax:success'); + } + + addListeners() { + $('.project_member, .group_member').on('ajax:success', this.removeRow); + $('.js-member-update-control').on('change', function () { + $(this).closest('form') + .trigger("submit.rails"); + $(this).disable(); + }); + $('.js-edit-member-form').on('ajax:success', function () { + $(this).find('.js-member-update-control').enable(); + }); + } + + removeRow(e) { + const $target = $(e.target); + + if ($target.hasClass('btn-remove')) { + $target.closest('.member') + .fadeOut(function () { + $(this).remove(); + }); + } + } + + submitForm() { + + } + } + + gl.Members = Members; +})(window); diff --git a/app/assets/javascripts/project_members.js.es6 b/app/assets/javascripts/project_members.js.es6 deleted file mode 100644 index 56bc98d1076..00000000000 --- a/app/assets/javascripts/project_members.js.es6 +++ /dev/null @@ -1,45 +0,0 @@ -((w) => { - w.gl = w.gl || {}; - - class ProjectMembers { - constructor() { - this.removeListeners(); - this.addListeners(); - } - - removeListeners() { - $('.project_member, .group_member').off('ajax:success'); - $('.js-member-update-control').off('change'); - $('.js-edit-member-form').off('ajax:success'); - } - - addListeners() { - $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', function () { - $(this).closest('form') - .trigger("submit.rails"); - $(this).disable(); - }); - $('.js-edit-member-form').on('ajax:success', function () { - $(this).find('.js-member-update-control').enable(); - }); - } - - removeRow(e) { - const $target = $(e.target); - - if ($target.hasClass('btn-remove')) { - $target.closest('.member') - .fadeOut(function () { - $(this).remove(); - }); - } - } - - submitForm() { - - } - } - - gl.ProjectMembers = ProjectMembers; -})(window); diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 2fb3190ab11..2987befd2a4 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -1,27 +1,22 @@ -= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) - .help-block += form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f| + .row + .col-md-4.col-lg-6 + = users_select_tag(:user_ids, multiple: true, class: 'input-full', scope: :all, email_user: true) + .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. - .form-group - = f.label :access_level, "Group Access", class: 'control-label' - .col-sm-10 - = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2" - .help-block - Read more about role permissions - %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" + .col-md-3.col-lg-2 + = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select" + .help-block.append-bottom-10 + = link_to "Read more", help_page_path("user/permissions"), class: "vlink" + about role permissions - .form-group - = f.label :expires_at, 'Access expiration date', class: 'control-label' - .col-sm-10 + .col-md-3.col-lg-2 .clearable-input - = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date' + = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block + .help-block.append-bottom-10 On this date, the user(s) will automatically lose access to this group and all of its projects. - .form-actions - = f.submit 'Add users to group', class: "btn btn-create" + .col-md-2 + = f.submit 'Add to group', class: "btn btn-create btn-block" diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index f789796e942..d2c7ec2e821 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,35 +1,31 @@ - page_title "Members" -.group-members-page.prepend-top-default +.project-members-page.prepend-top-default + %h4 + Members + %hr - if can?(current_user, :admin_group_member, @group) - .panel.panel-default - .panel-heading - Add new user to group - .panel-body - %p.light - Members of group have access to all group projects. - .new-group-member-holder - = render "new_group_member" + .project-members-new.append-bottom-default + %h5.clearfix + Add new user to + %strong= @group.name + = render "new_group_member" = render 'shared/members/requests', membership_source: @group, requesters: @requesters + .append-bottom-default.clearfix + %h5.member.existing-title + Existing users + = 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 members by name', class: 'form-control', spellcheck: false } + %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } + = icon("search") .panel.panel-default .panel-heading + Users with access to %strong #{@group.name} - group members %span.badge= @members.total_count - .controls - = 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', spellcheck: false } - = button_tag class: 'btn', title: 'Search' do - = icon("search") %ul.content-list = render partial: 'shared/members/member', collection: @members, as: :member = paginate @members, theme: 'gitlab' - -:javascript - $('form.member-search-form').on('submit', function(event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index d289d75454d..a90de32bd47 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,6 +1,6 @@ - page_title "Members" -.project-members-page.js-project-members-page.prepend-top-default +.project-members-page.prepend-top-default %h4.clearfix Members = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" -- cgit v1.2.1 From 2c3fa33ca2e75792d1027eb73e2f69fed67bc435 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 15:10:22 +0100 Subject: Updated some specs Fixed issue with group name not showing --- .../projects/project_members_controller.rb | 19 +++++++++---------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 4 ++-- app/views/shared/members/_member.html.haml | 7 ++++--- features/steps/group/members.rb | 8 ++++---- .../master_adds_member_with_expiration_date_spec.rb | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 7581833eacc..bf6ac25266a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,7 +7,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - members = [] project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) @@ -16,25 +15,25 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = project_members.where(user_id: users) end - members << project_members.pluck(:id) + members_ids = project_members.pluck(:id) - @group = @project.group - if @group - group_members = @group.group_members + group = @project.group + if group + group_members = group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) if params[:search].present? - users = @group.users.search(params[:search]).to_a + users = group.users.search(params[:search]).to_a group_members = group_members.where(user_id: users) end - members << group_members.pluck(:id) + members_ids << group_members.pluck(:id) end - @project_members = Member.where(id: members) - @project_members_size = @project_members.size + @members = Member.where(id: members_ids.flatten) + @members_size = @members.size - @project_members = @project_members.page(params[:page]) + @members = @members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 867cb2b97e4..d9799033e17 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,7 +2,7 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= @project_members_size + %span.badge= @members_size %ul.content-list - members.each do |member| = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index a90de32bd47..80882d0c11c 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,5 +25,5 @@ - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @project_members - = paginate @project_members, theme: "gitlab" + = render 'team', members: @members + = paginate @members, theme: "gitlab" diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2f98eeff658..6f8c3c4da2e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -1,6 +1,7 @@ - show_roles = local_assigns.fetch(:show_roles, true) - show_controls = local_assigns.fetch(:show_controls, true) - user = local_assigns.fetch(:user, member.user) +- source = member.source %li.member{ class: dom_class(member), id: dom_id(member) } %span{ class: ("list-item-name" if show_controls) } @@ -17,9 +18,9 @@ %label.label.label-danger %strong Blocked - - if member.respond_to?(:group) && !@group - = link_to member.group, class: "member-group-link prepend-left-5" do - = "· #{member.group.name}" + - if source.instance_of?(Group) && !@group + = link_to source, class: "member-group-link prepend-left-5" do + = "· #{source.name}" .hidden-xs.cgray - if member.request? diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index 4aec3d03ef6..e54add1d568 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -14,7 +14,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Reporter", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I select "Mike" as "Master"' do @@ -25,7 +25,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Master", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see "Mike" in team list as "Reporter"' do @@ -48,7 +48,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Reporter", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do @@ -67,7 +67,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps select "Reporter", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see user "John Doe" in team list' do diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb index 430c384ac2e..27a83fdcd1f 100644 --- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb +++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' feature 'Projects > Members > Master adds member with expiration date', feature: true, js: true do + include WaitForAjax include Select2Helper include ActiveSupport::Testing::TimeHelpers @@ -20,7 +21,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature: page.within '.users-project-form' do select2(new_member.id, from: '#user_ids', multiple: true) fill_in 'expires_at', with: '2016-08-10' - click_on 'Add users to project' + click_on 'Add to project' end page.within '.project_member:first-child' do @@ -35,9 +36,8 @@ feature 'Projects > Members > Master adds member with expiration date', feature: visit namespace_project_project_members_path(project.namespace, project) page.within '.project_member:first-child' do - click_on 'Edit' - fill_in 'Access expiration date', with: '2016-08-09' - click_on 'Save' + find('.js-access-expiration-date').set '2016-08-09' + wait_for_ajax expect(page).to have_content('Expires in 3 days') end end -- cgit v1.2.1 From 638376c35494860936bf2858c01115dc4afe0bfe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 15:44:46 +0100 Subject: Fixed group tests --- features/steps/admin/groups.rb | 2 +- features/steps/group/members.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 0c89a3db9ad..9396a76f0a2 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -105,7 +105,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps select "Developer", from: "access_level" end - click_button "Add users to group" + click_button "Add to group" end step 'I should see current user as "Developer"' do diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb index e54add1d568..cefc55d07ab 100644 --- a/features/steps/group/members.rb +++ b/features/steps/group/members.rb @@ -109,7 +109,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps step 'I search for \'Mary\' member' do page.within '.member-search-form' do fill_in 'search', with: 'Mary' - click_button 'Search' + find('.member-search-btn').click end end -- cgit v1.2.1 From 97a51817bf1e6b0504bb84b686daf7e931ded2da Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 17:39:16 +0100 Subject: Fixed error when updating groups --- app/controllers/projects/group_links_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index b5e314dced3..3574ecf2811 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,7 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, action_member_permission(:admin, @group_link.group), @group_link.group) + return render_403 unless can?(current_user, :admin_group, @group_link.group) @group_link.update_attributes(group_link_params) end -- cgit v1.2.1 From e747626fad5c0e675d6a5cd5b6fcd482f10dad90 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 09:24:47 +0100 Subject: Added test for updating groups permissions --- app/controllers/projects/group_links_controller.rb | 2 +- app/views/shared/members/_group.html.haml | 6 ++-- spec/features/projects/members/group_links_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 spec/features/projects/members/group_links_spec.rb diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 3574ecf2811..7b4c39cdb8f 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -21,7 +21,7 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, :admin_group, @group_link.group) + return render_403 unless can?(current_user, :admin_project_member, @project) @group_link.update_attributes(group_link_params) end diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 19b58ef20ae..56d31a949ff 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -13,11 +13,11 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) .prepend-left-5.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, action_member_permission(:admin, group), group) + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) %i.clear-icon.js-clear-input - - if can?(current_user, action_member_permission(:admin, group), group) + - if can?(current_user, :admin_project_member, @project) = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb new file mode 100644 index 00000000000..3d59df20197 --- /dev/null +++ b/spec/features/projects/members/group_links_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +feature 'Projects > Members > Anonymous user sees members', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:group) { create(:group, :public) } + let(:project) { create(:empty_project, :public) } + + background do + project.team << [user, :master] + @group_link = create(:project_group_link, project: project, group: group) + + login_as(user) + visit namespace_project_project_members_path(project.namespace, project) + end + + it 'updates group access level' do + select 'Guest', from: "member_access_level_#{group.id}" + wait_for_ajax + + visit namespace_project_project_members_path(project.namespace, project) + + expect(page).to have_select("member_access_level_#{group.id}", selected: 'Guest') + end + + it 'updates expiry date' do + tomorrow = Date.today + 3 + + fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F") + wait_for_ajax + + page.within(first('li.member')) do + expect(page).to have_content('Expires in 3 days') + end + end +end -- cgit v1.2.1 From fe71edc3336ae662997ebbad3b4c46b2a2b4927c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:11:45 +0100 Subject: JS update --- app/assets/javascripts/members.js.es6 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index fa259520810..7986987e49a 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -15,14 +15,8 @@ addListeners() { $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', function () { - $(this).closest('form') - .trigger("submit.rails"); - $(this).disable(); - }); - $('.js-edit-member-form').on('ajax:success', function () { - $(this).find('.js-member-update-control').enable(); - }); + $('.js-member-update-control').on('change', this.formSubmit); + $('.js-edit-member-form').on('ajax:success', this.formSuccess); } removeRow(e) { @@ -36,8 +30,16 @@ } } - submitForm() { + formSubmit() { + const $this = $(this); + $this.disable() + .closest('form') + .trigger("submit.rails"); + } + + formSuccess() { + $(this).find('.js-member-update-control').enable(); } } -- cgit v1.2.1 From 3354bfd8f92ba034816890f0ea89bca630405103 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:45:23 +0100 Subject: CSS cleanup --- app/assets/stylesheets/framework/selects.scss | 4 ---- app/assets/stylesheets/pages/members.scss | 10 +++------- app/views/groups/group_members/_new_group_member.html.haml | 2 +- app/views/groups/group_members/index.html.haml | 2 +- .../projects/project_members/_new_project_member.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 2 +- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 746ab89abd2..b309e2ad9f4 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -191,10 +191,6 @@ &.input-clamp { max-width: 100%; } - - &.input-full { - width: 100%; - } } .select2-highlighted { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index a7f1324f69a..72f31cb1168 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -1,9 +1,3 @@ -.project-members-new { - > h5 { - font-weight: normal; - } -} - .member { .list-item-name { float: none; @@ -16,6 +10,7 @@ .controls { @media (min-width: $screen-sm-min) { + display: -webkit-flex; display: flex; width: 400px; max-width: 50%; @@ -26,8 +21,9 @@ margin-top: 5px; @media (min-width: $screen-sm-min) { + display: -webkit-flex; display: flex; - flex: 1; + width: 100%; margin-top: 3px; } } diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 2987befd2a4..b185b81db7f 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -1,7 +1,7 @@ = form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f| .row .col-md-4.col-lg-6 - = users_select_tag(:user_ids, multiple: true, class: 'input-full', scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true) .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index d2c7ec2e821..ebf9aca7700 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -6,7 +6,7 @@ %hr - if can?(current_user, :admin_group_member, @group) .project-members-new.append-bottom-default - %h5.clearfix + %p.clearfix Add new user to %strong= @group.name = render "new_group_member" diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index 26e06a14c07..79dcd7a6ee9 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -1,7 +1,7 @@ = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f| .row .col-md-4.col-lg-6 - = users_select_tag(:user_ids, multiple: true, class: "input-full", scope: :all, email_user: true) + = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true) .help-block.append-bottom-10 Search for users by name, username, or email, or invite new ones using their email address. diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 80882d0c11c..86b2752cc0b 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -7,7 +7,7 @@ %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default - %h5.clearfix + %p.clearfix Add new user to %strong= @project.name = render "new_project_member" -- cgit v1.2.1 From 68c7b52364307e26ef7a85b80aa11242abbfa5b6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 17:14:42 +0100 Subject: Fixed fields not being sent --- app/assets/javascripts/members.js.es6 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 7986987e49a..12d212ca185 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -33,9 +33,10 @@ formSubmit() { const $this = $(this); - $this.disable() - .closest('form') + $this.closest('form') .trigger("submit.rails"); + + $this.disable(); } formSuccess() { -- cgit v1.2.1 From 11c0c6509251a280f46c6be74da64a1cd7a5e190 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 10:06:13 +0100 Subject: Expires in test update --- spec/features/projects/members/group_links_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 3d59df20197..2085e875f12 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -31,7 +31,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t wait_for_ajax page.within(first('li.member')) do - expect(page).to have_content('Expires in 3 days') + expect(page).to have_content('Expires in') end end end -- cgit v1.2.1 From 73c4da1780c5086543eb998d5bc9cbd632ef6576 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 16:20:20 +0100 Subject: Fixed removing groups --- app/controllers/projects/group_links_controller.rb | 7 ++++++- app/views/shared/members/_group.html.haml | 3 ++- spec/features/projects/members/group_links_spec.rb | 9 +++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7b4c39cdb8f..7b6f07465e0 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -29,7 +29,12 @@ class Projects::GroupLinksController < Projects::ApplicationController def destroy project.project_group_links.find(params[:id]).destroy - redirect_to namespace_project_group_links_path(project.namespace, project) + respond_to do |format| + format.html do + redirect_to namespace_project_group_links_path(project.namespace, project) + end + format.js { head :ok } + end end protected diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 56d31a949ff..171a388b233 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,6 +1,6 @@ - group = local_assigns[:group] - group_link = local_assigns[:group_link] -%li.member{ class: dom_class(group), id: dom_id(group) } +%li.member.group_member{ id: "group_member_#{group_link.id}" } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' %strong @@ -21,6 +21,7 @@ = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, + data: { confirm: "Are you sure you want to remove #{group.name}?" }, class: 'btn btn-remove prepend-left-10' do %span.visible-xs-block Delete diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 2085e875f12..7870bc663b1 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -34,4 +34,13 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t expect(page).to have_content('Expires in') end end + + it 'deletes group link' do + page.within(first('.group_member')) do + find('.btn-remove').click + end + wait_for_ajax + + expect(page).not_to have_selector('.group_member') + end end -- cgit v1.2.1 From 2b41db9215f322ba61113a7bef2f49da157bbd53 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 16:48:48 +0100 Subject: Search project groups --- .../projects/project_members_controller.rb | 49 ++++++++++++++++++---- spec/features/projects/members/group_links_spec.rb | 20 +++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index bf6ac25266a..ac83377148a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,23 +10,30 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) - if params[:search].present? - users = @project.users.search(params[:search]).to_a - project_members = project_members.where(user_id: users) - end - - members_ids = project_members.pluck(:id) - group = @project.group + if group group_members = group.group_members group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) + end + + if params[:search].present? + groups_id = @groups.pluck(:group_id) + groups = Group.where(id: groups_id).search(params[:search]).to_a + @groups = @project.project_group_links.where(group_id: groups) + + users = @project.users.search(params[:search]).to_a + project_members = project_members.where(user_id: users) - if params[:search].present? + if group_members users = group.users.search(params[:search]).to_a group_members = group_members.where(user_id: users) end + end + members_ids = project_members.pluck(:id) + + if group_members members_ids << group_members.pluck(:id) end @@ -48,6 +55,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) + group_ids = params[:group_ids].split(',') + groups = Group.where(id: group_ids) + + groups.each do |group| + project.project_group_links.create( + group: group, + group_access: params[:access_level], + expires_at: params[:expires_at] + ) + end + redirect_to namespace_project_project_members_path(@project.namespace, @project) end @@ -101,6 +119,21 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end + def options + users = User.all + users = users.search(params[:search]) if params[:search].present? + users = users.page(1) + + groups = Group.all + groups = groups.search(params[:search]) if params[:search].present? + groups = groups.page(1) + + render json: { + Groups: groups.as_json(only: [:id, :name], methods: [:avatar_url]), + Users: users.as_json(only: [:id, :name, :username], methods: [:avatar_url]), + } + end + protected def member_params diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 7870bc663b1..14ab7541fad 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -43,4 +43,24 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t expect(page).not_to have_selector('.group_member') end + + context 'search' do + it 'finds no results' do + page.within '.member-search-form' do + fill_in 'search', with: 'testing 123' + find('.member-search-btn').click + end + + expect(page).not_to have_selector('.group_member') + end + + it 'finds results' do + page.within '.member-search-form' do + fill_in 'search', with: group.name + find('.member-search-btn').click + end + + expect(page).to have_selector('.group_member', count: 1) + end + end end -- cgit v1.2.1 From 401b797671b9b67ef40c4afa75acdeca83b6a6de Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 17:11:58 +0100 Subject: Fixed bug when group_ids not present when creating --- .../projects/project_members_controller.rb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index ac83377148a..d49598d2786 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -55,15 +55,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController current_user: current_user ) - group_ids = params[:group_ids].split(',') - groups = Group.where(id: group_ids) - - groups.each do |group| - project.project_group_links.create( - group: group, - group_access: params[:access_level], - expires_at: params[:expires_at] - ) + if params[:group_ids].present? + group_ids = params[:group_ids].split(',') + groups = Group.where(id: group_ids) + + groups.each do |group| + project.project_group_links.create( + group: group, + group_access: params[:access_level], + expires_at: params[:expires_at] + ) + end end redirect_to namespace_project_project_members_path(@project.namespace, @project) -- cgit v1.2.1 From ecf7640b28562468880dda97ba42e5fd18c0859f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 10:58:52 +0100 Subject: Fixed group_links expire date not updating in view --- app/views/projects/group_links/update.js.haml | 2 +- spec/features/projects/members/group_links_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index d3a37847f58..231d5a79723 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,3 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); - $("##{dom_id(@group_link.group)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb index 14ab7541fad..cc2f695211c 100644 --- a/spec/features/projects/members/group_links_spec.rb +++ b/spec/features/projects/members/group_links_spec.rb @@ -30,7 +30,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F") wait_for_ajax - page.within(first('li.member')) do + page.within(find('li.group_member')) do expect(page).to have_content('Expires in') end end -- cgit v1.2.1 From d8fee09e338006acb09c80ebcb032b6a75f3d7cd Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 12:36:21 +0100 Subject: Fixed jQuery chaining --- app/assets/javascripts/members.js.es6 | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 12d212ca185..1b4b3f38838 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -31,12 +31,10 @@ } formSubmit() { - const $this = $(this); - - $this.closest('form') - .trigger("submit.rails"); - - $this.disable(); + $(this).closest('form') + .trigger("submit.rails") + .end() + .disable(); } formSuccess() { -- cgit v1.2.1 From 2abbb0980f061d4297aab02f914c324c7fbe073b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 14:26:07 +0100 Subject: Removed group members from the list for now --- .../projects/project_members_controller.rb | 25 ---------------------- 1 file changed, 25 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d49598d2786..64cbc76da6f 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,33 +10,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController project_members = @project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) - group = @project.group - - if group - group_members = group.group_members - group_members = group_members.non_invite unless can?(current_user, :admin_project, @project) - end - - if params[:search].present? - groups_id = @groups.pluck(:group_id) - groups = Group.where(id: groups_id).search(params[:search]).to_a - @groups = @project.project_group_links.where(group_id: groups) - - users = @project.users.search(params[:search]).to_a - project_members = project_members.where(user_id: users) - - if group_members - users = group.users.search(params[:search]).to_a - group_members = group_members.where(user_id: users) - end - end - members_ids = project_members.pluck(:id) - if group_members - members_ids << group_members.pluck(:id) - end - @members = Member.where(id: members_ids.flatten) @members_size = @members.size -- cgit v1.2.1 From 7cca8ffe60f4cdc7ca012cf223c6d7855b928685 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 8 Sep 2016 10:45:53 +0100 Subject: Ruby update --- app/controllers/projects/project_members_controller.rb | 14 +++++++------- app/views/projects/project_members/_team.html.haml | 2 +- app/views/projects/project_members/index.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 64cbc76da6f..1c49ebfb99d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -7,15 +7,15 @@ class Projects::ProjectMembersController < Projects::ApplicationController def index @groups = @project.project_group_links - project_members = @project.project_members - project_members = project_members.non_invite unless can?(current_user, :admin_project, @project) + @project_members = @project.project_members + @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) - members_ids = project_members.pluck(:id) - - @members = Member.where(id: members_ids.flatten) - @members_size = @members.size + if params[:search].present? + users = @project.users.search(params[:search]).to_a + @project_members = @project_members.where(user_id: users) + end - @members = @members.page(params[:page]) + @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index d9799033e17..ff54035cfe1 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,7 +2,7 @@ .panel-heading Users with access to %strong #{@project.name} - %span.badge= @members_size + %span.badge= @project_members.total_count %ul.content-list - members.each do |member| = render 'shared/members/member', member: member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 86b2752cc0b..f566748e95a 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -25,5 +25,5 @@ - if @groups.size > 0 = render 'groups', groups: @groups - = render 'team', members: @members - = paginate @members, theme: "gitlab" + = render 'team', members: @project_members + = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From b61cd8a3930f194333ea417a03d53a0ad91efa42 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 10:45:55 +0100 Subject: Added back ordering --- app/controllers/projects/project_members_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 1c49ebfb99d..5fd2e77a51a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -15,6 +15,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project_members.where(user_id: users) end + @project_members = @project_members.order('access_level DESC') @project_members = @project_members.page(params[:page]) @requesters = @project.requesters if can?(current_user, :admin_project, @project) -- cgit v1.2.1 From aac80d76c272523a1ee7c9ef751034e955dfab9e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 11:31:57 +0100 Subject: Group links search test fix --- app/controllers/projects/project_members_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 5fd2e77a51a..4d27617608d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -13,6 +13,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController if params[:search].present? users = @project.users.search(params[:search]).to_a @project_members = @project_members.where(user_id: users) + + group_ids = @groups.pluck(:group_id) + group_ids = Group.where(id: group_ids).search(params[:search]).to_a + @groups = @project.project_group_links.where(group_id: group_ids) end @project_members = @project_members.order('access_level DESC') -- cgit v1.2.1 From b8d41220bde1b5c1f0e86a3346959fc7b760ccf5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 12 Sep 2016 15:34:54 +0100 Subject: Admin group members UI fix --- app/views/shared/members/_member.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 6f8c3c4da2e..80e52bf5637 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -4,7 +4,7 @@ - source = member.source %li.member{ class: dom_class(member), id: dom_id(member) } - %span{ class: ("list-item-name" if show_controls) } + %span.list-item-name - if user = image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' %strong -- cgit v1.2.1 From dffd33252f029901e33883935b20f6b0368d819b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 11:55:14 +0200 Subject: Move reply by email docs to a new location [ci skip] --- config/gitlab.yml.example | 2 +- doc/README.md | 2 +- doc/administration/reply_by_email.md | 302 +++++++++++++++++++ doc/administration/reply_by_email_postfix_setup.md | 324 +++++++++++++++++++++ doc/administration/restart_gitlab.md | 2 +- doc/incoming_email/README.md | 303 +------------------ doc/incoming_email/postfix.md | 322 +------------------- doc/install/installation.md | 2 +- lib/tasks/gitlab/check.rake | 6 +- 9 files changed, 635 insertions(+), 630 deletions(-) create mode 100644 doc/administration/reply_by_email.md create mode 100644 doc/administration/reply_by_email_postfix_setup.md diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1470a6e2550..b26c9f7ccc9 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -111,7 +111,7 @@ production: &base ## Reply by email # Allow users to comment on issues and merge requests by replying to notification emails. - # For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html + # For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html incoming_email: enabled: false diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..cb378e68c60 100644 --- a/doc/README.md +++ b/doc/README.md @@ -42,7 +42,7 @@ - [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. -- [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. +- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md new file mode 100644 index 00000000000..5a9a1582877 --- /dev/null +++ b/doc/administration/reply_by_email.md @@ -0,0 +1,302 @@ +# Reply by email + +GitLab can be set up to allow users to comment on issues and merge requests by +replying to notification emails. + +## Requirement + +Reply by email requires an IMAP-enabled email account. GitLab allows you to use +three strategies for this feature: +- using email sub-addressing +- using a dedicated email address +- using a catch-all mailbox + +### Email sub-addressing + +**If your provider or server supports email sub-addressing, we recommend using it.** + +[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is +a feature where any email to `user+some_arbitrary_tag@example.com` will end up +in the mailbox for `user@example.com`, and is supported by providers such as +Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix +mail server which you can run on-premises. + +### Dedicated email address + +This solution is really simple to set up: you just have to create an email +address dedicated to receive your users' replies to GitLab notifications. + +### Catch-all mailbox + +A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will +"catch all" the emails addressed to the domain that do not exist in the mail +server. + +## How it works? + +### 1. GitLab sends a notification email + +When GitLab sends a notification and Reply by email is enabled, the `Reply-To` +header is set to the address defined in your GitLab configuration, with the +`%{key}` placeholder (if present) replaced by a specific "reply key". In +addition, this "reply key" is also added to the `References` header. + +### 2. You reply to the notification email + +When you reply to the notification email, your email client will: + +- send the email to the `Reply-To` address it got from the notification email +- set the `In-Reply-To` header to the value of the `Message-ID` header from the + notification email +- set the `References` header to the value of the `Message-ID` plus the value of + the notification email's `References` header. + +### 3. GitLab receives your reply to the notification email + +When GitLab receives your reply, it will look for the "reply key" in the +following headers, in this order: + +1. the `To` header +1. the `References` header + +If it finds a reply key, it will be able to leave your reply as a comment on +the entity the notification was about (issue, merge request, commit...). + +For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, +please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). + +## Set it up + +If you want to use Gmail / Google Apps with Reply by email, make sure you have +[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) +and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). + +To set up a basic Postfix mail server with IMAP access on Ubuntu, follow +[these instructions](./postfix.md). + +### Omnibus package installations + +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the + feature and fill in the details for your specific IMAP server and email account: + + ```ruby + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "incoming" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "gitlab.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 143 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = false + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + + ```ruby + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "imap.gmail.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + +1. Reconfigure GitLab and restart mailroom for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart mailroom + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo gitlab-rake gitlab:incoming_email:check + ``` + +1. Reply by email should now be working. + +### Installations from source + +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature + and fill in the details for your specific IMAP server and email account: + + ```sh + sudo editor config/gitlab.yml + ``` + + ```yaml + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "incoming" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "gitlab.example.com" + # IMAP server port + port: 143 + # Whether the IMAP server uses SSL + ssl: false + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: + + ```sh + sudo mkdir -p /etc/default + echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab + ``` + +1. Restart GitLab: + + ```sh + sudo service gitlab restart + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production + ``` + +1. Reply by email should now be working. + +### Development + +1. Go to the GitLab installation directory. + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. + +1. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +1. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +1. Verify that everything is configured correctly: + + ```sh + bundle exec rake gitlab:incoming_email:check RAILS_ENV=development + ``` + +1. Reply by email should now be working. diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md new file mode 100644 index 00000000000..22f10489a6c --- /dev/null +++ b/doc/administration/reply_by_email_postfix_setup.md @@ -0,0 +1,324 @@ +# Set up Postfix for Reply by email + +This document will take you through the steps of setting up a basic Postfix mail +server with IMAP authentication on Ubuntu, to be used with [Reply by email]. + +The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. + +## Configure your server firewall + +1. Open up port 25 on your server so that people can send email into the server over SMTP. +2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. + +## Install packages + +1. Install the `postfix` package if it is not installed already: + + ```sh + sudo apt-get install postfix + ``` + + When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. + +1. Install the `mailutils` package. + + ```sh + sudo apt-get install mailutils + ``` + +## Create user + +1. Create a user for incoming email. + + ```sh + sudo useradd -m -s /bin/bash incoming + ``` + +1. Set a password for this user. + + ```sh + sudo passwd incoming + ``` + + Be sure not to forget this, you'll need it later. + +## Test the out-of-the-box setup + +1. Connect to the local SMTP server: + + ```sh + telnet localhost 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, verify that `postfix` is running: + + ```sh + sudo postfix status + ``` + + If it is not, start it: + + ```sh + sudo postfix start + ``` + +1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo localhost + mail from: root@localhost + rcpt to: incoming@localhost + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + _**Note:** The `.` is a literal period on its own line._ + + _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` + then your Postfix `my_network` configuration is not correct. The error will + say 'Temporary lookup failure'. See + [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ + +1. Check if the `incoming` user received the email: + + ```sh + su - incoming + mail + ``` + + You should see output like this: + + ``` + "/var/mail/incoming": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Configure Postfix to use Maildir-style mailboxes + +Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. + +1. Configure Postfix to use Maildir-style mailboxes: + + ```sh + sudo postconf -e "home_mailbox = Maildir/" + ``` + +1. Restart Postfix: + + ```sh + sudo /etc/init.d/postfix restart + ``` + +1. Test the new setup: + + 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + _**Note:** If `mail` returns an error `Maildir: Is a directory` then your + version of `mail` doesn't support Maildir style mailboxes. Install + `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, + try the above steps again, substituting `heirloom-mailx` for the `mail` + command._ + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Install the Courier IMAP server + +1. Install the `courier-imap` package: + + ```sh + sudo apt-get install courier-imap + ``` + +## Configure Postfix to receive email from the internet + +1. Let Postfix know about the domains that it should consider local: + + ```sh + sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" + ``` + +1. Let Postfix know about the IPs that it should consider part of the LAN: + + We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. + + ```sh + sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" + ``` + +1. Configure Postfix to receive mail on all interfaces, which includes the internet: + + ```sh + sudo postconf -e "inet_interfaces = all" + ``` + +1. Configure Postfix to use the `+` delimiter for sub-addressing: + + ```sh + sudo postconf -e "recipient_delimiter = +" + ``` + +1. Restart Postfix: + + ```sh + sudo service postfix restart + ``` + +## Test the final setup + +1. Test SMTP under the new setup: + + 1. Connect to the SMTP server: + + ```sh + telnet gitlab.example.com 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to gitlab.example.com. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. + + 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo gitlab.example.com + mail from: root@gitlab.example.com + rcpt to: incoming@gitlab.example.com + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + (Note: The `.` is a literal period on its own line) + + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@gitlab.example.com 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + 1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +1. Test IMAP under the new setup: + + 1. Connect to the IMAP server: + + ```sh + telnet gitlab.example.com 143 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to mail.example.gitlab.com. + Escape character is '^]'. + - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. + ``` + + 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: + + ``` + a login incoming PASSWORD + ``` + + Replace PASSWORD with the password you set on the `incoming` user earlier. + + You should see output like this: + + ``` + a OK LOGIN Ok. + ``` + + 1. Disconnect from the IMAP server: + + ```sh + a logout + ``` + +## Done! + +If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. + +--- + +_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ + +[reply by email]: reply_by_email.md diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md index 483060395dd..b561c2f82aa 100644 --- a/doc/administration/restart_gitlab.md +++ b/doc/administration/restart_gitlab.md @@ -139,7 +139,7 @@ If you are using other init systems, like systemd, you can check the [omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages" [install]: ../install/installation.md "Documentation to install GitLab from source" -[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests" +[mailroom]: reply_by_email.md "Used for replying by email in GitLab issues and merge requests" [chef]: https://www.chef.io/chef/ "Chef official website" [src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file" [gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository" diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md index 5a9a1582877..db0f03f2c98 100644 --- a/doc/incoming_email/README.md +++ b/doc/incoming_email/README.md @@ -1,302 +1 @@ -# Reply by email - -GitLab can be set up to allow users to comment on issues and merge requests by -replying to notification emails. - -## Requirement - -Reply by email requires an IMAP-enabled email account. GitLab allows you to use -three strategies for this feature: -- using email sub-addressing -- using a dedicated email address -- using a catch-all mailbox - -### Email sub-addressing - -**If your provider or server supports email sub-addressing, we recommend using it.** - -[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is -a feature where any email to `user+some_arbitrary_tag@example.com` will end up -in the mailbox for `user@example.com`, and is supported by providers such as -Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix -mail server which you can run on-premises. - -### Dedicated email address - -This solution is really simple to set up: you just have to create an email -address dedicated to receive your users' replies to GitLab notifications. - -### Catch-all mailbox - -A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will -"catch all" the emails addressed to the domain that do not exist in the mail -server. - -## How it works? - -### 1. GitLab sends a notification email - -When GitLab sends a notification and Reply by email is enabled, the `Reply-To` -header is set to the address defined in your GitLab configuration, with the -`%{key}` placeholder (if present) replaced by a specific "reply key". In -addition, this "reply key" is also added to the `References` header. - -### 2. You reply to the notification email - -When you reply to the notification email, your email client will: - -- send the email to the `Reply-To` address it got from the notification email -- set the `In-Reply-To` header to the value of the `Message-ID` header from the - notification email -- set the `References` header to the value of the `Message-ID` plus the value of - the notification email's `References` header. - -### 3. GitLab receives your reply to the notification email - -When GitLab receives your reply, it will look for the "reply key" in the -following headers, in this order: - -1. the `To` header -1. the `References` header - -If it finds a reply key, it will be able to leave your reply as a comment on -the entity the notification was about (issue, merge request, commit...). - -For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, -please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). - -## Set it up - -If you want to use Gmail / Google Apps with Reply by email, make sure you have -[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) -and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). - -To set up a basic Postfix mail server with IMAP access on Ubuntu, follow -[these instructions](./postfix.md). - -### Omnibus package installations - -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the - feature and fill in the details for your specific IMAP server and email account: - - ```ruby - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "incoming" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "gitlab.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 143 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = false - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - - ```ruby - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "imap.gmail.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 993 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - -1. Reconfigure GitLab and restart mailroom for the changes to take effect: - - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart mailroom - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo gitlab-rake gitlab:incoming_email:check - ``` - -1. Reply by email should now be working. - -### Installations from source - -1. Go to the GitLab installation directory: - - ```sh - cd /home/git/gitlab - ``` - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature - and fill in the details for your specific IMAP server and email account: - - ```sh - sudo editor config/gitlab.yml - ``` - - ```yaml - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "incoming" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "gitlab.example.com" - # IMAP server port - port: 143 - # Whether the IMAP server uses SSL - ssl: false - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - -1. Enable `mail_room` in the init script at `/etc/default/gitlab`: - - ```sh - sudo mkdir -p /etc/default - echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab - ``` - -1. Restart GitLab: - - ```sh - sudo service gitlab restart - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production - ``` - -1. Reply by email should now be working. - -### Development - -1. Go to the GitLab installation directory. - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. - -1. Uncomment the `mail_room` line in your `Procfile`: - - ```yaml - mail_room: bundle exec mail_room -q -c config/mail_room.yml - ``` - -1. Restart GitLab: - - ```sh - bundle exec foreman start - ``` - -1. Verify that everything is configured correctly: - - ```sh - bundle exec rake gitlab:incoming_email:check RAILS_ENV=development - ``` - -1. Reply by email should now be working. +This document was moved to [administration/reply_by_email](../administration/reply_by_email.md). diff --git a/doc/incoming_email/postfix.md b/doc/incoming_email/postfix.md index 787d21f7f8f..90833238ac5 100644 --- a/doc/incoming_email/postfix.md +++ b/doc/incoming_email/postfix.md @@ -1,321 +1 @@ -# Set up Postfix for Reply by email - -This document will take you through the steps of setting up a basic Postfix mail server with IMAP authentication on Ubuntu, to be used with Reply by email. - -The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. - -## Configure your server firewall - -1. Open up port 25 on your server so that people can send email into the server over SMTP. -2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. - -## Install packages - -1. Install the `postfix` package if it is not installed already: - - ```sh - sudo apt-get install postfix - ``` - - When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. - -1. Install the `mailutils` package. - - ```sh - sudo apt-get install mailutils - ``` - -## Create user - -1. Create a user for incoming email. - - ```sh - sudo useradd -m -s /bin/bash incoming - ``` - -1. Set a password for this user. - - ```sh - sudo passwd incoming - ``` - - Be sure not to forget this, you'll need it later. - -## Test the out-of-the-box setup - -1. Connect to the local SMTP server: - - ```sh - telnet localhost 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 127.0.0.1... - Connected to localhost. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, verify that `postfix` is running: - - ```sh - sudo postfix status - ``` - - If it is not, start it: - - ```sh - sudo postfix start - ``` - -1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo localhost - mail from: root@localhost - rcpt to: incoming@localhost - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - _**Note:** The `.` is a literal period on its own line._ - - _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` - then your Postfix `my_network` configuration is not correct. The error will - say 'Temporary lookup failure'. See - [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ - -1. Check if the `incoming` user received the email: - - ```sh - su - incoming - mail - ``` - - You should see output like this: - - ``` - "/var/mail/incoming": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Configure Postfix to use Maildir-style mailboxes - -Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. - -1. Configure Postfix to use Maildir-style mailboxes: - - ```sh - sudo postconf -e "home_mailbox = Maildir/" - ``` - -1. Restart Postfix: - - ```sh - sudo /etc/init.d/postfix restart - ``` - -1. Test the new setup: - - 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - _**Note:** If `mail` returns an error `Maildir: Is a directory` then your - version of `mail` doesn't support Maildir style mailboxes. Install - `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, - try the above steps again, substituting `heirloom-mailx` for the `mail` - command._ - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Install the Courier IMAP server - -1. Install the `courier-imap` package: - - ```sh - sudo apt-get install courier-imap - ``` - -## Configure Postfix to receive email from the internet - -1. Let Postfix know about the domains that it should consider local: - - ```sh - sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" - ``` - -1. Let Postfix know about the IPs that it should consider part of the LAN: - - We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. - - ```sh - sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" - ``` - -1. Configure Postfix to receive mail on all interfaces, which includes the internet: - - ```sh - sudo postconf -e "inet_interfaces = all" - ``` - -1. Configure Postfix to use the `+` delimiter for sub-addressing: - - ```sh - sudo postconf -e "recipient_delimiter = +" - ``` - -1. Restart Postfix: - - ```sh - sudo service postfix restart - ``` - -## Test the final setup - -1. Test SMTP under the new setup: - - 1. Connect to the SMTP server: - - ```sh - telnet gitlab.example.com 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to gitlab.example.com. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. - - 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo gitlab.example.com - mail from: root@gitlab.example.com - rcpt to: incoming@gitlab.example.com - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - (Note: The `.` is a literal period on its own line) - - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@gitlab.example.com 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - 1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -1. Test IMAP under the new setup: - - 1. Connect to the IMAP server: - - ```sh - telnet gitlab.example.com 143 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to mail.example.gitlab.com. - Escape character is '^]'. - - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. - ``` - - 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: - - ``` - a login incoming PASSWORD - ``` - - Replace PASSWORD with the password you set on the `incoming` user earlier. - - You should see output like this: - - ``` - a OK LOGIN Ok. - ``` - - 1. Disconnect from the IMAP server: - - ```sh - a logout - ``` - -## Done! - -If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. - ---------- - -_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ +This document was moved to [administration/reply_by_email_postfix_setup](../administration/reply_by_email_postfix_setup.md). diff --git a/doc/install/installation.md b/doc/install/installation.md index 3ac813aa914..da3f92f9a6c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -563,7 +563,7 @@ Using a self-signed certificate is discouraged but if you must use it follow the ### Enable Reply by email -See the ["Reply by email" documentation](../incoming_email/README.md) for more information on how to set this up. +See the ["Reply by email" documentation](../administration/reply_by_email.md) for more information on how to set this up. ### LDAP Authentication diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 5f4a6bbfa35..2ae48a970ce 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -671,7 +671,7 @@ namespace :gitlab do "Enable mail_room in the init.d configuration." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -690,7 +690,7 @@ namespace :gitlab do "Enable mail_room in your Procfile." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -747,7 +747,7 @@ namespace :gitlab do "Check that the information in config/gitlab.yml is correct" ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end -- cgit v1.2.1 From 6207dc9037601d5188c86876c2ff79d6ddbbe540 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:16:14 +0200 Subject: Move monitoring/ to new location --- doc/README.md | 4 +- doc/administration/monitoring/health_check.md | 66 +++++++ .../monitoring/img/health_check_token.png | Bin 0 -> 6630 bytes .../monitoring/performance/gitlab_configuration.md | 40 +++++ .../performance/grafana_configuration.md | 111 ++++++++++++ .../performance/img/grafana_dashboard_dropdown.png | Bin 0 -> 14368 bytes .../performance/img/grafana_dashboard_import.png | Bin 0 -> 18267 bytes .../img/grafana_data_source_configuration.png | Bin 0 -> 26060 bytes .../performance/img/grafana_data_source_empty.png | Bin 0 -> 21821 bytes .../performance/img/grafana_save_icon.png | Bin 0 -> 9107 bytes .../img/metrics_gitlab_configuration_settings.png | Bin 0 -> 61357 bytes .../performance/influxdb_configuration.md | 193 ++++++++++++++++++++ .../monitoring/performance/influxdb_schema.md | 97 +++++++++++ .../monitoring/performance/introduction.md | 65 +++++++ doc/monitoring/health_check.md | 67 +------ doc/monitoring/img/health_check_token.png | Bin 6630 -> 0 bytes doc/monitoring/performance/gitlab_configuration.md | 41 +---- .../performance/grafana_configuration.md | 112 +----------- .../performance/influxdb_configuration.md | 194 +-------------------- doc/monitoring/performance/influxdb_schema.md | 98 +---------- doc/monitoring/performance/introduction.md | 66 +------ 21 files changed, 580 insertions(+), 574 deletions(-) create mode 100644 doc/administration/monitoring/health_check.md create mode 100644 doc/administration/monitoring/img/health_check_token.png create mode 100644 doc/administration/monitoring/performance/gitlab_configuration.md create mode 100644 doc/administration/monitoring/performance/grafana_configuration.md create mode 100644 doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png create mode 100644 doc/administration/monitoring/performance/img/grafana_dashboard_import.png create mode 100644 doc/administration/monitoring/performance/img/grafana_data_source_configuration.png create mode 100644 doc/administration/monitoring/performance/img/grafana_data_source_empty.png create mode 100644 doc/administration/monitoring/performance/img/grafana_save_icon.png create mode 100644 doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png create mode 100644 doc/administration/monitoring/performance/influxdb_configuration.md create mode 100644 doc/administration/monitoring/performance/influxdb_schema.md create mode 100644 doc/administration/monitoring/performance/introduction.md delete mode 100644 doc/monitoring/img/health_check_token.png diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..5a6f467c17c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -46,8 +46,8 @@ - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint. +- [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Monitoring uptime](administration/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/administration/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/administration/monitoring/img/health_check_token.png b/doc/administration/monitoring/img/health_check_token.png new file mode 100644 index 00000000000..2d7c82a65a8 Binary files /dev/null and b/doc/administration/monitoring/img/health_check_token.png differ diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md new file mode 100644 index 00000000000..771584268d9 --- /dev/null +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -0,0 +1,40 @@ +# GitLab Configuration + +GitLab Performance Monitoring is disabled by default. To enable it and change any of its +settings, navigate to the Admin area in **Settings > Metrics** +(`/admin/application_settings`). + +The minimum required settings you need to set are the InfluxDB host and port. +Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the +changes. + +--- + +![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) + +--- + +Finally, a restart of all GitLab processes is required for the changes to take +effect: + +```bash +# For Omnibus installations +sudo gitlab-ctl restart + +# For installations from source +sudo service gitlab restart +``` + +## Pending Migrations + +When any migrations are pending, the metrics are disabled until the migrations +have been performed. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md new file mode 100644 index 00000000000..7947b0fedc4 --- /dev/null +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -0,0 +1,111 @@ +# Grafana Configuration + +[Grafana](http://grafana.org/) is a tool that allows you to visualize time +series metrics through graphs and dashboards. It supports several backend +data stores, including InfluxDB. GitLab writes performance data to InfluxDB +and Grafana will allow you to query InfluxDB to display useful graphs. + +For the easiest installation and configuration, install Grafana on the same +server as InfluxDB. For larger installations, you may want to split out these +services. + +## Installation + +Grafana supplies package repositories (Yum/Apt) for easy installation. +See [Grafana installation documentation](http://docs.grafana.org/installation/) +for detailed steps. + +> **Note**: Before starting Grafana for the first time, set the admin user +and password in `/etc/grafana/grafana.ini`. Otherwise, the default password +will be `admin`. + +## Configuration + +Login as the admin user. Expand the menu by clicking the Grafana logo in the +top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' +in the top bar. + +![Grafana empty data source page](img/grafana_data_source_empty.png) + +Fill in the configuration details for the InfluxDB data source. Save and +Test Connection to ensure the configuration is correct. + +- **Name**: InfluxDB +- **Default**: Checked +- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) +- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB +on a separate server) +- **Access**: proxy +- **Database**: gitlab +- **User**: admin (Or the username configured when setting up InfluxDB) +- **Password**: The password configured when you set up InfluxDB + +![Grafana data source configurations](img/grafana_data_source_configuration.png) + +## Apply retention policies and create continuous queries + +If you intend to import the GitLab provided Grafana dashboards, you will need to +set up the right retention policies and continuous queries. The easiest way of +doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) +repository. + +To use this repository you must first clone it: + +``` +git clone https://gitlab.com/gitlab-org/influxdb-management.git +cd influxdb-management +``` + +Next you must install the required dependencies: + +``` +gem install bundler +bundle install +``` + +Now you must configure the repository by first copying `.env.example` to `.env` +and then editing the `.env` file to contain the correct InfluxDB settings. Once +configured you can simply run `bundle exec rake` and the InfluxDB database will +be configured for you. + +For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). + +## Import Dashboards + +You can now import a set of default dashboards that will give you a good +start on displaying useful information. GitLab has published a set of default +[Grafana dashboards][grafana-dashboards] to get you started. Clone the +repository or download a zip/tarball, then follow these steps to import each +JSON file. + +Open the dashboard dropdown menu and click 'Import' + +![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) + +Click 'Choose file' and browse to the location where you downloaded or cloned +the dashboard repository. Pick one of the JSON files to import. + +![Grafana dashboard import](img/grafana_dashboard_import.png) + +Once the dashboard is imported, be sure to click save icon in the top bar. If +you do not save the dashboard after importing it will be removed when you +navigate away. + +![Grafana save icon](img/grafana_save_icon.png) + +Repeat this process for each dashboard you wish to import. + +Alternatively you can automatically import all the dashboards into your Grafana +instance. See the README of the [Grafana dashboards][grafana-dashboards] +repository for more information on this process. + +[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Installation/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png new file mode 100644 index 00000000000..7e34fad71ce Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_import.png b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png new file mode 100644 index 00000000000..f97624365c7 Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png new file mode 100644 index 00000000000..7d50e4c88c2 Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_empty.png b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png new file mode 100644 index 00000000000..aa39a53acae Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_save_icon.png b/doc/administration/monitoring/performance/img/grafana_save_icon.png new file mode 100644 index 00000000000..c740e33cd1c Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_save_icon.png differ diff --git a/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png new file mode 100644 index 00000000000..db396423e30 Binary files /dev/null and b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png differ diff --git a/doc/administration/monitoring/performance/influxdb_configuration.md b/doc/administration/monitoring/performance/influxdb_configuration.md new file mode 100644 index 00000000000..c30cd2950d8 --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_configuration.md @@ -0,0 +1,193 @@ +# InfluxDB Configuration + +The default settings provided by [InfluxDB] are not sufficient for a high traffic +GitLab environment. The settings discussed in this document are based on the +settings GitLab uses for GitLab.com, depending on your own needs you may need to +further adjust them. + +If you are intending to run InfluxDB on the same server as GitLab, make sure +you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. + +Unless you are going with a budget setup, it's advised to run it separately. + +## Requirements + +- InfluxDB 0.9.5 or newer +- A fairly modern version of Linux +- At least 4GB of RAM +- At least 10GB of storage for InfluxDB data + +Note that the RAM and storage requirements can differ greatly depending on the +amount of data received/stored. To limit the amount of stored data users can +look into [InfluxDB Retention Policies][influxdb-retention]. + +## Installation + +Installing InfluxDB is out of the scope of this document. Please refer to the +[InfluxDB documentation]. + +## InfluxDB Server Settings + +Since InfluxDB has many settings that users may wish to customize themselves +(e.g. what port to run InfluxDB on), we'll only cover the essentials. + +The configuration file in question is usually located at +`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, +InfluxDB needs to be restarted. + +### Storage Engine + +InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new +storage engine is available, called [TSM Tree]. All users **must** use the new +`tsm1` storage engine as this [will be the default engine][tsm1-commit] in +upcoming InfluxDB releases. + +Make sure you have the following in your configuration file: + +``` +[data] + dir = "/var/lib/influxdb/data" + engine = "tsm1" +``` + +### Admin Panel + +Production environments should have the InfluxDB admin panel **disabled**. This +feature can be disabled by adding the following to your InfluxDB configuration +file: + +``` +[admin] + enabled = false +``` + +### HTTP + +HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, +thus it should be enabled. When enabling make sure to _also_ enable +authentication: + +``` +[http] + enabled = true + auth-enabled = true +``` + +_**Note:** Before you enable authentication, you might want to [create an +admin user](#create-a-new-admin-user)._ + +### UDP + +GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling +UDP can be done using the following settings: + +``` +[[udp]] + enabled = true + bind-address = ":8089" + database = "gitlab" + batch-size = 1000 + batch-pending = 5 + batch-timeout = "1s" + read-buffer = 209715200 +``` + +This does the following: + +1. Enable UDP and bind it to port 8089 for all addresses. +2. Store any data received in the "gitlab" database. +3. Define a batch of points to be 1000 points in size and allow a maximum of + 5 batches _or_ flush them automatically after 1 second. +4. Define a UDP read buffer size of 200 MB. + +One of the most important settings here is the UDP read buffer size as if this +value is set too low, packets will be dropped. You must also make sure the OS +buffer size is set to the same value, the default value is almost never enough. + +To set the OS buffer size to 200 MB, on Linux you can run the following command: + +```bash +sysctl -w net.core.rmem_max=209715200 +``` + +To make this permanent, add the following to `/etc/sysctl.conf` and restart the +server: + +```bash +net.core.rmem_max=209715200 +``` + +It is **very important** to make sure the buffer sizes are large enough to +handle all data sent to InfluxDB as otherwise you _will_ lose data. The above +buffer sizes are based on the traffic for GitLab.com. Depending on the amount of +traffic, users may be able to use a smaller buffer size, but we highly recommend +using _at least_ 100 MB. + +When enabling UDP, users should take care to not expose the port to the public, +as doing so will allow anybody to write data into your InfluxDB database (as +[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either +whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only +allowing traffic from members of said VLAN. + +## Create a new admin user + +If you want to [enable authentication](#http), you might want to [create an +admin user][influx-admin]: + +``` +influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" +``` + +## Create the `gitlab` database + +Once you get InfluxDB up and running, you need to create a database for GitLab. +Make sure you have changed the [storage engine](#storage-engine) to `tsm1` +before creating a database. + +_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled +[HTTP authentication](#http), remember to append the username (`-username `) +and password (`-password `) you set earlier to the commands below._ + +Run the following command to create a database named `gitlab`: + +```bash +influx -execute 'CREATE DATABASE gitlab' +``` + +The name **must** be `gitlab`, do not use any other name. + +Next, make sure that the database was successfully created: + +```bash +influx -execute 'SHOW DATABASES' +``` + +The output should be similar to: + +``` +name: databases +--------------- +name +_internal +gitlab +``` + +That's it! Now your GitLab instance should send data to InfluxDB. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management +[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ +[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ +[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ +[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d +[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user diff --git a/doc/administration/monitoring/performance/influxdb_schema.md b/doc/administration/monitoring/performance/influxdb_schema.md new file mode 100644 index 00000000000..eff0e29f58d --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_schema.md @@ -0,0 +1,97 @@ +# InfluxDB Schema + +The following measurements are currently stored in InfluxDB: + +- `PROCESS_file_descriptors` +- `PROCESS_gc_statistics` +- `PROCESS_memory_usage` +- `PROCESS_method_calls` +- `PROCESS_object_counts` +- `PROCESS_transactions` +- `PROCESS_views` +- `events` + +Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the +process type. In all series, any form of duration is stored in milliseconds. + +## PROCESS_file_descriptors + +This measurement contains the number of open file descriptors over time. The +value field `value` contains the number of descriptors. + +## PROCESS_gc_statistics + +This measurement contains Ruby garbage collection statistics such as the amount +of minor/major GC runs (relative to the last sampling interval), the time spent +in garbage collection cycles, and all fields/values returned by `GC.stat`. + +## PROCESS_memory_usage + +This measurement contains the process' memory usage (in bytes) over time. The +value field `value` contains the number of bytes. + +## PROCESS_method_calls + +This measurement contains the methods called during a transaction along with +their duration, and a name of the transaction action that invoked the method (if +available). The method call duration is stored in the value field `duration`, +while the method name is stored in the tag `method`. The tag `action` contains +the full name of the transaction action. Both the `method` and `action` fields +are in the following format: + +``` +ClassName#method_name +``` + +For example, a method called by the `show` method in the `UsersController` class +would have `action` set to `UsersController#show`. + +## PROCESS_object_counts + +This measurement is used to store retained Ruby objects (per class) and the +amount of retained objects. The number of objects is stored in the `count` value +field while the class name is stored in the `type` tag. + +## PROCESS_transactions + +This measurement is used to store basic transaction details such as the time it +took to complete a transaction, how much time was spent in SQL queries, etc. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The total duration of the transaction | +| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | +| `method_duration` | The total time spent in method calls | +| `sql_duration` | The total time spent in SQL queries | +| `view_duration` | The total time spent in views | + +## PROCESS_views + +This measurement is used to store view rendering timings for a transaction. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The rendering time of the view | +| `view` | The path of the view, relative to the application's root directory | + +The `action` tag contains the action name of the transaction that rendered the +view. + +## events + +This measurement is used to store generic events such as the number of Git +pushes, Emails sent, etc. Each point in this measurement has a single value +field called `count`. The value of this field is simply set to `1`. Each point +also has at least one tag: `event`. This tag's value is set to the event name. +Depending on the event type additional tags may be available as well. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md new file mode 100644 index 00000000000..79904916b7e --- /dev/null +++ b/doc/administration/monitoring/performance/introduction.md @@ -0,0 +1,65 @@ +# GitLab Performance Monitoring + +GitLab comes with its own application performance measuring system as of GitLab +8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the +Community and Enterprise editions. + +Apart from this introduction, you are advised to read through the following +documents in order to understand and properly configure GitLab Performance Monitoring: + +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Install/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +## Introduction to GitLab Performance Monitoring + +GitLab Performance Monitoring makes it possible to measure a wide variety of statistics +including (but not limited to): + +- The time it took to complete a transaction (a web request or Sidekiq job). +- The time spent in running SQL queries and rendering HAML views. +- The time spent executing (instrumented) Ruby methods. +- Ruby object allocations, and retained objects in particular. +- System statistics such as the process' memory usage and open file descriptors. +- Ruby garbage collection statistics. + +Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored +data can be visualized using [Grafana][grafana] or any other application that +supports reading data from InfluxDB. Alternatively data can be queried using the +InfluxDB CLI. + +## Metric Types + +Two types of metrics are collected: + +1. Transaction specific metrics. +1. Sampled metrics, collected at a certain interval in a separate thread. + +### Transaction Metrics + +Transaction metrics are metrics that can be associated with a single +transaction. This includes statistics such as the transaction duration, timings +of any executed SQL queries, time spent rendering HAML views, etc. These metrics +are collected for every Rack request and Sidekiq job processed. + +### Sampled Metrics + +Sampled metrics are metrics that can't be associated with a single transaction. +Examples include garbage collection statistics and retained Ruby objects. These +metrics are collected at a regular interval. This interval is made up out of two +parts: + +1. A user defined interval. +1. A randomly generated offset added on top of the interval, the same offset + can't be used twice in a row. + +The actual interval can be anywhere between a half of the defined interval and a +half above the interval. For example, for a user defined interval of 15 seconds +the actual interval can be anywhere between 7.5 and 22.5. The interval is +re-generated for every sampling run instead of being generated once and re-used +for the duration of the process' lifetime. + +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[grafana]: http://grafana.org/ diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index eac57bc3de4..23ae1b17258 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1,66 +1 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring +This document was moved to [administration/monitoring/health_check](../administration/monitoring/health_check.md). diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png deleted file mode 100644 index 2d7c82a65a8..00000000000 Binary files a/doc/monitoring/img/health_check_token.png and /dev/null differ diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index 771584268d9..a669bb28904 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -1,40 +1 @@ -# GitLab Configuration - -GitLab Performance Monitoring is disabled by default. To enable it and change any of its -settings, navigate to the Admin area in **Settings > Metrics** -(`/admin/application_settings`). - -The minimum required settings you need to set are the InfluxDB host and port. -Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the -changes. - ---- - -![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) - ---- - -Finally, a restart of all GitLab processes is required for the changes to take -effect: - -```bash -# For Omnibus installations -sudo gitlab-ctl restart - -# For installations from source -sudo service gitlab restart -``` - -## Pending Migrations - -When any migrations are pending, the metrics are disabled until the migrations -have been performed. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/gitlab_configuration](../administration/monitoring/performance/gitlab_configuration.md). diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 7947b0fedc4..93320b40174 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1,111 +1 @@ -# Grafana Configuration - -[Grafana](http://grafana.org/) is a tool that allows you to visualize time -series metrics through graphs and dashboards. It supports several backend -data stores, including InfluxDB. GitLab writes performance data to InfluxDB -and Grafana will allow you to query InfluxDB to display useful graphs. - -For the easiest installation and configuration, install Grafana on the same -server as InfluxDB. For larger installations, you may want to split out these -services. - -## Installation - -Grafana supplies package repositories (Yum/Apt) for easy installation. -See [Grafana installation documentation](http://docs.grafana.org/installation/) -for detailed steps. - -> **Note**: Before starting Grafana for the first time, set the admin user -and password in `/etc/grafana/grafana.ini`. Otherwise, the default password -will be `admin`. - -## Configuration - -Login as the admin user. Expand the menu by clicking the Grafana logo in the -top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' -in the top bar. - -![Grafana empty data source page](img/grafana_data_source_empty.png) - -Fill in the configuration details for the InfluxDB data source. Save and -Test Connection to ensure the configuration is correct. - -- **Name**: InfluxDB -- **Default**: Checked -- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) -- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB -on a separate server) -- **Access**: proxy -- **Database**: gitlab -- **User**: admin (Or the username configured when setting up InfluxDB) -- **Password**: The password configured when you set up InfluxDB - -![Grafana data source configurations](img/grafana_data_source_configuration.png) - -## Apply retention policies and create continuous queries - -If you intend to import the GitLab provided Grafana dashboards, you will need to -set up the right retention policies and continuous queries. The easiest way of -doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) -repository. - -To use this repository you must first clone it: - -``` -git clone https://gitlab.com/gitlab-org/influxdb-management.git -cd influxdb-management -``` - -Next you must install the required dependencies: - -``` -gem install bundler -bundle install -``` - -Now you must configure the repository by first copying `.env.example` to `.env` -and then editing the `.env` file to contain the correct InfluxDB settings. Once -configured you can simply run `bundle exec rake` and the InfluxDB database will -be configured for you. - -For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). - -## Import Dashboards - -You can now import a set of default dashboards that will give you a good -start on displaying useful information. GitLab has published a set of default -[Grafana dashboards][grafana-dashboards] to get you started. Clone the -repository or download a zip/tarball, then follow these steps to import each -JSON file. - -Open the dashboard dropdown menu and click 'Import' - -![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) - -Click 'Choose file' and browse to the location where you downloaded or cloned -the dashboard repository. Pick one of the JSON files to import. - -![Grafana dashboard import](img/grafana_dashboard_import.png) - -Once the dashboard is imported, be sure to click save icon in the top bar. If -you do not save the dashboard after importing it will be removed when you -navigate away. - -![Grafana save icon](img/grafana_save_icon.png) - -Repeat this process for each dashboard you wish to import. - -Alternatively you can automatically import all the dashboards into your Grafana -instance. See the README of the [Grafana dashboards][grafana-dashboards] -repository for more information on this process. - -[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Installation/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) +This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index c30cd2950d8..02647de1eb0 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -1,193 +1 @@ -# InfluxDB Configuration - -The default settings provided by [InfluxDB] are not sufficient for a high traffic -GitLab environment. The settings discussed in this document are based on the -settings GitLab uses for GitLab.com, depending on your own needs you may need to -further adjust them. - -If you are intending to run InfluxDB on the same server as GitLab, make sure -you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. - -Unless you are going with a budget setup, it's advised to run it separately. - -## Requirements - -- InfluxDB 0.9.5 or newer -- A fairly modern version of Linux -- At least 4GB of RAM -- At least 10GB of storage for InfluxDB data - -Note that the RAM and storage requirements can differ greatly depending on the -amount of data received/stored. To limit the amount of stored data users can -look into [InfluxDB Retention Policies][influxdb-retention]. - -## Installation - -Installing InfluxDB is out of the scope of this document. Please refer to the -[InfluxDB documentation]. - -## InfluxDB Server Settings - -Since InfluxDB has many settings that users may wish to customize themselves -(e.g. what port to run InfluxDB on), we'll only cover the essentials. - -The configuration file in question is usually located at -`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, -InfluxDB needs to be restarted. - -### Storage Engine - -InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new -storage engine is available, called [TSM Tree]. All users **must** use the new -`tsm1` storage engine as this [will be the default engine][tsm1-commit] in -upcoming InfluxDB releases. - -Make sure you have the following in your configuration file: - -``` -[data] - dir = "/var/lib/influxdb/data" - engine = "tsm1" -``` - -### Admin Panel - -Production environments should have the InfluxDB admin panel **disabled**. This -feature can be disabled by adding the following to your InfluxDB configuration -file: - -``` -[admin] - enabled = false -``` - -### HTTP - -HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, -thus it should be enabled. When enabling make sure to _also_ enable -authentication: - -``` -[http] - enabled = true - auth-enabled = true -``` - -_**Note:** Before you enable authentication, you might want to [create an -admin user](#create-a-new-admin-user)._ - -### UDP - -GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling -UDP can be done using the following settings: - -``` -[[udp]] - enabled = true - bind-address = ":8089" - database = "gitlab" - batch-size = 1000 - batch-pending = 5 - batch-timeout = "1s" - read-buffer = 209715200 -``` - -This does the following: - -1. Enable UDP and bind it to port 8089 for all addresses. -2. Store any data received in the "gitlab" database. -3. Define a batch of points to be 1000 points in size and allow a maximum of - 5 batches _or_ flush them automatically after 1 second. -4. Define a UDP read buffer size of 200 MB. - -One of the most important settings here is the UDP read buffer size as if this -value is set too low, packets will be dropped. You must also make sure the OS -buffer size is set to the same value, the default value is almost never enough. - -To set the OS buffer size to 200 MB, on Linux you can run the following command: - -```bash -sysctl -w net.core.rmem_max=209715200 -``` - -To make this permanent, add the following to `/etc/sysctl.conf` and restart the -server: - -```bash -net.core.rmem_max=209715200 -``` - -It is **very important** to make sure the buffer sizes are large enough to -handle all data sent to InfluxDB as otherwise you _will_ lose data. The above -buffer sizes are based on the traffic for GitLab.com. Depending on the amount of -traffic, users may be able to use a smaller buffer size, but we highly recommend -using _at least_ 100 MB. - -When enabling UDP, users should take care to not expose the port to the public, -as doing so will allow anybody to write data into your InfluxDB database (as -[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either -whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only -allowing traffic from members of said VLAN. - -## Create a new admin user - -If you want to [enable authentication](#http), you might want to [create an -admin user][influx-admin]: - -``` -influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" -``` - -## Create the `gitlab` database - -Once you get InfluxDB up and running, you need to create a database for GitLab. -Make sure you have changed the [storage engine](#storage-engine) to `tsm1` -before creating a database. - -_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled -[HTTP authentication](#http), remember to append the username (`-username `) -and password (`-password `) you set earlier to the commands below._ - -Run the following command to create a database named `gitlab`: - -```bash -influx -execute 'CREATE DATABASE gitlab' -``` - -The name **must** be `gitlab`, do not use any other name. - -Next, make sure that the database was successfully created: - -```bash -influx -execute 'SHOW DATABASES' -``` - -The output should be similar to: - -``` -name: databases ---------------- -name -_internal -gitlab -``` - -That's it! Now your GitLab instance should send data to InfluxDB. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management -[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ -[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ -[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ -[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d -[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user +This document was moved to [administration/monitoring/performance/influxdb_configuration](../administration/monitoring/performance/influxdb_configuration.md). diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index eff0e29f58d..a989e323e04 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -1,97 +1 @@ -# InfluxDB Schema - -The following measurements are currently stored in InfluxDB: - -- `PROCESS_file_descriptors` -- `PROCESS_gc_statistics` -- `PROCESS_memory_usage` -- `PROCESS_method_calls` -- `PROCESS_object_counts` -- `PROCESS_transactions` -- `PROCESS_views` -- `events` - -Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the -process type. In all series, any form of duration is stored in milliseconds. - -## PROCESS_file_descriptors - -This measurement contains the number of open file descriptors over time. The -value field `value` contains the number of descriptors. - -## PROCESS_gc_statistics - -This measurement contains Ruby garbage collection statistics such as the amount -of minor/major GC runs (relative to the last sampling interval), the time spent -in garbage collection cycles, and all fields/values returned by `GC.stat`. - -## PROCESS_memory_usage - -This measurement contains the process' memory usage (in bytes) over time. The -value field `value` contains the number of bytes. - -## PROCESS_method_calls - -This measurement contains the methods called during a transaction along with -their duration, and a name of the transaction action that invoked the method (if -available). The method call duration is stored in the value field `duration`, -while the method name is stored in the tag `method`. The tag `action` contains -the full name of the transaction action. Both the `method` and `action` fields -are in the following format: - -``` -ClassName#method_name -``` - -For example, a method called by the `show` method in the `UsersController` class -would have `action` set to `UsersController#show`. - -## PROCESS_object_counts - -This measurement is used to store retained Ruby objects (per class) and the -amount of retained objects. The number of objects is stored in the `count` value -field while the class name is stored in the `type` tag. - -## PROCESS_transactions - -This measurement is used to store basic transaction details such as the time it -took to complete a transaction, how much time was spent in SQL queries, etc. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The total duration of the transaction | -| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | -| `method_duration` | The total time spent in method calls | -| `sql_duration` | The total time spent in SQL queries | -| `view_duration` | The total time spent in views | - -## PROCESS_views - -This measurement is used to store view rendering timings for a transaction. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The rendering time of the view | -| `view` | The path of the view, relative to the application's root directory | - -The `action` tag contains the action name of the transaction that rendered the -view. - -## events - -This measurement is used to store generic events such as the number of Git -pushes, Emails sent, etc. Each point in this measurement has a single value -field called `count`. The value of this field is simply set to `1`. Each point -also has at least one tag: `event`. This tag's value is set to the event name. -Depending on the event type additional tags may be available as well. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/influxdb_schema](../administration/monitoring/performance/influxdb_schema.md). diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md index 79904916b7e..ab3f3ac1664 100644 --- a/doc/monitoring/performance/introduction.md +++ b/doc/monitoring/performance/introduction.md @@ -1,65 +1 @@ -# GitLab Performance Monitoring - -GitLab comes with its own application performance measuring system as of GitLab -8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the -Community and Enterprise editions. - -Apart from this introduction, you are advised to read through the following -documents in order to understand and properly configure GitLab Performance Monitoring: - -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Install/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -## Introduction to GitLab Performance Monitoring - -GitLab Performance Monitoring makes it possible to measure a wide variety of statistics -including (but not limited to): - -- The time it took to complete a transaction (a web request or Sidekiq job). -- The time spent in running SQL queries and rendering HAML views. -- The time spent executing (instrumented) Ruby methods. -- Ruby object allocations, and retained objects in particular. -- System statistics such as the process' memory usage and open file descriptors. -- Ruby garbage collection statistics. - -Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored -data can be visualized using [Grafana][grafana] or any other application that -supports reading data from InfluxDB. Alternatively data can be queried using the -InfluxDB CLI. - -## Metric Types - -Two types of metrics are collected: - -1. Transaction specific metrics. -1. Sampled metrics, collected at a certain interval in a separate thread. - -### Transaction Metrics - -Transaction metrics are metrics that can be associated with a single -transaction. This includes statistics such as the transaction duration, timings -of any executed SQL queries, time spent rendering HAML views, etc. These metrics -are collected for every Rack request and Sidekiq job processed. - -### Sampled Metrics - -Sampled metrics are metrics that can't be associated with a single transaction. -Examples include garbage collection statistics and retained Ruby objects. These -metrics are collected at a regular interval. This interval is made up out of two -parts: - -1. A user defined interval. -1. A randomly generated offset added on top of the interval, the same offset - can't be used twice in a row. - -The actual interval can be anywhere between a half of the defined interval and a -half above the interval. For example, for a user defined interval of 15 seconds -the actual interval can be anywhere between 7.5 and 22.5. The interval is -re-generated for every sampling run instead of being generated once and re-used -for the duration of the process' lifetime. - -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[grafana]: http://grafana.org/ +This document was moved to [administration/monitoring/performance/introduction](../administration/monitoring/performance/introduction.md). -- cgit v1.2.1 From 44d5be2e8a248657165f932d859a3be4f1ee9089 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:24:35 +0200 Subject: Add links to internal docs in Metrics section in the admin area --- app/views/admin/application_settings/_form.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 0d79ca7dc52..c4c68cd7891 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -221,7 +221,11 @@ %fieldset %legend Metrics %p - These settings require a restart to take effect. + Setup InfluxDB to measure a wide variety of statistics like the time spent + in running SQL queries. These settings require a + = link_to 'restart', help_page_path('administration/restart_gitlab') + to take effect. + = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') .form-group .col-sm-offset-2.col-sm-10 .checkbox -- cgit v1.2.1 From ffec230f537776959f40e66c3cb0809bb9c173b1 Mon Sep 17 00:00:00 2001 From: TMate Software Support Date: Tue, 27 Sep 2016 16:28:19 +0000 Subject: Update migrating_from_svn.md Documentation updated to cover Git/SVN mirror approach to migration from SVN to GitLab. --- doc/workflow/importing/migrating_from_svn.md | 92 +++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 4828bb5dce6..76839315c53 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -4,6 +4,94 @@ Subversion (SVN) is a central version control system (VCS) while Git is a distributed version control system. There are some major differences between the two, for more information consult your favorite search engine. +## Overview + +There are two approaches to SVN to Git migration: + +#### [Git/SVN Mirror](#mirror) + + Make GitLab repository mirror SVN project. + + Git and SVN project are kept in sync; use either one or another. + + Smoothens migration process and allows to manage migration risks. + +#### [Cut over migration](#cutover) + + Translate existing data and history from SVN to Git. + + Fire and forget approach, good for smaller teams. + +## Smooth migration with a Git/SVN mirror using SubGit + +#### Prerequisites + +Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions follow this +[instruction](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). + +Download SubGit tool from [https://subgit.com/download/](https://subgit.com/download/) + +Unpack downloaded SubGit zip archive to `/opt` directory, subgit will be available +at `/opt/subgit-VERSION/bin/subgit` + +#### Configuration + +In GitLab create new empty repository. In filesystem it will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path. + +Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran +on behalf of the same user that runs GitLab. + +``` +subgit configure --layout auto SVN_PROJECT_URL GIT_REPOS_PATH +``` + +Adjust authors and branches mappings, if necessary: + +``` +edit GIT_REPOS_PATH/subgit/authors.txt +edit GIT_REPOS_PATH/subgit/config +``` + +For more information regarding SubGit configuration options, refer to +[documentation](https://subgit.com/documentation.html) at SubGit web site. + +#### Initial translation + +Run `subgit` to perform initial translation of existing SVN revisions into +Git repository: + +``` +subgit install GIT_REPOS_PATH +``` + +After initial translation is completed, GitLab Git repository and SVN project +will be kept in sync by `subgit` - new Git commits will be translated to SVN +revisions and new SVN revisions will be translated to Git commits. Mirror works +transparently and does not require any special commands. + +Would you prefer to perform one-time cut over migration with `subgit` use +`import` command in place of `install`: + +``` +subgit import GIT_REPOS_PATH +``` + +#### Licensing + +Running SubGit in a mirror mode requires [registration](https://subgit.com/pricing.html). Registration is free for Open Source, +Academic and Startup projects. + +We're currently working on deeper GitLab/SubGit intergation. You may track our +progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). + +#### Support + +For any questions related to SVN to GitLab migration with SubGit contact us at [support@subgit.com](mailto:support@subgit.com). We support +all our users. + +## Cut over migration with svn2git + If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once and then have all developers start using the new GitLab repository immediately. @@ -74,6 +162,4 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on -how to migrate from SVN and other version control systems. - - +how to migrate from SVN and other version control systems. \ No newline at end of file -- cgit v1.2.1 From d90166172e91dbd63062c24d61e6566e57079a45 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 30 Sep 2016 11:38:56 +0200 Subject: Cache gems in CI on tags --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f3873e57c1..899e0b6b105 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -332,3 +332,16 @@ pages: - public only: - master + +# Insurance in case a gem needed by one of our releases gets yanked from +# rubygems.org in the future. +cache gems: + only: + - tags + variables: + SETUP_DB: "false" + script: + - bundle package --all --all-platforms + artifacts: + paths: + - vendor/cache -- cgit v1.2.1 From a34c0e5490c78402b72fab7196d43352ff719cbb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 10:32:24 +0100 Subject: Border instead of hr --- app/assets/stylesheets/pages/members.scss | 5 +++++ app/views/projects/project_members/index.html.haml | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 72f31cb1168..187151fe26c 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -1,3 +1,8 @@ +.project-members-title { + padding-bottom: 10px; + border-bottom: 1px solid $border-color; +} + .member { .list-item-name { float: none; diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index f566748e95a..24e5a8e4015 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,10 +1,9 @@ - page_title "Members" .project-members-page.prepend-top-default - %h4.clearfix + %h4.project-members-title.clearfix Members = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project" - %hr - if can?(current_user, :admin_project_member, @project) .project-members-new.append-bottom-default %p.clearfix -- cgit v1.2.1 From c2602aaff3f78ad12e1cc06136a7345699951454 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 4 Oct 2016 14:40:03 +0100 Subject: Updated Ruby --- app/controllers/projects/group_links_controller.rb | 2 +- app/controllers/projects/project_members_controller.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7b6f07465e0..2994d8c9666 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -1,6 +1,7 @@ class Projects::GroupLinksController < Projects::ApplicationController layout 'project_settings' before_action :authorize_admin_project! + before_action :authorize_admin_project_member!, only: [:update] def index @group_links = project.project_group_links.all @@ -21,7 +22,6 @@ class Projects::GroupLinksController < Projects::ApplicationController def update @group_link = @project.project_group_links.find(params[:id]) - return render_403 unless can?(current_user, :admin_project_member, @project) @group_link.update_attributes(group_link_params) end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index eb1bf445a7d..870dc8abbd4 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -19,8 +19,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController @groups = @project.project_group_links.where(group_id: group_ids) end - @project_members = @project_members.order('access_level DESC') - @project_members = @project_members.page(params[:page]) + @project_members = @project_members.order(access_level: :desc).page(params[:page]) @requesters = AccessRequestsFinder.new(@project).execute(current_user) @@ -40,6 +39,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController groups = Group.where(id: group_ids) groups.each do |group| + next unless can?(current_user, :read_group, group) + project.project_group_links.create( group: group, group_access: params[:access_level], -- cgit v1.2.1 From 66c7f5cb46d446a1c6c89ba888efd9e5f74f876d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 5 Oct 2016 14:31:33 +0200 Subject: fix empty import URL errors --- app/models/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 507228606df..4beec36242d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -494,7 +494,7 @@ class Project < ActiveRecord::Base end def import_url - if import_data && super + if import_data && super.present? import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials) import_url.full_url else -- cgit v1.2.1 From a0eaff14124b829ccc02df951bd7cb7d3abb7708 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 5 Oct 2016 14:37:03 +0100 Subject: Updated Ruby variable name Fixed Ruby code based on review feedback --- app/controllers/projects/project_members_controller.rb | 8 +++----- app/views/projects/project_members/_groups.html.haml | 4 ++-- app/views/projects/project_members/index.html.haml | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 870dc8abbd4..b2c8656d124 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -5,7 +5,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access] def index - @groups = @project.project_group_links + @group_links = @project.project_group_links @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) @@ -14,9 +14,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController users = @project.users.search(params[:search]).to_a @project_members = @project_members.where(user_id: users) - group_ids = @groups.pluck(:group_id) - group_ids = Group.where(id: group_ids).search(params[:search]).to_a - @groups = @project.project_group_links.where(group_id: group_ids) + @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) end @project_members = @project_members.order(access_level: :desc).page(params[:page]) @@ -40,7 +38,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController groups.each do |group| next unless can?(current_user, :read_group, group) - + project.project_group_links.create( group: group, group_access: params[:access_level], diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 340e4cd06b8..11f896006da 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -2,8 +2,8 @@ .panel-heading Groups with access to %strong #{@project.name} - %span.badge= groups.size + %span.badge= group_links.size %ul.content-list - - @groups.each do |group_link| + - group_links.each do |group_link| - group = group_link.group = render 'shared/members/group', group_link: group_link, group: group diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 24e5a8e4015..f1461444241 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,8 +21,8 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") - - if @groups.size > 0 - = render 'groups', groups: @groups + - if @group_links.size > 0 + = render 'groups', group_links: @group_links = render 'team', members: @project_members = paginate @project_members, theme: "gitlab" -- cgit v1.2.1 From 4d20083bd7e8839bfef64267535fd3c947a3b374 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 18:13:49 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 1 + app/controllers/projects/tags_controller.rb | 2 ++ spec/controllers/projects/tags_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e410d73d1f6..ea54f37a22e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Add ability to fork to a specific namespace using API. (ritave) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..40899abf6ee 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 if @tag.nil? + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end -- cgit v1.2.1 From 6b9671388d523a03b058e1cc467de77d805fc7a2 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 18:13:49 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 2 ++ app/controllers/projects/tags_controller.rb | 2 ++ spec/controllers/projects/tags_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 07b2b23003b..1b461f54729 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -90,6 +90,8 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.0 (unreleased) + - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..40899abf6ee 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 if @tag.nil? + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end -- cgit v1.2.1 From ff378e19e6dc385f1c85b7704263129a56778752 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 19:31:33 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 3 +-- app/controllers/projects/tags_controller.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1b461f54729..fd480bf87be 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) @@ -90,8 +91,6 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 -v 8.12.0 (unreleased) - - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 40899abf6ee..8fea20cefef 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,7 +20,7 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) - return render_404 if @tag.nil? + return render_404 unless @tag @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) -- cgit v1.2.1 From 217244074fbf78e2aabe1a5019ae0e9c0501e258 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 15:59:11 +0200 Subject: Move MWBS trigger from build to pipeline event --- app/models/ci/pipeline.rb | 16 +++++++++------- app/models/commit_status.rb | 4 ---- .../add_todo_when_build_fails_service.rb | 4 ++-- app/services/merge_requests/base_service.rb | 21 ++++++++++----------- .../merge_when_build_succeeds_service.rb | 9 +++++---- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..040ba20eb09 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -56,6 +56,10 @@ module Ci pipeline.finished_at = Time.now end + before_transition do |pipeline| + pipeline.update_duration + end + after_transition [:created, :pending] => :running do |pipeline| MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) @@ -66,8 +70,8 @@ module Ci update_all(latest_build_finished_at: pipeline.finished_at) end - before_transition do |pipeline| - pipeline.update_duration + after_transition [:created, :pending, :running] => :success do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) end after_transition do |pipeline, transition| @@ -292,11 +296,9 @@ module Ci # Merge requests for which the current pipeline is running against # the merge request's latest commit. def merge_requests - @merge_requests ||= - begin - project.merge_requests.where(source_branch: self.ref). - select { |merge_request| merge_request.pipeline.try(:id) == self.id } - end + @merge_requests ||= project.merge_requests + .where(source_branch: self.ref) + .select { |merge_request| merge_request.pipeline.try(:id) == self.id } end private diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9fa8d17e74e..5a240bcf2c6 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -98,10 +98,6 @@ class CommitStatus < ActiveRecord::Base true end - after_transition [:created, :pending, :running] => :success do |commit_status| - MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) - end - after_transition any => :failed do |commit_status| MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) end diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 566049525cb..d572a928a42 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -2,14 +2,14 @@ module MergeRequests class AddTodoWhenBuildFailsService < MergeRequests::BaseService # Adds a todo to the parent merge_request when a CI build fails def execute(commit_status) - each_merge_request(commit_status) do |merge_request| + commit_status_merge_requests(commit_status) do |merge_request| todo_service.merge_request_build_failed(merge_request) end end # Closes any pending build failed todos for the parent MRs when a build is retried def close(commit_status) - each_merge_request(commit_status) do |merge_request| + commit_status_merge_requests(commit_status) do |merge_request| todo_service.merge_request_build_retried(merge_request) end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index d0d155b7ee1..a726dd98697 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,11 +42,9 @@ module MergeRequests super(:merge_request) end - def merge_request_from(commit_status) - branches = commit_status.ref - + def merge_requests_for(branches, sha) # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(commit_status.sha) + branches ||= @project.repository.branch_names_contains(sha) return [] if branches.blank? @@ -56,14 +54,15 @@ module MergeRequests merge_requests.uniq.select(&:source_project) end - def each_merge_request(commit_status) - merge_request_from(commit_status).each do |merge_request| - pipeline = merge_request.pipeline - - next unless pipeline - next unless pipeline.sha == commit_status.sha + def pipeline_merge_requests(pipeline) + merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + yield merge_request + end + end - yield merge_request, pipeline + def commit_status_merge_requests(commit_status) + merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + yield merge_request end end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 4ad5fb08311..dc159de0058 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -18,12 +18,13 @@ module MergeRequests merge_request.save end - # Triggers the automatic merge of merge_request once the build succeeds - def trigger(commit_status) - each_merge_request(commit_status) do |merge_request, pipeline| + # Triggers the automatic merge of merge_request once the pipeline succeeds + def trigger(pipeline) + return unless pipeline.success? + + pipeline_merge_requests(pipeline) do |merge_request| next unless merge_request.merge_when_build_succeeds? next unless merge_request.mergeable? - next unless pipeline.success? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) end -- cgit v1.2.1 From c65ea7a5c30ee44c859ac711f1054dab734bdb89 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 10:45:23 +0100 Subject: Merge request tabs stick when scrolling page Closes #20548 --- CHANGELOG | 1 + app/assets/javascripts/merge_request_tabs.js | 38 +++++++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 9 ++++++ app/views/projects/merge_requests/_show.html.haml | 2 +- spec/features/merge_requests/sticky_tabs_spec.rb | 26 ++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 spec/features/merge_requests/sticky_tabs_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..5b68a670507 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -194,6 +194,7 @@ v 8.12.0 - Remove prefixes from transition CSS property (ClemMakesApps) - Add Sentry logging to API calls - Add BroadcastMessage API + - Merge request tabs are fixed when scrolling page - Use 'git update-ref' for safer web commits !6130 - Sort pipelines requested through the API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index bec11a198a1..f685af7d97e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -68,6 +68,7 @@ this._location = location; this.bindEvents(); this.activateTab(this.opts.action); + this.initAffix(); } MergeRequestTabs.prototype.bindEvents = function() { @@ -367,6 +368,43 @@ // Only when sidebar is collapsed }; + MergeRequestTabs.prototype.initAffix = function () { + // Screen space on small screens is usually very sparse + // So we dont affix the tabs on these + if (Breakpoints.get().getBreakpointSize() === 'xs') return; + + var $tabs = $('.js-tabs-affix'), + tabsWidth = $tabs.outerWidth(), + $diffTabs = $('#diff-notes-app'), + offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + + $tabs.off('affix.bs.affix affix-top.bs.affix') + .affix({ + offset: offsetTop + }).on('affix.bs.affix', function () { + $tabs.css({ + left: $tabs.offset().left, + width: tabsWidth + }); + $diffTabs.css({ + marginTop: $tabs.height() + }); + }).on('affix-top.bs.affix', function () { + $tabs.css({ + left: '', + width: '' + }); + $diffTabs.css({ + marginTop: '' + }); + }); + + // Fix bug when reloading the page already scrolling + if ($tabs.hasClass('affix')) { + $tabs.trigger('affix.bs.affix'); + } + }; + return MergeRequestTabs; })(); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..d01aaa4be51 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -421,3 +421,12 @@ margin-bottom: 20px; } } + +.merge-request-tabs { + background-color: #fff; + + &.affix { + top: 100px; + z-index: 9; + } +} diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9f34ca9ff4e..351c9d6ff91 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,7 +47,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom + %ul.merge-request-tabs.nav-links.no-top.no-bottom.js-tabs-affix %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb new file mode 100644 index 00000000000..6f8c3dc55f4 --- /dev/null +++ b/spec/features/merge_requests/sticky_tabs_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +feature 'Merge request tabs', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + + before do + project.team << [user, :master] + login_as user + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'affixes to top of page when scrolling' do + page.execute_script "window.scrollBy(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') + end + + it 'removes affix when scrolling to top' do + page.execute_script "window.scrollBy(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') + + page.execute_script "window.scrollBy(0,-10000)" + expect(page).to have_selector('.js-tabs-affix.affix-top') + end +end -- cgit v1.2.1 From 8962a3359c4282c755fff0fa08deeb64d74543a0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 11:21:45 +0100 Subject: Fixed merge request tab JS spec --- spec/javascripts/merge_request_tabs_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 395032a7416..96ee5235acf 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -1,5 +1,6 @@ /*= require merge_request_tabs */ +//= require breakpoints (function() { describe('MergeRequestTabs', function() { -- cgit v1.2.1 From 97dc95b18e5a5c29daab84e0d65522b3d3158dc1 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 14:32:43 +0100 Subject: Fixed tests --- app/assets/javascripts/merge_request_tabs.js | 15 +++++++++------ spec/features/merge_requests/sticky_tabs_spec.rb | 10 +++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index f685af7d97e..1a214cdd00e 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -369,18 +369,21 @@ }; MergeRequestTabs.prototype.initAffix = function () { + var $tabs = $('.js-tabs-affix'); + // Screen space on small screens is usually very sparse // So we dont affix the tabs on these - if (Breakpoints.get().getBreakpointSize() === 'xs') return; + if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; - var $tabs = $('.js-tabs-affix'), - tabsWidth = $tabs.outerWidth(), - $diffTabs = $('#diff-notes-app'), - offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); + var tabsWidth = $tabs.outerWidth(), + $diffTabs = $('#diff-notes-app'), + offsetTop = $tabs.offset().top - ($('.navbar-fixed-top').height() + $('.layout-nav').height()); $tabs.off('affix.bs.affix affix-top.bs.affix') .affix({ - offset: offsetTop + offset: { + top: offsetTop + } }).on('affix.bs.affix', function () { $tabs.css({ left: $tabs.offset().left, diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb index 6f8c3dc55f4..e78f3f134d5 100644 --- a/spec/features/merge_requests/sticky_tabs_spec.rb +++ b/spec/features/merge_requests/sticky_tabs_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Merge request tabs', js: true, feature: true do + include WaitForAjax + let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } @@ -9,18 +11,20 @@ feature 'Merge request tabs', js: true, feature: true do project.team << [user, :master] login_as user visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + + wait_for_ajax end it 'affixes to top of page when scrolling' do - page.execute_script "window.scrollBy(0,10000)" + page.execute_script "window.scroll(0,10000)" expect(page).to have_selector('.js-tabs-affix.affix') end it 'removes affix when scrolling to top' do - page.execute_script "window.scrollBy(0,10000)" + page.execute_script "window.scroll(0,10000)" expect(page).to have_selector('.js-tabs-affix.affix') - page.execute_script "window.scrollBy(0,-10000)" + page.execute_script "window.scroll(0,-10000)" expect(page).to have_selector('.js-tabs-affix.affix-top') end end -- cgit v1.2.1 From ef1b5988af68a2811c079f785351e30fa8afd347 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 3 Oct 2016 09:50:50 +0100 Subject: Tests update --- spec/features/merge_requests/sticky_tabs_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb index e78f3f134d5..3e8bd768324 100644 --- a/spec/features/merge_requests/sticky_tabs_spec.rb +++ b/spec/features/merge_requests/sticky_tabs_spec.rb @@ -17,14 +17,17 @@ feature 'Merge request tabs', js: true, feature: true do it 'affixes to top of page when scrolling' do page.execute_script "window.scroll(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') end it 'removes affix when scrolling to top' do page.execute_script "window.scroll(0,10000)" + expect(page).to have_selector('.js-tabs-affix.affix') page.execute_script "window.scroll(0,-10000)" + expect(page).to have_selector('.js-tabs-affix.affix-top') end end -- cgit v1.2.1 From 9824a69c8a4028b757a3d13d7ed4251589d20589 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 12:19:50 +0200 Subject: Fix tests and code for MWBS event move to pipeline --- app/services/merge_requests/base_service.rb | 6 +++ .../merge_when_build_succeeds_service_spec.rb | 48 +++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index a726dd98697..4f4c1c77ec3 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -56,12 +56,18 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + next unless pipeline == merge_request.pipeline + yield merge_request end end def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + pipeline = merge_request.pipeline + next unless pipeline + next unless pipeline.sha == commit_status.sha + yield merge_request end end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 520e906b21f..df8f8b61df5 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -58,42 +58,44 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end describe "#trigger" do - context 'build with ref' do - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } + let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } + let(:merge_request_head) do + project.commit(mr_merge_if_green_enabled.source_branch).id + end - it "merges all merge requests with merge when build succeeds enabled" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) + context 'when triggered by pipeline with valid ref and sha' do + let(:triggering_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: merge_request_head, status: 'success') + end + it "merges all merge requests with merge when build succeeds enabled" do expect(MergeWorker).to receive(:perform_async) - service.trigger(build) + service.trigger(triggering_pipeline) end end - context 'triggered by an old build' do - let(:old_build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } - - it "merges all merge requests with merge when build succeeds enabled" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) - allow(old_build).to receive(:sha).and_return('1234abcdef') + context 'when triggered by an old pipeline' do + let(:old_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: '1234abcdef', status: 'success') + end + it 'it does not merge merge request' do expect(MergeWorker).not_to receive(:perform_async) - service.trigger(old_build) + service.trigger(old_pipeline) end end - context 'commit status without ref' do - let(:commit_status) { create(:generic_commit_status, status: 'success') } - - before { mr_merge_if_green_enabled } - - it "doesn't merge a requests for status on other branch" do - allow(project.repository).to receive(:branch_names_contains).with(commit_status.sha).and_return([]) + context 'when triggered by pipeline from a different branch' do + let(:unrelated_pipeline) do + create(:ci_pipeline, project: project, ref: 'feature', + sha: merge_request_head, status: 'success') + end + it 'does not merge request' do expect(MergeWorker).not_to receive(:perform_async) - service.trigger(commit_status) + service.trigger(unrelated_pipeline) end it 'discovers branches and merges all merge requests when status is success' do -- cgit v1.2.1 From cd58410811564130e6975c62fe77d2c9fccce46f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 12:31:39 +0200 Subject: Remove support for branch-less builds in MWBS See !6675#note_16580143 --- app/services/merge_requests/base_service.rb | 20 ++++++++++---------- .../merge_when_build_succeeds_service_spec.rb | 13 +------------ 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 4f4c1c77ec3..58f69a41e14 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,20 +42,19 @@ module MergeRequests super(:merge_request) end - def merge_requests_for(branches, sha) - # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(sha) + def merge_requests_for(branch) + origin_merge_requests = @project.origin_merge_requests + .opened.where(source_branch: branch).to_a - return [] if branches.blank? + fork_merge_requests = @project.fork_merge_requests + .opened.where(source_branch: branch).to_a - merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a - merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a - - merge_requests.uniq.select(&:source_project) + (origin_merge_requests + fork_merge_requests) + .uniq.select(&:source_project) end def pipeline_merge_requests(pipeline) - merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + merge_requests_for(pipeline.ref).each do |merge_request| next unless pipeline == merge_request.pipeline yield merge_request @@ -63,8 +62,9 @@ module MergeRequests end def commit_status_merge_requests(commit_status) - merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + merge_requests_for(commit_status.ref).each do |merge_request| pipeline = merge_request.pipeline + next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index df8f8b61df5..03345ed9c15 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -97,20 +97,9 @@ describe MergeRequests::MergeWhenBuildSucceedsService do expect(MergeWorker).not_to receive(:perform_async) service.trigger(unrelated_pipeline) end - - it 'discovers branches and merges all merge requests when status is success' do - allow(project.repository).to receive(:branch_names_contains). - with(commit_status.sha).and_return([mr_merge_if_green_enabled.source_branch]) - allow(pipeline).to receive(:success?).and_return(true) - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) - - expect(MergeWorker).to receive(:perform_async) - service.trigger(commit_status) - end end - context 'properly handles multiple stages' do + context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') } let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') } -- cgit v1.2.1 From 492b4332a46fa0ae7d6547fe7417977f34c77b99 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2016 23:30:01 +0100 Subject: Added link to bulk assign issues to MR author. (Issue #18876) --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 21 ++++++++- app/helpers/merge_requests_helper.rb | 13 ++++++ .../merge_requests/assign_issues_service.rb | 35 +++++++++++++++ .../projects/merge_requests/widget/_open.html.haml | 1 + config/routes/project.rb | 1 + .../projects/merge_requests_controller_spec.rb | 31 +++++++++++++ spec/features/merge_requests/assign_issues_spec.rb | 51 ++++++++++++++++++++++ .../merge_requests/assign_issues_service_spec.rb | 49 +++++++++++++++++++++ 9 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 app/services/merge_requests/assign_issues_service.rb create mode 100644 spec/features/merge_requests/assign_issues_spec.rb create mode 100644 spec/services/merge_requests/assign_issues_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..a273e58f65c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,7 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8c8c56228ad..8bbf3ec67b3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts + :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -355,6 +355,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController render layout: false end + def assign_related_issues + result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute + + respond_to do |format| + format.html do + case result[:count] + when 0 + flash[:error] = "Failed to assign you issues related to the merge request" + when 1 + flash[:notice] = "1 issue has been assigned to you" + else + flash[:notice] = "#{result[:count]} issues have been assigned to you" + end + + redirect_to(merge_request_path(@merge_request)) + end + end + end + def ci_status pipeline = @merge_request.pipeline if pipeline diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 8abe7865fed..b0a76765d97 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -72,6 +72,19 @@ module MergeRequestsHelper ) end + def mr_assign_issues_link + issues = MergeRequests::AssignIssuesService.new(@project, + current_user, + merge_request: @merge_request, + closes_issues: mr_closes_issues + ).assignable_issues + path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + if issues.present? + pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue" + link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post + end + end + def source_branch_with_namespace(merge_request) branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb new file mode 100644 index 00000000000..2e84f844449 --- /dev/null +++ b/app/services/merge_requests/assign_issues_service.rb @@ -0,0 +1,35 @@ +module MergeRequests + class AssignIssuesService < BaseService + def assignable_issues + @assignable_issues ||= begin + if current_user == merge_request.author + closes_issues. + reject { |issue| issue.assignee_id? }. + select { |issue| can?(current_user, :admin_issue, issue) } + else + [] + end + end + end + + def execute + assignable_issues.each do |issue| + Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue) + end + + { + count: assignable_issues.count + } + end + + private + + def merge_request + params[:merge_request] + end + + def closes_issues + @closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user) + end + end +end diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 6f5ee5f16c5..842b6df310d 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -35,3 +35,4 @@ Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} = succeed '.' do != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author + = mr_assign_issues_link diff --git a/config/routes/project.rb b/config/routes/project.rb index 224ec7e8324..6c0c2a1184f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -277,6 +277,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :remove_wip get :diff_for_path post :resolve_conflicts + post :assign_related_issues end collection do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 742edd8ba3d..18041bce482 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -708,4 +708,35 @@ describe Projects::MergeRequestsController do end end end + + describe 'POST assign_related_issues' do + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + + def post_assign_issues + merge_request.update!(description: "Closes #{issue1.to_reference} and #{issue2.to_reference}", + author: user, + source_branch: 'feature', + target_branch: 'master') + + post :assign_related_issues, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid + end + + it 'shows a flash message on success' do + post_assign_issues + + expect(flash[:notice]).to eq '2 issues have been assigned to you' + end + + it 'correctly pluralizes flash message on success' do + issue2.update!(assignee: user) + + post_assign_issues + + expect(flash[:notice]).to eq '1 issue has been assigned to you' + end + end end diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb new file mode 100644 index 00000000000..43cc6f2a2a7 --- /dev/null +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +feature 'Merge request issue assignment', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") } + let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) } + + before do + project.team << [user, :developer] + end + + def visit_merge_request(current_user = nil) + login_as(current_user || user) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'logged in as author' do + scenario 'updates related issues' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content "2 issues have been assigned to you" + end + + it 'returns user to the merge request' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content merge_request.description + end + + it "doesn't display if related issues are already assigned" do + [issue1, issue2].each { |issue| issue.update!(assignee: user) } + + visit_merge_request + + expect(page).not_to have_content "Assign yourself" + end + end + + context 'not MR author' do + it "doesn't not show assignment link" do + visit_merge_request(create(:user)) + + expect(page).not_to have_content "Assign yourself" + end + end +end diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb new file mode 100644 index 00000000000..7aeb95a15ea --- /dev/null +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe MergeRequests::AssignIssuesService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue.to_reference}") } + let(:service) { described_class.new(project, user, merge_request: merge_request) } + + before do + project.team << [user, :developer] + end + + it 'finds unassigned issues fixed in merge request' do + expect(service.assignable_issues.map(&:id)).to include(issue.id) + end + + it 'ignores issues already assigned to any user' do + issue.update!(assignee: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'ignores issues the user cannot update assignee on' do + project.team.truncate + + expect(service.assignable_issues).to be_empty + end + + it 'ignores all issues unless current_user is merge_request.author' do + merge_request.update!(author: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'accepts precomputed data for closes_issues' do + issue2 = create(:issue, project: project) + service2 = described_class.new(project, + user, + merge_request: merge_request, + closes_issues: [issue, issue2]) + + expect(service2.assignable_issues.count).to eq 2 + end + + it 'assigns these to the merge request owner' do + expect { service.execute }.to change { issue.reload.assignee }.to(user) + end +end -- cgit v1.2.1 From a43baa056e69827c342e705e2d5ea8cfc67bfd9c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 14:52:00 +0200 Subject: Rename pipeline workers to match current convention --- app/models/commit_status.rb | 4 ++-- app/workers/pipeline_process_worker.rb | 10 ++++++++++ app/workers/pipeline_update_worker.rb | 10 ++++++++++ app/workers/process_pipeline_worker.rb | 10 ---------- app/workers/update_pipeline_worker.rb | 10 ---------- spec/workers/pipeline_proccess_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/pipeline_update_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/process_pipeline_worker_spec.rb | 22 ---------------------- spec/workers/update_pipeline_worker_spec.rb | 22 ---------------------- 9 files changed, 66 insertions(+), 66 deletions(-) create mode 100644 app/workers/pipeline_process_worker.rb create mode 100644 app/workers/pipeline_update_worker.rb delete mode 100644 app/workers/process_pipeline_worker.rb delete mode 100644 app/workers/update_pipeline_worker.rb create mode 100644 spec/workers/pipeline_proccess_worker_spec.rb create mode 100644 spec/workers/pipeline_update_worker_spec.rb delete mode 100644 spec/workers/process_pipeline_worker_spec.rb delete mode 100644 spec/workers/update_pipeline_worker_spec.rb diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 5a240bcf2c6..3b575e5627d 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -89,10 +89,10 @@ class CommitStatus < ActiveRecord::Base break if transition.loopback? if commit_status.complete? - ProcessPipelineWorker.perform_async(pipeline.id) + PipelineProcessWorker.perform_async(pipeline.id) end - UpdatePipelineWorker.perform_async(pipeline.id) + PipelineUpdateWorker.perform_async(pipeline.id) end true diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb new file mode 100644 index 00000000000..f44227d7086 --- /dev/null +++ b/app/workers/pipeline_process_worker.rb @@ -0,0 +1,10 @@ +class PipelineProcessWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:process!) + end +end diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb new file mode 100644 index 00000000000..44a7f24e401 --- /dev/null +++ b/app/workers/pipeline_update_worker.rb @@ -0,0 +1,10 @@ +class PipelineUpdateWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:update_status) + end +end diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb deleted file mode 100644 index 26ea5f1c24d..00000000000 --- a/app/workers/process_pipeline_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class ProcessPipelineWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:process!) - end -end diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/update_pipeline_worker.rb deleted file mode 100644 index 6ef5678073e..00000000000 --- a/app/workers/update_pipeline_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class UpdatePipelineWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:update_status) - end -end diff --git a/spec/workers/pipeline_proccess_worker_spec.rb b/spec/workers/pipeline_proccess_worker_spec.rb new file mode 100644 index 00000000000..86e9d7f6684 --- /dev/null +++ b/spec/workers/pipeline_proccess_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe PipelineProcessWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'processes pipeline' do + expect_any_instance_of(Ci::Pipeline).to receive(:process!) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb new file mode 100644 index 00000000000..0b456cfd0da --- /dev/null +++ b/spec/workers/pipeline_update_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe PipelineUpdateWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'updates pipeline status' do + expect_any_instance_of(Ci::Pipeline).to receive(:update_status) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/process_pipeline_worker_spec.rb b/spec/workers/process_pipeline_worker_spec.rb deleted file mode 100644 index 7b5f98d5763..00000000000 --- a/spec/workers/process_pipeline_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe ProcessPipelineWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - - it 'processes pipeline' do - expect_any_instance_of(Ci::Pipeline).to receive(:process!) - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end diff --git a/spec/workers/update_pipeline_worker_spec.rb b/spec/workers/update_pipeline_worker_spec.rb deleted file mode 100644 index fadc42b22f0..00000000000 --- a/spec/workers/update_pipeline_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe UpdatePipelineWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - - it 'updates pipeline status' do - expect_any_instance_of(Ci::Pipeline).to receive(:update_status) - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end -- cgit v1.2.1 From 6865c46c6649118e09e60dd29dfa060470010aa9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 15:41:00 +0100 Subject: Changed how collections are rendered Used variables in haml for replicated checks Fixed broken conflict --- .../projects/project_members_controller.rb | 15 - app/views/projects/group_links/update.js.haml | 2 +- .../projects/project_members/_groups.html.haml | 4 +- app/views/projects/project_members/_team.html.haml | 3 +- app/views/projects/project_members/index.html.haml | 2 +- app/views/shared/members/_group.html.haml | 9 +- app/views/shared/members/_member.html.haml | 10 +- config/routes.rb | 794 --------------------- 8 files changed, 15 insertions(+), 824 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 67b41c4573a..37a86ed0523 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -98,21 +98,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController notice: notice) end - def options - users = User.all - users = users.search(params[:search]) if params[:search].present? - users = users.page(1) - - groups = Group.all - groups = groups.search(params[:search]) if params[:search].present? - groups = groups.page(1) - - render json: { - Groups: groups.as_json(only: [:id, :name], methods: [:avatar_url]), - Users: users.as_json(only: [:id, :name, :username], methods: [:avatar_url]), - } - end - protected def member_params diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index 231d5a79723..af9a5b19060 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,3 @@ :plain - var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link, group: @group_link.group))}'); + var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}'); $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index 11f896006da..d7f5fa96527 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -4,6 +4,4 @@ %strong #{@project.name} %span.badge= group_links.size %ul.content-list - - group_links.each do |group_link| - - group = group_link.group - = render 'shared/members/group', group_link: group_link, group: group + = render partial: 'shared/members/group', collection: group_links, as: :group_link diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index ff54035cfe1..c1e894d8f40 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -4,5 +4,4 @@ %strong #{@project.name} %span.badge= @project_members.total_count %ul.content-list - - members.each do |member| - = render 'shared/members/member', member: member + = render partial: 'shared/members/member', collection: members, as: :member diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index f1461444241..bdeb704b6da 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -21,7 +21,7 @@ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } = icon("search") - - if @group_links.size > 0 + - if @group_links.any? = render 'groups', group_links: @group_links = render 'team', members: @project_members diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 171a388b233..1c0346bbc78 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,5 +1,6 @@ -- group = local_assigns[:group] - group_link = local_assigns[:group_link] +- group = group_link.group +- can_admin_member = can?(current_user, :admin_project_member, @project) %li.member.group_member{ id: "group_member_#{group_link.id}" } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' @@ -13,11 +14,11 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) + = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member .prepend-left-5.clearable-input.member-form-control - = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can?(current_user, :admin_project_member, @project) + = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - - if can?(current_user, :admin_project_member, @project) + - if can_admin_member = link_to namespace_project_group_link_path(@project.namespace, @project, group_link), remote: true, method: :delete, diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 80e52bf5637..432047a1c4e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -2,6 +2,7 @@ - show_controls = local_assigns.fetch(:show_controls, true) - user = local_assigns.fetch(:user, member.user) - source = member.source +- can_admin_member = can?(current_user, action_member_permission(:update, member), member) %li.member{ class: dom_class(member), id: dom_id(member) } %span.list-item-name @@ -47,18 +48,19 @@ - if show_controls - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member .prepend-left-5.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can?(current_user, action_member_permission(:update, member), member) + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input - else %span.member-access-text= member.human_access - - if !user && can?(current_user, action_member_permission(:admin, member), member.source) + + - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source) = link_to 'Resend invite', polymorphic_path([:resend_invite, member]), method: :post, class: 'btn btn-default prepend-left-10' - - if member.request? && can?(current_user, action_member_permission(:update, member), member) + - elsif member.request? && can_admin_member = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]), method: :post, class: 'btn btn-success prepend-left-10', diff --git a/config/routes.rb b/config/routes.rb index f93c406e692..bf7c5b76128 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,799 +74,6 @@ Rails.application.routes.draw do # Notification settings resources :notification_settings, only: [:create, :update] -<<<<<<< HEAD - # - # Import - # - namespace :import do - resource :github, only: [:create, :new], controller: :github do - post :personal_access_token - get :status - get :callback - get :jobs - end - - resource :gitlab, only: [:create], controller: :gitlab do - get :status - get :callback - get :jobs - end - - resource :bitbucket, only: [:create], controller: :bitbucket do - get :status - get :callback - get :jobs - end - - resource :google_code, only: [:create, :new], controller: :google_code do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :fogbugz, only: [:create, :new], controller: :fogbugz do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :gitlab_project, only: [:create, :new] do - post :create - end - end - - # - # Uploads - # - - scope path: :uploads do - # Note attachments and User/Group/Project avatars - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } - - # Appearance - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } - - # Project markdown uploads - get ":namespace_id/:project_id/:secret/:filename", - to: "projects/uploads#show", - constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } - end - - # Redirect old note attachments path to new uploads path. - get "files/note/:id/:filename", - to: redirect("uploads/note/attachment/%{id}/%{filename}"), - constraints: { filename: /[^\/]+/ } - - # - # Explore area - # - namespace :explore do - resources :projects, only: [:index] do - collection do - get :trending - get :starred - end - end - - resources :groups, only: [:index] - resources :snippets, only: [:index] - root to: 'projects#trending' - end - - # Compatibility with old routing - get 'public' => 'explore/projects#index' - get 'public/projects' => 'explore/projects#index' - - # - # Admin Area - # - namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do - resources :keys, only: [:show, :destroy] - resources :identities, except: [:show] - - member do - get :projects - get :keys - get :groups - put :block - put :unblock - put :unlock - put :confirm - post :impersonate - patch :disable_two_factor - delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' - end - end - - resource :impersonation, only: :destroy - - resources :abuse_reports, only: [:index, :destroy] - resources :spam_logs, only: [:index, :destroy] do - member do - post :mark_as_ham - end - end - - resources :applications - - resources :groups, constraints: { id: /[^\/]+/ } do - member do - put :members_update - end - end - - resources :deploy_keys, only: [:index, :new, :create, :destroy] - - resources :hooks, only: [:index, :create, :destroy] do - get :test - end - - resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do - post :preview, on: :collection - end - - resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] - resource :background_jobs, controller: 'background_jobs', only: [:show] - resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects - - resources(:projects, - path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' - - member do - put :transfer - post :repository_check - end - - resources :runner_projects, only: [:create, :destroy] - end - end - - resource :appearances, only: [:show, :create, :update], path: 'appearance' do - member do - get :preview - delete :logo - delete :header_logos - end - end - - resource :application_settings, only: [:show, :update] do - resources :services, only: [:index, :edit, :update] - put :reset_runners_token - put :reset_health_check_token - put :clear_repository_check_states - end - - resources :labels - - resources :runners, only: [:index, :show, :update, :destroy] do - member do - get :resume - get :pause - end - end - - resources :builds, only: :index do - collection do - post :cancel_all - end - end - - root to: 'dashboard#index' - end - - # - # Profile Area - # - resource :profile, only: [:show, :update] do - member do - get :audit_log - get :applications, to: 'oauth/applications#index' - - put :reset_private_token - put :update_username - end - - scope module: :profiles do - resource :account, only: [:show] do - member do - delete :unlink - end - end - resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create, :edit, :update] do - member do - put :reset - end - end - resource :preferences, only: [:show, :update] - resources :keys, only: [:index, :show, :new, :create, :destroy] - resources :emails, only: [:index, :create, :destroy] - resource :avatar, only: [:destroy] - - resources :personal_access_tokens, only: [:index, :create] do - member do - put :revoke - end - end - - resource :two_factor_auth, only: [:show, :create, :destroy] do - member do - post :create_u2f - post :codes - patch :skip - end - end - - resources :u2f_registrations, only: [:destroy] - end - end - - scope(path: 'u/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error - get '/users/almost_there' => 'confirmations#almost_there' - end - - root to: "root#index" - - # - # Project Area - # - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template - - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, - as: :commits - ) - end - - resource :avatar, only: [:show, :destroy] - resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do - member do - get :branches - get :builds - get :pipelines - post :cancel_builds - post :retry_builds - post :revert - post :cherry_pick - get :diff_for_path - end - end - - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :commits - get :ci - get :languages - end - end - end - - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get 'raw' - end - end - - WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID - - scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' - - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do - member do - put :enable - put :disable - end - end - - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get :commits - get :diffs - get :conflicts - get :builds - get :pipelines - get :merge_check - post :merge - post :cancel_merge_when_build_succeeds - get :ci_status - post :toggle_subscription - post :remove_wip - get :diff_for_path - post :resolve_conflicts - end - - collection do - get :branch_from - get :branch_to - get :update_branches - get :diff_for_path - post :bulk_update - end - - resources :discussions, only: [], constraints: { id: /\h{40}/ } do - member do - post :resolve - delete :resolve, action: :unresolve - end - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] - resources :triggers, only: [:index, :create, :destroy] - - resources :pipelines, only: [:index, :new, :create, :show] do - collection do - resource :pipelines_settings, path: 'settings', only: [:show, :update] - end - - member do - post :cancel - post :retry - end - end - - resources :environments - - resource :cycle_analytics, only: [:show] - - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do - collection do - post :cancel_all - - resources :artifacts, only: [] do - collection do - get :latest_succeeded, - path: '*ref_name_and_path', - format: false - end - end - end - - member do - get :status - post :cancel - post :retry - post :play - post :erase - get :trace - get :raw - end - - resource :artifacts, only: [] do - get :download - get :browse, path: 'browse(/*path)', format: false - get :file, path: 'file/*path', format: false - post :keep - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do - member do - get :test - end - end - - resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } - - resources :milestones, constraints: { id: /\d+/ } do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :toggle_subscription - delete :remove_priority - end - end - - resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do - member do - post :toggle_subscription - post :mark_as_spam - get :referenced_merge_requests - get :related_branches - get :can_create_branch - end - collection do - post :bulk_update - end - end - - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - - member do - post :resend_invite - end - end - - resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ } - - resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do - member do - delete :delete_attachment - post :resolve - delete :resolve, action: :unresolve - end - end - - resource :board, only: [:show] do - scope module: :boards do - resources :issues, only: [:update] - - resources :lists, only: [:index, :create, :update, :destroy] do - collection do - post :generate - end - - resources :issues, only: [:index] - end - end - end - - resources :todos, only: [:create] - - resources :uploads, only: [:create] do - collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } - end - end - - resources :runners, only: [:index, :edit, :update, :destroy, :show] do - member do - get :resume - get :pause - end - - collection do - post :toggle_shared_runners - end - end - - resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [:index] do - collection do - scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do - constraints format: /svg/ do - get :build - get :coverage - end - end - end - end - end - end - end -======= draw :import draw :uploads draw :explore @@ -876,7 +83,6 @@ Rails.application.routes.draw do draw :group draw :user draw :project ->>>>>>> master # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From b2e5240af3d14bd67900ef194226274639537888 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 6 Oct 2016 15:42:48 +0100 Subject: Changed jQuery to be in single line Removed un-required CSS --- app/assets/javascripts/members.js.es6 | 5 +---- app/assets/stylesheets/pages/members.scss | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 1b4b3f38838..5dc6990bdb6 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -31,10 +31,7 @@ } formSubmit() { - $(this).closest('form') - .trigger("submit.rails") - .end() - .disable(); + $(this).closest('form').trigger("submit.rails").end().disable(); } formSuccess() { diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 187151fe26c..ff4fd751f26 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -5,8 +5,6 @@ .member { .list-item-name { - float: none; - @media (min-width: $screen-sm-min) { float: left; width: 50%; -- cgit v1.2.1 From 90f49cc6969ec522457ddce8e864169f5c91e6aa Mon Sep 17 00:00:00 2001 From: Tjaart van der Walt Date: Thu, 6 Oct 2016 11:53:53 -0500 Subject: Update mail_room gem --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 30c8e801ea1..67fe6aa4e95 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -68,6 +68,7 @@ v 8.13.0 (unreleased) - Grouped pipeline dropdown is a scrollable container v 8.12.5 (unreleased) + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) diff --git a/Gemfile b/Gemfile index 3e8ce8b2fc5..0cc98ad9210 100644 --- a/Gemfile +++ b/Gemfile @@ -323,7 +323,7 @@ gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.3.0' -gem 'mail_room', '~> 0.8' +gem 'mail_room', '~> 0.8.1' gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 96b49faf727..817c1725879 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) @@ -886,7 +886,7 @@ DEPENDENCIES license_finder (~> 2.1.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.8) + mail_room (~> 0.8.1) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) -- cgit v1.2.1 From a98e4081d1b122d001438d17036f395fc82b9d5c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 09:32:29 +0200 Subject: Process MWBS in successful pipeline asynchronously --- app/models/ci/pipeline.rb | 2 +- app/workers/pipeline_success_worker.rb | 12 ++++++++++++ spec/workers/pipeline_success_worker_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 app/workers/pipeline_success_worker.rb create mode 100644 spec/workers/pipeline_success_worker_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 040ba20eb09..d3a6e0a11f0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -71,7 +71,7 @@ module Ci end after_transition [:created, :pending, :running] => :success do |pipeline| - MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) + PipelineSuccessWorker.perform_async(pipeline.id) end after_transition do |pipeline, transition| diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb new file mode 100644 index 00000000000..5dd443fea59 --- /dev/null +++ b/app/workers/pipeline_success_worker.rb @@ -0,0 +1,12 @@ +class PipelineSuccessWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService + .new(pipeline.project, nil) + .trigger(pipeline) + end + end +end diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb new file mode 100644 index 00000000000..5e31cc2c8e7 --- /dev/null +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe PipelineSuccessWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline, status: 'success') } + + it 'performs "merge when pipeline succeeds"' do + expect_any_instance_of( + MergeRequests::MergeWhenBuildSucceedsService + ).to receive(:trigger) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 2f66969e43d25192017ebbe4ff56df213a2dae3f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 09:49:43 +0200 Subject: Improve merge when builds succeeds specs readability --- .../merge_when_build_succeeds_spec.rb | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 60bc07bd1a0..8b6544269a7 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -2,18 +2,26 @@ require 'spec_helper' feature 'Merge When Build Succeeds', feature: true, js: true do let(:user) { create(:user) } + let(:project) { create(:project, :public) } - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end - before do - project.team << [user, :master] - project.enable_ci + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) end - context "Active build for Merge Request" do - let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, pipeline: pipeline) } + before { project.team << [user, :master] } + + context 'when there is active build for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end before do login_as user @@ -41,21 +49,25 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end - context 'When it is enabled' do + context 'when merge when build succeeds is enabled' do let(:merge_request) do - create(:merge_request_with_diffs, :simple, source_project: project, author: user, - merge_user: user, title: "MepMep", merge_when_build_succeeds: true) + create(:merge_request_with_diffs, :simple, source_project: project, + author: user, + merge_user: user, + title: 'MepMep', + merge_when_build_succeeds: true) end - let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, pipeline: pipeline) } + background do + create(:ci_build, pipeline: pipeline) + end before do login_as user visit_merge_request(merge_request) end - it 'cancels the automatic merge' do + it 'allows to cancel the automatic merge' do click_link "Cancel Automatic Merge" expect(page).to have_button "Merge When Build Succeeds" @@ -72,8 +84,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end - context 'Build is not active' do - it "does not allow for enabling" do + context 'when build is not active' do + it "does not allow to enable merge when build succeeds" do visit_merge_request(merge_request) expect(page).not_to have_link "Merge When Build Succeeds" end -- cgit v1.2.1 From e351ed2d11b611bf06eba8d64d2549fbf60fb605 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:10:46 +0200 Subject: Add test that checks actual merge for MWBS feature --- .../merge_requests/merge_when_build_succeeds_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 8b6544269a7..bc2b0ff3e2c 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -58,7 +58,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do merge_when_build_succeeds: true) end - background do + let!(:build) do create(:ci_build, pipeline: pipeline) end @@ -72,7 +72,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).to have_button "Merge When Build Succeeds" - visit_merge_request(merge_request) # Needed to refresh the page + visit_merge_request(merge_request) # refresh the page expect(page).to have_content "Canceled the automatic merge" end @@ -82,6 +82,17 @@ feature 'Merge When Build Succeeds', feature: true, js: true do click_link "Remove Source Branch When Merged" expect(page).to have_content "The source branch will be removed" end + + context 'when build succeeds' do + background { build.success } + + it 'merges merge request' do + visit_merge_request(merge_request) # refresh the page + + expect(page).to have_content 'The changes were merged' + expect(merge_request.reload).to be_merged + end + end end context 'when build is not active' do -- cgit v1.2.1 From bc57fc7cb29b40ae8111e391dfcbd6c55eb6c963 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:23:18 +0200 Subject: Update documentation according to changes in MWBS --- .../merge_requests/merge_when_build_succeeds.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index 011f9cbc381..c138061fd40 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -1,16 +1,16 @@ # Merge When Build Succeeds When reviewing a merge request that looks ready to merge but still has one or -more CI builds running, you can set it to be merged automatically when all -builds succeed. This way, you don't have to wait for the builds to finish and -remember to merge the request manually. +more CI builds running, you can set it to be merged automatically when the +builds pipeline succeed. This way, you don't have to wait for the builds to +finish and remember to merge the request manually. ![Enable](img/merge_when_build_succeeds_enable.png) When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait -for the build to succeed and want to merge immediately, this option is available -in the dropdown menu on the right of the main button. +for the pipeline to succeed and want to merge immediately, this option is +available in the dropdown menu on the right of the main button. Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged @@ -18,9 +18,9 @@ after all. ![Status](img/merge_when_build_succeeds_status.png) -When the build succeeds, the merge request will automatically be merged. When -the build fails, the author gets a chance to retry any failed builds, or to -push new commits to fix the failure. +When the pipeline succeeds, the merge request will automatically be merged. +When the pipeline fails, the author gets a chance to retry any failed builds, +or to push new commits to fix the failure. When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with @@ -40,7 +40,7 @@ hit **Save** for the changes to take effect. ![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) -From now on, every time the build fails you will not be able to merge the merge -request from the UI, until you make the build pass. +From now on, every time the pipelinefails you will not be able to merge the +merge request from the UI, until you make all relevant builds pass. ![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png) -- cgit v1.2.1 From 6f7afaa8a0b5594b88cb5a4a2fe2a822ee1334a9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:30:40 +0200 Subject: Add Changelog entry for MWBS trigger improvements --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..909b2002e66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) -- cgit v1.2.1 From 680efd4f8d6fb3a1036edc135f5761a512f8eea7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 24 Aug 2016 23:44:58 +0100 Subject: Added subnav to labels edit+new and milestones edit+new+show Added subnav to blame show, blob edit, builds show, commit builds, commit show, environments edit and pipelines show Added subnav to new enviro view Added sidebar top position calculation logic Added sidebar translation to follow the subnav up when the body is scrolled until a certain limit --- app/assets/javascripts/build.js | 36 ++++++++-- app/assets/stylesheets/pages/builds.scss | 6 ++ app/views/projects/blame/show.html.haml | 87 +++++++++++------------ app/views/projects/blob/edit.html.haml | 43 ++++++------ app/views/projects/builds/show.html.haml | 95 +++++++++++++------------- app/views/projects/commit/builds.html.haml | 11 +-- app/views/projects/commit/show.html.haml | 25 ++++--- app/views/projects/environments/edit.html.haml | 11 +-- app/views/projects/environments/new.html.haml | 11 +-- app/views/projects/labels/edit.html.haml | 11 +-- app/views/projects/labels/new.html.haml | 11 +-- app/views/projects/milestones/edit.html.haml | 12 ++-- app/views/projects/milestones/new.html.haml | 11 +-- app/views/projects/milestones/show.html.haml | 83 +++++++++++----------- app/views/projects/pipelines/_head.html.haml | 2 +- app/views/projects/pipelines/show.html.haml | 13 ++-- 16 files changed, 268 insertions(+), 200 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index f336bfc36d6..97462a5959c 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -15,18 +15,17 @@ this.hideSidebar = bind(this.hideSidebar, this); this.toggleSidebar = bind(this.toggleSidebar, this); this.updateDropdown = bind(this.updateDropdown, this); + this.$document = $(document); clearInterval(Build.interval); // Init breakpoint checker this.bp = Breakpoints.get(); - $('.js-build-sidebar').niceScroll(); + this.initSidebar(); this.populateJobs(this.build_stage); this.updateStageDropdownText(this.build_stage); - this.hideSidebar(); - $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); - $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); $('#js-build-scroll > a').off('click').on('click', this.stepTrace); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { @@ -62,6 +61,21 @@ } } + Build.prototype.initSidebar = function() { + this.$sidebar = $('.js-build-sidebar'); + this.sidebarTranslationLimits = { + min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + } + this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight(); + this.$sidebar.css({ + top: this.sidebarTranslationLimits.max + }); + this.$sidebar.niceScroll(); + this.hideSidebar(); + this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); + this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this)); + }; + Build.prototype.getInitialBuildTrace = function() { var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'] @@ -129,15 +143,23 @@ Build.prototype.toggleSidebar = function() { if (this.shouldHideSidebar()) { - return $('.js-build-sidebar').toggleClass('right-sidebar-expanded right-sidebar-collapsed'); + return this.$sidebar.toggleClass('right-sidebar-expanded right-sidebar-collapsed'); } }; + Build.prototype.translateSidebar = function(e) { + var newPosition = this.sidebarTranslationLimits.max - document.body.scrollTop; + if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min; + this.$sidebar.css({ + top: newPosition + }); + }; + Build.prototype.hideSidebar = function() { if (this.shouldHideSidebar()) { - return $('.js-build-sidebar').removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); + return this.$sidebar.removeClass('right-sidebar-expanded').addClass('right-sidebar-collapsed'); } else { - return $('.js-build-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); + return this.$sidebar.removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded'); } }; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 194a39a8377..fcaba954615 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -233,3 +233,9 @@ right: 0; margin-top: -17px; } + +@media (min-width: $screen-md-min) { + .sub-nav.build { + width: calc(100% + #{$gutter_width}); + } +} diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 5a98e258b22..dfb96305f48 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,45 +1,48 @@ +- @no_container = true - page_title "Blame", @blob.path, @ref += render "projects/commits/head" -%h3.page-title Blame view +%div{ class: container_class } + %h3.page-title Blame view -#blob-content-holder.tree-holder - .file-holder - .file-title - = blob_icon @blob.mode, @blob.name - %strong - = @path - %small= number_to_human_size @blob.size - .file-actions - = render "projects/blob/actions" - .table-responsive.file-content.blame.code.js-syntax-highlight - %table - - current_line = 1 - - @blame_groups.each do |blame_group| - %tr - %td.blame-commit - .commit - - commit = blame_group[:commit] - = author_avatar(commit, size: 36) - .commit-row-title - %strong - = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" - .pull-right - = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace" -   - .light - = commit_author_link(commit, avatar: false) - authored - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} - %td.line-numbers - - line_count = blame_group[:lines].count - - (current_line...(current_line + line_count)).each do |i| - %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} - = icon("link") - = i - \ - - current_line += line_count - %td.lines - %pre.code.highlight - %code - - blame_group[:lines].each do |line| - #{line} + #blob-content-holder.tree-holder + .file-holder + .file-title + = blob_icon @blob.mode, @blob.name + %strong + = @path + %small= number_to_human_size @blob.size + .file-actions + = render "projects/blob/actions" + .table-responsive.file-content.blame.code.js-syntax-highlight + %table + - current_line = 1 + - @blame_groups.each do |blame_group| + %tr + %td.blame-commit + .commit + - commit = blame_group[:commit] + = author_avatar(commit, size: 36) + .commit-row-title + %strong + = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" + .pull-right + = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "monospace" +   + .light + = commit_author_link(commit, avatar: false) + authored + #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + %td.line-numbers + - line_count = blame_group[:lines].count + - (current_line...(current_line + line_count)).each do |i| + %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + = icon("link") + = i + \ + - current_line += line_count + %td.lines + %pre.code.highlight + %code + - blame_group[:lines].each do |line| + #{line} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 680e95ac6b5..2a0352a71b7 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,28 +1,31 @@ +- @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') = page_specific_javascript_tag('blob_edit/blob_edit_bundle.js') += render "projects/commits/head" -- if @conflict - .alert.alert-danger - Someone edited the file the same time you did. Please check out - = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" - and make sure your changes will not unintentionally remove theirs. +%div{ class: container_class } + - if @conflict + .alert.alert-danger + Someone edited the file the same time you did. Please check out + = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" + and make sure your changes will not unintentionally remove theirs. -.file-editor - %ul.nav-links.no-bottom.js-edit-mode - %li.active - = link_to '#editor' do - Edit File + .file-editor + %ul.nav-links.no-bottom.js-edit-mode + %li.active + = link_to '#editor' do + Edit File - %li - = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do - = editing_preview_title(@blob.name) + %li + = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do + = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do - = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" - = hidden_field_tag 'last_commit_sha', @last_commit_sha - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" + = hidden_field_tag 'last_commit_sha', @last_commit_sha + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index e4d41288aa6..b5e8b0bf6eb 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,56 +1,59 @@ +- @no_container = true - page_title "#{@build.name} (##{@build.id})", "Builds" - trace_with_state = @build.trace_with_state - header_title project_title(@project, "Builds", project_builds_path(@project)) += render "projects/pipelines/head", build_subnav: true -.build-page - = render "header" +%div{ class: container_class } + .build-page + = render "header" - - if @build.stuck? - - unless @build.any_runners_online? - .bs-callout.bs-callout-warning - %p - - if no_runners_for_project?(@build.project) - This build is stuck, because the project doesn't have any runners online assigned to it. - - elsif @build.tags.any? - This build is stuck, because you don't have any active runners online with any of these tags assigned to them: - - @build.tags.each do |tag| - %span.label.label-primary - = tag - - else - This build is stuck, because you don't have any active runners that can run this build. + - if @build.stuck? + - unless @build.any_runners_online? + .bs-callout.bs-callout-warning + %p + - if no_runners_for_project?(@build.project) + This build is stuck, because the project doesn't have any runners online assigned to it. + - elsif @build.tags.any? + This build is stuck, because you don't have any active runners online with any of these tags assigned to them: + - @build.tags.each do |tag| + %span.label.label-primary + = tag + - else + This build is stuck, because you don't have any active runners that can run this build. - %br - Go to - = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do - Runners page + %br + Go to + = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do + Runners page - .prepend-top-default - - if @build.active? - .autoscroll-container - %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll - - if @build.erased? - .erased.alert.alert-warning - - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by - Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} - - else - #js-build-scroll.scroll-controls - = link_to '#build-trace', class: 'btn' do - %i.fa.fa-angle-up - = link_to '#down-build-trace', class: 'btn' do - %i.fa.fa-angle-down - %pre.build-trace#build-trace - %code.bash.js-build-output - = icon("refresh spin", class: "js-build-refresh") + .prepend-top-default + - if @build.active? + .autoscroll-container + %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll + - if @build.erased? + .erased.alert.alert-warning + - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by + Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} + - else + #js-build-scroll.scroll-controls + = link_to '#build-trace', class: 'btn' do + %i.fa.fa-angle-up + = link_to '#down-build-trace', class: 'btn' do + %i.fa.fa-angle-down + %pre.build-trace#build-trace + %code.bash.js-build-output + = icon("refresh spin", class: "js-build-refresh") - #down-build-trace + #down-build-trace -= render "sidebar" + = render "sidebar" -:javascript - new Build({ - page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", - build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", - build_status: "#{@build.status}", - build_stage: "#{@build.stage}", - state1: "#{trace_with_state[:state]}" - }) + :javascript + new Build({ + page_url: "#{namespace_project_build_url(@project.namespace, @project, @build)}", + build_url: "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", + build_status: "#{@build.status}", + build_stage: "#{@build.stage}", + state1: "#{trace_with_state[:state]}" + }) diff --git a/app/views/projects/commit/builds.html.haml b/app/views/projects/commit/builds.html.haml index 2f051fb90e0..f9d7eac3542 100644 --- a/app/views/projects/commit/builds.html.haml +++ b/app/views/projects/commit/builds.html.haml @@ -1,7 +1,10 @@ +- @no_container = true - page_title "Builds", "#{@commit.title} (#{@commit.short_id})", "Commits" += render "projects/commits/head" -.prepend-top-default - = render "commit_box" +%div{ class: container_class } + .prepend-top-default + = render "commit_box" -= render "ci_menu" -= render "builds" + = render "ci_menu" + = render "builds" diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index ed44d86a687..cebf58d63df 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,14 +1,17 @@ +- @no_container = true - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" - page_description @commit.description += render "projects/commits/head" -.prepend-top-default - = render "commit_box" -- if @commit.status - = render "ci_menu" -- else - %div.block-connector -= render "projects/diffs/diffs", diffs: @diffs -= render "projects/notes/notes_with_form" -- if can_collaborate_with_project? - - %w(revert cherry-pick).each do |type| - = render "projects/commit/change", type: type, commit: @commit, title: @commit.title +%div{ class: container_class } + .prepend-top-default + = render "commit_box" + - if @commit.status + = render "ci_menu" + - else + %div.block-connector + = render "projects/diffs/diffs", diffs: @diffs + = render "projects/notes/notes_with_form" + - if can_collaborate_with_project? + - %w(revert cherry-pick).each do |type| + = render "projects/commit/change", type: type, commit: @commit, title: @commit.title diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index 6d1bdb9320f..3871165763c 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "Edit", @environment.name, "Environments" += render "projects/pipelines/head" -%h3.page-title - Edit environment -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + Edit environment + %hr + = render 'form' diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index e51667ade2d..24638c77cbb 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title 'New Environment' += render "projects/pipelines/head" -%h3.page-title - New environment -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + New environment + %hr + = render 'form' diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 6901ba13ab7..52b187e7e58 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "Edit", @label.name, "Labels" += render "projects/issues/head" -%h3.page-title - Edit Label -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + Edit Label + %hr + = render 'form' diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 49ddf901619..a1bb66cfb6c 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,6 +1,9 @@ +- @no_container = true - page_title "New Label" += render "projects/issues/head" -%h3.page-title - New Label -%hr -= render 'form' +%div{ class: container_class } + %h3.page-title + New Label + %hr + = render 'form' diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index be682226ab6..11f41e75e63 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -1,8 +1,12 @@ +- @no_container = true - page_title "Edit", @milestone.title, "Milestones" += render "projects/issues/head" -%h3.page-title - Edit Milestone ##{@milestone.iid} +%div{ class: container_class } -%hr + %h3.page-title + Edit Milestone ##{@milestone.iid} -= render "form" + %hr + + = render "form" diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 7f372b41698..cda093ade81 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,8 +1,11 @@ +- @no_container = true - page_title "New Milestone" += render "projects/issues/head" -%h3.page-title - New Milestone +%div{ class: container_class } + %h3.page-title + New Milestone -%hr + %hr -= render "form" + = render "form" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index e62f810a521..c83818e9199 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,49 +1,52 @@ +- @no_container = true - page_title @milestone.title, "Milestones" - page_description @milestone.description += render "projects/issues/head" -.detail-page-header - .status-box{ class: status_box_class(@milestone) } - - if @milestone.closed? - Closed - - elsif @milestone.expired? - Past due - - else - Open - %span.identifier - Milestone ##{@milestone.iid} - - if @milestone.expires_at - %span.creator - · - = @milestone.expires_at - .pull-right - - if can?(current_user, :admin_milestone, @project) - - if @milestone.active? - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" +%div{ class: container_class } + .detail-page-header + .status-box{ class: status_box_class(@milestone) } + - if @milestone.closed? + Closed + - elsif @milestone.expired? + Past due - else - = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" + Open + %span.identifier + Milestone ##{@milestone.iid} + - if @milestone.expires_at + %span.creator + · + = @milestone.expires_at + .pull-right + - if can?(current_user, :admin_milestone, @project) + - if @milestone.active? + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped" + - else + = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped" - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do - Edit + = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do + Edit - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do - Delete + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do + Delete -.detail-page-description.milestone-detail - %h2.title - = markdown_field(@milestone, :title) - %div - - if @milestone.description.present? - .description - .wiki - = preserve do - = markdown_field(@milestone, :description) + .detail-page-description.milestone-detail + %h2.title + = markdown_field(@milestone, :title) + %div + - if @milestone.description.present? + .description + .wiki + = preserve do + = markdown_field(@milestone, :description) -- if @milestone.total_items_count(current_user).zero? - .alert.alert-success.prepend-top-default - %span Assign some issues to this milestone. -- elsif @milestone.complete?(current_user) && @milestone.active? - .alert.alert-success.prepend-top-default - %span All issues for this milestone are closed. You may close this milestone now. + - if @milestone.total_items_count(current_user).zero? + .alert.alert-success.prepend-top-default + %span Assign some issues to this milestone. + - elsif @milestone.complete?(current_user) && @milestone.active? + .alert.alert-success.prepend-top-default + %span All issues for this milestone are closed. You may close this milestone now. -= render 'shared/milestones/summary', milestone: @milestone, project: @project -= render 'shared/milestones/tabs', milestone: @milestone + = render 'shared/milestones/summary', milestone: @milestone, project: @project + = render 'shared/milestones/tabs', milestone: @milestone diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 7d421c0e740..b10dd47709f 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -1,7 +1,7 @@ = content_for :sub_nav do .scrolling-tabs-container.sub-nav-scroll = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs + .nav-links.sub-nav.scrolling-tabs{ class: ('build' if local_assigns.fetch(:build_subnav, false)) } %ul{ class: (container_class) } - if project_nav_tab? :pipelines = nav_link(controller: :pipelines) do diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 75943c64276..688535ad764 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,8 +1,11 @@ +- @no_container = true - page_title "Pipeline" += render "projects/pipelines/head" -.prepend-top-default - - if @commit - = render "projects/pipelines/info" - %div.block-connector +%div{ class: container_class } + .prepend-top-default + - if @commit + = render "projects/pipelines/info" + %div.block-connector -= render "projects/commit/pipeline", pipeline: @pipeline + = render "projects/commit/pipeline", pipeline: @pipeline -- cgit v1.2.1 From 8d2de73a83b98741dbbbc21fe2cbcdaf7840996d Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 7 Oct 2016 17:16:42 +0100 Subject: fixup! Added link to bulk assign issues to MR author. (Issue #18876) --- app/controllers/projects/merge_requests_controller.rb | 2 ++ app/services/merge_requests/assign_issues_service.rb | 6 +++--- .../projects/merge_requests_controller_spec.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8bbf3ec67b3..17dc89f717b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -29,6 +29,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController # Allow modify merge_request before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] + before_action :authenticate_user!, only: [:assign_related_issues] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] def index diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb index 2e84f844449..f636e5fec4f 100644 --- a/app/services/merge_requests/assign_issues_service.rb +++ b/app/services/merge_requests/assign_issues_service.rb @@ -3,9 +3,9 @@ module MergeRequests def assignable_issues @assignable_issues ||= begin if current_user == merge_request.author - closes_issues. - reject { |issue| issue.assignee_id? }. - select { |issue| can?(current_user, :admin_issue, issue) } + closes_issues.select do |issue| + !issue.assignee_id? && can?(current_user, :admin_issue, issue) + end else [] end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 18041bce482..2a68e5a2c9b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -738,5 +738,22 @@ describe Projects::MergeRequestsController do expect(flash[:notice]).to eq '1 issue has been assigned to you' end + + it 'calls MergeRequests::AssignIssuesService' do + expect(MergeRequests::AssignIssuesService).to receive(:new). + with(project, user, merge_request: merge_request). + and_return(double(execute: {count: 1})) + + post_assign_issues + end + + it 'is skipped when not signed in' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + sign_out(:user) + + expect(MergeRequests::AssignIssuesService).not_to receive(:new) + + post_assign_issues + end end end -- cgit v1.2.1 From 3530489761fbf892adbf335fe65c0c73b978da39 Mon Sep 17 00:00:00 2001 From: Georg G Date: Fri, 7 Oct 2016 19:15:14 +0200 Subject: Use defined colour for a language when available This commit changes the colours of languages in the language graph to the ones Linguist has defined. When there is no colour defined for a language in Linguist, it will fall back to the old method of finding a colour. --- app/controllers/projects/graphs_controller.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 092ef32e6e3..f4f2e5841a0 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -35,15 +35,19 @@ class Projects::GraphsController < Projects::ApplicationController def languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum + colors = Linguist::Language.colors. + select { |lang| @languages.include? lang.name }. + map { |lang| [lang.name, lang.color] }. + to_h @languages = @languages.map do |language| name, share = language - color = Digest::SHA256.hexdigest(name)[0...6] + color = colors[name] || "##{Digest::SHA256.hexdigest(name)[0...6]}" { value: (share.to_f * 100 / total).round(2), label: name, - color: "##{color}", - highlight: "##{color}" + color: color, + highlight: color } end -- cgit v1.2.1 From 2de8fc3e13b64dfc50e872486aad0c33b35df8a6 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 19 Sep 2016 12:31:51 +0100 Subject: removes inconsistency regarding tagging immediately as merged once you create a branch using new branch button and adds changelog entry --- CHANGELOG | 2 ++ app/models/repository.rb | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..841861293c8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -119,6 +119,8 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.0 (unreleased) + - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/models/repository.rb b/app/models/repository.rb index bf59b74495b..1bf6e58b9db 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1013,13 +1013,17 @@ class Repository branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) - if branch_commit + if branch_commit && !same_head?(branch_commit.id, root_ref_commit.id) is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end end + def same_head?(first_commit_id, second_commit_id) + first_commit_id == second_commit_id + end + def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id second_commit_id = commit(second_commit_id).try(:id) || second_commit_id -- cgit v1.2.1 From ff076d88df70a70f6534faefefdca92b059318bf Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Tue, 27 Sep 2016 16:39:29 +0100 Subject: writes tests to verify the issue is solved and fixes breaking issues. --- spec/models/repository_spec.rb | 16 +++++++++++----- spec/support/test_env.rb | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 98c64c079b9..6dd3f91be17 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -115,10 +115,16 @@ describe Repository, models: true do describe '#merged_to_root_ref?' do context 'merged branch' do - subject { repository.merged_to_root_ref?('improve/awesome') } + subject { repository.merged_to_root_ref?('branch-merged') } it { is_expected.to be_truthy } end + + context 'not merged branch' do + subject { repository.merged_to_root_ref?('not-merged-branch') } + + it { is_expected.to be_falsey } + end end describe '#can_be_merged?' do @@ -316,7 +322,7 @@ describe Repository, models: true do subject { results.first } it { is_expected.to be_an String } - it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } + it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") } end end @@ -960,10 +966,10 @@ describe Repository, models: true do context 'cherry-picking a merge commit' do it 'cherry-picks the changes' do - expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).to be_nil + expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).to be_nil - repository.cherry_pick(user, pickable_merge, 'master') - expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).not_to be_nil + repository.cherry_pick(user, pickable_merge, 'improve/awesome') + expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).not_to be_nil end end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 0097dbf8fad..d56274d0979 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -5,6 +5,8 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { + 'not-merged-branch' => 'b83d6e3', + 'branch-merged' => '498214d', 'empty-branch' => '7efb185', 'ends-with.json' => '98b0d8b', 'flatten-dir' => 'e56497b', @@ -14,7 +16,7 @@ module TestEnv 'improve/awesome' => '5937ac0', 'markdown' => '0ed8c6c', 'lfs' => 'be93687', - 'master' => '5937ac0', + 'master' => 'b83d6e3', "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', -- cgit v1.2.1 From 237c8f66e6608420629503280aaea555ee980022 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 7 Oct 2016 15:24:09 +0200 Subject: Precalculate trending projects This commit introduces a Sidekiq worker that precalculates the list of trending projects on a daily basis. The resulting set is stored in a database table that is then queried by Project.trending. This setup means that Unicorn workers no longer _may_ have to calculate the list of trending projects. Furthermore it supports filtering without any complex caching mechanisms. The data in the "trending_projects" table is inserted in the same order as the project ranking. This means that getting the projects in the correct order is simply a matter of: SELECT projects.* FROM projects INNER JOIN trending_projects ON trending_projects.project_id = projects.id ORDER BY trending_projects.id ASC; Such a query will only take a few milliseconds at most (as measured on GitLab.com), opposed to a few seconds for the query used for calculating the project ranks. The migration in this commit does not require downtime and takes care of populating an initial list of trending projects. --- app/controllers/explore/projects_controller.rb | 3 +- app/finders/trending_projects_finder.rb | 16 ------- app/models/project.rb | 16 ++----- app/models/trending_project.rb | 35 ++++++++++++++ app/workers/trending_projects_worker.rb | 11 +++++ config/initializers/1_settings.rb | 4 ++ ...0161007133303_precalculate_trending_projects.rb | 38 +++++++++++++++ db/schema.rb | 9 +++- spec/finders/trending_projects_finder_spec.rb | 48 ------------------- spec/models/project_spec.rb | 26 ++-------- spec/models/trending_project_spec.rb | 56 ++++++++++++++++++++++ spec/workers/trending_projects_worker_spec.rb | 11 +++++ 12 files changed, 171 insertions(+), 102 deletions(-) delete mode 100644 app/finders/trending_projects_finder.rb create mode 100644 app/models/trending_project.rb create mode 100644 app/workers/trending_projects_worker.rb create mode 100644 db/migrate/20161007133303_precalculate_trending_projects.rb delete mode 100644 spec/finders/trending_projects_finder_spec.rb create mode 100644 spec/models/trending_project_spec.rb create mode 100644 spec/workers/trending_projects_worker_spec.rb diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 38e5943eb76..a62c6211372 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = TrendingProjectsFinder.new.execute - @projects = filter_projects(@projects) + @projects = filter_projects(Project.trending) @projects = @projects.page(params[:page]) respond_to do |format| diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb deleted file mode 100644 index c1e434d9926..00000000000 --- a/app/finders/trending_projects_finder.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Finder for retrieving public trending projects in a given time range. -class TrendingProjectsFinder - # current_user - The currently logged in User, if any. - # last_months - The number of months to limit the trending data to. - def execute(months_limit = 1) - Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do - Project.public_only.trending(months_limit.months.ago) - end - end - - private - - def cache_key_for(months) - "trending_projects/#{months}" - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 88e4bd14860..74d54e69648 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -375,19 +375,9 @@ class Project < ActiveRecord::Base %r{(?#{name_pattern}/#{name_pattern})} end - def trending(since = 1.month.ago) - # By counting in the JOIN we don't expose the GROUP BY to the outer query. - # This means that calls such as "any?" and "count" just return a number of - # the total count, instead of the counts grouped per project as a Hash. - join_body = "INNER JOIN ( - SELECT project_id, COUNT(*) AS amount - FROM notes - WHERE created_at >= #{sanitize(since)} - AND system IS FALSE - GROUP BY project_id - ) join_note_counts ON projects.id = join_note_counts.project_id" - - joins(join_body).reorder('join_note_counts.amount DESC') + def trending + joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id'). + reorder('trending_projects.id ASC') end def cached_count diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb new file mode 100644 index 00000000000..27e3732da17 --- /dev/null +++ b/app/models/trending_project.rb @@ -0,0 +1,35 @@ +class TrendingProject < ActiveRecord::Base + belongs_to :project + + # The number of months to include in the trending calculation. + MONTHS_TO_INCLUDE = 1 + + # The maximum number of projects to include in the trending set. + PROJECTS_LIMIT = 100 + + # Populates the trending projects table with the current list of trending + # projects. + def self.refresh! + # The calculation **must** run in a transaction. If the removal of data and + # insertion of new data were to run separately a user might end up with an + # empty list of trending projects for a short period of time. + transaction do + delete_all + + timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago) + + connection.execute <<-EOF.strip_heredoc + INSERT INTO #{table_name} (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT #{PROJECTS_LIMIT}; + EOF + end + end +end diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb new file mode 100644 index 00000000000..df4c4a6628b --- /dev/null +++ b/app/workers/trending_projects_worker.rb @@ -0,0 +1,11 @@ +class TrendingProjectsWorker + include Sidekiq::Worker + + sidekiq_options queue: :trending_projects + + def perform + Rails.logger.info('Refreshing trending projects') + + TrendingProject.refresh! + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c5ed2162c92..efe0ac9c965 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -304,6 +304,10 @@ Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *' Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' +Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['trending_projects_worker']['cron'] = '0 1 * * *' +Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsWorker' + # # GitLab Shell # diff --git a/db/migrate/20161007133303_precalculate_trending_projects.rb b/db/migrate/20161007133303_precalculate_trending_projects.rb new file mode 100644 index 00000000000..b324cd94268 --- /dev/null +++ b/db/migrate/20161007133303_precalculate_trending_projects.rb @@ -0,0 +1,38 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class PrecalculateTrendingProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :trending_projects do |t| + t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false + end + + timestamp = connection.quote(1.month.ago) + + # We're hardcoding the visibility level (public) here so that if it ever + # changes this query doesn't suddenly use the new value (which may break + # later migrations). + visibility = 20 + + execute <<-EOF.strip_heredoc + INSERT INTO trending_projects (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{visibility} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT 100; + EOF + end + + def down + drop_table :trending_projects + end +end diff --git a/db/schema.rb b/db/schema.rb index 56da70b3c02..c5ddf9eee32 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: 20160926145521) do +ActiveRecord::Schema.define(version: 20161007133303) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1070,6 +1070,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree + create_table "trending_projects", force: :cascade do |t| + t.integer "project_id", null: false + end + + add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree + create_table "u2f_registrations", force: :cascade do |t| t.text "certificate" t.string "key_handle" @@ -1212,5 +1218,6 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_foreign_key "personal_access_tokens", "users" add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches" + add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" end diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb deleted file mode 100644 index cfe15b9defa..00000000000 --- a/spec/finders/trending_projects_finder_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe TrendingProjectsFinder do - let(:user) { create(:user) } - let(:public_project1) { create(:empty_project, :public) } - let(:public_project2) { create(:empty_project, :public) } - let(:private_project) { create(:empty_project, :private) } - let(:internal_project) { create(:empty_project, :internal) } - - before do - 3.times do - create(:note_on_commit, project: public_project1) - end - - 2.times do - create(:note_on_commit, project: public_project2, created_at: 5.weeks.ago) - end - - create(:note_on_commit, project: private_project) - create(:note_on_commit, project: internal_project) - end - - describe '#execute', caching: true do - context 'without an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute - - expect(projects).to eq([public_project1]) - end - end - - context 'with an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute(2) - - expect(projects).to eq([public_project1, public_project2]) - end - end - - it 'caches the list of projects' do - projects = described_class.new - - expect(Project).to receive(:trending).once - - 2.times { projects.execute } - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8aadfcb439b..dae546a0cdc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -800,32 +800,14 @@ describe Project, models: true do end create(:note_on_commit, project: project2) - end - - describe 'without an explicit start date' do - subject { described_class.trending.to_a } - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project1, project2]) - end + TrendingProject.refresh! end - describe 'with an explicit start date' do - let(:date) { 2.months.ago } + subject { described_class.trending.to_a } - subject { described_class.trending(date).to_a } - - before do - 2.times do - # Little fix for special issue related to Fractional Seconds support for MySQL. - # See: https://github.com/rails/rails/pull/14359/files - create(:note_on_commit, project: project2, created_at: date + 1) - end - end - - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project2, project1]) - end + it 'sorts projects by the amount of notes in descending order' do + expect(subject).to eq([project1, project2]) end it 'does not take system notes into account' do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb new file mode 100644 index 00000000000..cc28c6d4004 --- /dev/null +++ b/spec/models/trending_project_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe TrendingProject do + let(:user) { create(:user) } + let(:public_project1) { create(:empty_project, :public) } + let(:public_project2) { create(:empty_project, :public) } + let(:public_project3) { create(:empty_project, :public) } + let(:private_project) { create(:empty_project, :private) } + let(:internal_project) { create(:empty_project, :internal) } + + before do + 3.times do + create(:note_on_commit, project: public_project1) + end + + 2.times do + create(:note_on_commit, project: public_project2) + end + + create(:note_on_commit, project: public_project3, created_at: 5.weeks.ago) + create(:note_on_commit, project: private_project) + create(:note_on_commit, project: internal_project) + end + + describe '.refresh!' do + before do + described_class.refresh! + end + + it 'populates the trending projects table' do + expect(described_class.count).to eq(2) + end + + it 'removes existing rows before populating the table' do + described_class.refresh! + + expect(described_class.count).to eq(2) + end + + it 'stores the project IDs for every trending project' do + rows = described_class.order(id: :asc).all + + expect(rows[0].project_id).to eq(public_project1.id) + expect(rows[1].project_id).to eq(public_project2.id) + end + + it 'does not store projects that fall out of the trending time range' do + expect(described_class.where(project_id: public_project3).any?).to eq(false) + end + + it 'stores only public projects' do + expect(described_class.where(project_id: [public_project1.id, public_project2.id]).count).to eq(2) + expect(described_class.where(project_id: [private_project.id, internal_project.id]).count).to eq(0) + end + end +end diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb new file mode 100644 index 00000000000..c3c6fdcf2d5 --- /dev/null +++ b/spec/workers/trending_projects_worker_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe TrendingProjectsWorker do + describe '#perform' do + it 'refreshes the trending projects' do + expect(TrendingProject).to receive(:refresh!) + + described_class.new.perform + end + end +end -- cgit v1.2.1 From 33ce1976451ee8fc0275e0ae012755053d50ec34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 13:35:26 +0200 Subject: API: New /users/:id/events endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/api/users.md | 146 ++++++++++++++++++++++++++++++++++++++++ lib/api/users.rb | 20 ++++++ spec/requests/api/users_spec.rb | 55 +++++++++++++++ 4 files changed, 222 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e6cdc09b9c6..4c257b36175 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.13.0 (unreleased) - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view + - API: New /users/:id/events endpoint - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 diff --git a/doc/api/users.md b/doc/api/users.md index 9be4f2e6ec3..15202010b7b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -627,3 +627,149 @@ Parameters: Will return `200 OK` on success, `404 User Not Found` is user cannot be found or `403 Forbidden` when trying to unblock a user blocked by LDAP synchronization. + +### Get user contribution events + +Get the contribution events for the specified user, sorted from newest to latest. + +``` +GET /users/:id/events +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/user/:id/events +``` + +Example response: + +```json +[ + { + "title": null, + "project_id": 15, + "action_name": "closed", + "target_id": 830, + "target_type": "Issue", + "author_id": 1, + "data": null, + "target_title": "Public project search field", + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + }, + { + "title": null, + "project_id": 15, + "action_name": "opened", + "target_id": null, + "target_type": null, + "author_id": 1, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "john", + "data": { + "before": "50d4420237a9de7be1304607147aec22e4a14af7", + "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "ref": "refs/heads/master", + "user_id": 1, + "user_name": "Dmitriy Zaporozhets", + "repository": { + "name": "gitlabhq", + "url": "git@dev.gitlab.org:gitlab/gitlabhq.git", + "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.", + "homepage": "https://dev.gitlab.org/gitlab/gitlabhq" + }, + "commits": [ + { + "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "message": "Add simple search to projects in public area", + "timestamp": "2013-05-13T18:18:08+00:00", + "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "author": { + "name": "Dmitriy Zaporozhets", + "email": "dmitriy.zaporozhets@gmail.com" + } + } + ], + "total_commits_count": 1 + }, + "target_title": null + }, + { + "title": null, + "project_id": 15, + "action_name": "closed", + "target_id": 840, + "target_type": "Issue", + "author_id": 1, + "data": null, + "target_title": "Finish & merge Code search PR", + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + }, + { + "title": null, + "project_id": 15, + "action_name": "commented on", + "target_id": 1312, + "target_type": "Note", + "author_id": 1, + "data": null, + "target_title": null, + "created_at": "2015-12-04T10:33:58.089Z", + "note": { + "id": 1312, + "body": "What an awesome day!", + "attachment": null, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2015-12-04T10:33:56.698Z", + "system": false, + "upvote": false, + "downvote": false, + "noteable_id": 377, + "noteable_type": "Issue" + }, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + } +] +``` diff --git a/lib/api/users.rb b/lib/api/users.rb index 18c4cad09ae..e868f628404 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -321,6 +321,26 @@ module API user.activate end end + + desc 'Get contribution events of a specified user' do + detail 'This feature was introduced in GitLab 8.13.' + success Entities::Event + end + params do + requires :id, type: String, desc: 'The user ID' + end + get ':id/events' do + user = User.find_by(id: declared(params).id) + not_found!('User') unless user + + events = user.recent_events. + merge(ProjectsFinder.new.execute(current_user)). + references(:project). + with_associations. + page(params[:page]) + + present paginate(events), with: Entities::Event + end end resource :user do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..56b2ec6271e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -895,4 +895,59 @@ describe API::API, api: true do expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) end end + + describe 'GET /user/:id/events' do + let(:user) { create(:user) } + let(:lambda_user) { create(:user) } + let(:project) { create(:empty_project) } + let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } + + before do + project.add_user(user, :developer) + EventCreateService.new.leave_note(note, user) + end + + context "as a user than cannot see the event's project" do + it 'returns no events' do + get api("/users/#{user.id}/events", lambda_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end + + context "as a user than can see the event's project" do + it_behaves_like 'a paginated resources' do + let(:request) { get api("/users/#{user.id}/events", user) } + end + + context 'joined event' do + it 'returns the "joined" event' do + get api("/users/#{user.id}/events", user) + + first_event = json_response.first + + expect(first_event['action_name']).to eq('commented on') + expect(first_event['project_id'].to_i).to eq(project.id) + expect(first_event['author_username']).to eq(user.username) + expect(first_event['note']['id']).to eq(note.id) + expect(first_event['note']['body']).to eq('What an awesome day!') + + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(user.username) + expect(last_event['author']['name']).to eq(user.name) + end + end + end + + it 'returns a 404 error if not found' do + get api('/users/42/events', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + end end -- cgit v1.2.1 From 517895da4c1ca6201f952e443a579e4f2845e6e0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 13:13:07 +0100 Subject: Don't run affix tabs in test env This was messing up other tests --- app/views/projects/merge_requests/_show.html.haml | 2 +- spec/features/merge_requests/sticky_tabs_spec.rb | 33 ----------------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 spec/features/merge_requests/sticky_tabs_spec.rb diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 351c9d6ff91..72ad96aa217 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -47,7 +47,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits_count.nonzero? - %ul.merge-request-tabs.nav-links.no-top.no-bottom.js-tabs-affix + %ul.merge-request-tabs.nav-links.no-top.no-bottom{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion diff --git a/spec/features/merge_requests/sticky_tabs_spec.rb b/spec/features/merge_requests/sticky_tabs_spec.rb deleted file mode 100644 index 3e8bd768324..00000000000 --- a/spec/features/merge_requests/sticky_tabs_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'spec_helper' - -feature 'Merge request tabs', js: true, feature: true do - include WaitForAjax - - let(:user) { create(:user) } - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } - - before do - project.team << [user, :master] - login_as user - visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) - - wait_for_ajax - end - - it 'affixes to top of page when scrolling' do - page.execute_script "window.scroll(0,10000)" - - expect(page).to have_selector('.js-tabs-affix.affix') - end - - it 'removes affix when scrolling to top' do - page.execute_script "window.scroll(0,10000)" - - expect(page).to have_selector('.js-tabs-affix.affix') - - page.execute_script "window.scroll(0,-10000)" - - expect(page).to have_selector('.js-tabs-affix.affix-top') - end -end -- cgit v1.2.1 From 66c32cab1af621caa6ae3cb24b82b344d43512a5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 14:17:06 +0300 Subject: Remove NamespacesController The main purpose of this controller was redirect to group or user page when URL like https://gitlab.com/gitlab-org was used. Now this functionality is handled by contrainers and take user to correct controller right from the start Signed-off-by: Dmitriy Zaporozhets --- app/controllers/namespaces_controller.rb | 25 ------ config/routes.rb | 2 - spec/controllers/namespaces_controller_spec.rb | 118 ------------------------- 3 files changed, 145 deletions(-) delete mode 100644 app/controllers/namespaces_controller.rb delete mode 100644 spec/controllers/namespaces_controller_spec.rb diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb deleted file mode 100644 index 83eec1bf4a2..00000000000 --- a/app/controllers/namespaces_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class NamespacesController < ApplicationController - skip_before_action :authenticate_user! - - def show - namespace = Namespace.find_by(path: params[:id]) - - if namespace - if namespace.is_a?(Group) - group = namespace - else - user = namespace.owner - end - end - - if user - redirect_to user_path(user) - elsif group && can?(current_user, :read_group, group) - redirect_to group_path(group) - elsif current_user.nil? - authenticate_user! - else - render_404 - end - end -end diff --git a/config/routes.rb b/config/routes.rb index bf7c5b76128..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,7 +87,5 @@ Rails.application.routes.draw do # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } - root to: "root#index" end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb deleted file mode 100644 index 2b334ed1172..00000000000 --- a/spec/controllers/namespaces_controller_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -describe NamespacesController do - let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } - - describe "GET show" do - context "when the namespace belongs to a user" do - let!(:other_user) { create(:user) } - - it "redirects to the user's page" do - get :show, id: other_user.username - - expect(response).to redirect_to(user_path(other_user)) - end - end - - context "when the namespace belongs to a group" do - let!(:group) { create(:group) } - - context "when the group is public" do - context "when not signed in" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the group is private" do - before do - group.update_attribute(:visibility_level, Group::PRIVATE) - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: group.path - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - context "when the user has access to the group" do - before do - group.add_developer(user) - end - - context "when the user is blocked" do - before do - user.block - end - - it "redirects to the sign in page" do - get :show, id: group.path - - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when the user isn't blocked" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the user doesn't have access to the group" do - it "responds with status 404" do - get :show, id: group.path - - expect(response).to have_http_status(404) - end - end - end - end - end - - context "when the namespace doesn't exist" do - context "when signed in" do - before do - sign_in(user) - end - - it "responds with status 404" do - get :show, id: "doesntexist" - - expect(response).to have_http_status(404) - end - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: "doesntexist" - - expect(response).to redirect_to(new_user_session_path) - end - end - end - end -end -- cgit v1.2.1 From 68ab7047dae98172a0bd8b92956f2ee51b9167a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 14:53:49 +0300 Subject: Update git over http test to match new routing Signed-off-by: Dmitriy Zaporozhets --- spec/requests/git_http_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..413d06715b3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -412,10 +412,9 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } - before { get path, params } - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) + it "fails to find a route" do + expect { get(path, params) }.to raise_error(ActionController::RoutingError) end end end -- cgit v1.2.1 From d6cfc0042ed2ce9a33f31a6c44661c136e861b98 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 16:39:57 +0300 Subject: Catch any undefined API routing and return 400 Bad Request Signed-off-by: Dmitriy Zaporozhets --- lib/api/api.rb | 4 ++++ spec/requests/api/users_spec.rb | 4 ++-- spec/routing/routing_spec.rb | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index 0bbf73a1b63..95a64f14ac7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,5 +73,9 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + + route :any, '*path' do + error!('400 Bad Request', 400) + end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..c040000e8bb 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,9 +90,9 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 404 if invalid ID" do + it "returns a 400 if invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0dd00af878d..0ee1c811dfb 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -266,7 +266,9 @@ describe "Groups", "routing" do end it "also display group#show on the short path" do - expect(get('/1')).to route_to('namespaces#show', id: '1') + allow(Group).to receive(:find_by_path).and_return(true) + + expect(get('/1')).to route_to('groups#show', id: '1') end end -- cgit v1.2.1 From fdfc93679d1ca91d4666095ba2ca732fdb273947 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 18:39:04 +0300 Subject: Fix API specs behaviour for invalid routing Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/api/README.md | 13 +++++++++ spec/requests/api/project_hooks_spec.rb | 5 ++-- spec/requests/api/users_spec.rb | 52 +++++++++++++++++++++------------ 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 72e146f4f3f..aec47dd66f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md + - API: all unknown routing will be handled with 400 Bad Request v 8.12.5 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index bbd5bcfb386..8004a00659c 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,6 +355,19 @@ follows: } ``` +## Bad request + +When you try to access API URL that does not exist you will receive 400 Bad Request. + +``` +HTTP/1.1 400 Bad Request +Content-Type: application/json +{ + "error": "400 Bad Request" +} +``` + + ## Clients There are many unofficial GitLab API Clients for most of the popular diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 765dc8a8f66..5d739802095 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,9 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 405 error if hook id not given" do + it "returns a 400 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(400) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c040000e8bb..9537b0ec83d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -92,6 +92,7 @@ describe API::API, api: true do it "returns a 400 if invalid ID" do get api("/users/1ASDF", user) + expect(response).to have_http_status(400) end end @@ -340,8 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 if invalid ID" do + put api("/users/ASDF", admin) + + expect(response).to have_http_status(400) end it 'returns 400 error if user does not validate' do @@ -525,9 +528,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "raises error for invalid ID" do + it "returns a 400 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(400) end end end @@ -566,8 +570,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF/emails/bar", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/ASDF/emails/bar", admin) + + expect(response).to have_http_status(400) end end end @@ -600,8 +606,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -667,9 +675,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(404) + + expect(response).to have_http_status(400) end end @@ -727,8 +736,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/keys/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/keys/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -776,9 +787,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(404) + + expect(response).to have_http_status(400) end end @@ -825,8 +837,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/emails/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/emails/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -891,8 +905,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + put api("/users/ASDF/block", admin) + + expect(response).to have_http_status(400) end end end -- cgit v1.2.1 From 137ebcfb3cb013174f2885776a47264cffd193a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 20:18:02 +0300 Subject: Replace undefined Grape routing code from 400 to 404 Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/api/README.md | 6 ++-- lib/api/api.rb | 2 +- spec/requests/api/project_hooks_spec.rb | 4 +-- spec/requests/api/users_spec.rb | 50 +++++++++++++++++---------------- 5 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index aec47dd66f2..fc5e02cfd73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,6 +83,7 @@ v 8.13.0 (unreleased) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 400 Bad Request + - API: all unknown routing will be handled with 404 Not Found v 8.12.5 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index 8004a00659c..d47e537debc 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -357,13 +357,13 @@ follows: ## Bad request -When you try to access API URL that does not exist you will receive 400 Bad Request. +When you try to access API URL that does not exist you will receive 404 Not Found. ``` -HTTP/1.1 400 Bad Request +HTTP/1.1 404 Not Found Content-Type: application/json { - "error": "400 Bad Request" + "error": "404 Not Found" } ``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 95a64f14ac7..99722a0a65c 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -75,7 +75,7 @@ module API mount ::API::Variables route :any, '*path' do - error!('400 Bad Request', 400) + error!('404 Not Found', 404) end end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 5d739802095..cfcdcad74cd 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,10 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 400 error if hook id not given" do + it "returns a 404 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 9537b0ec83d..f0dd0592adb 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,10 +90,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 400 if invalid ID" do + it "returns a 404 for invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -341,10 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 400 if invalid ID" do + it "returns a 404 if invalid ID" do put api("/users/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end it 'returns 400 error if user does not validate' do @@ -410,9 +410,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -496,9 +496,10 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "raises error for invalid ID" do + it "returns a 404 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(400) + + expect(response).to have_http_status(404) end end @@ -528,10 +529,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end @@ -570,10 +571,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/ASDF/emails/bar", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end @@ -606,10 +607,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -662,6 +663,7 @@ describe API::API, api: true do it "returns 404 Not Found within invalid ID" do get api("/user/keys/42", user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -675,10 +677,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -736,10 +738,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/keys/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -787,10 +789,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -837,10 +839,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -905,10 +907,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/block", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end -- cgit v1.2.1 From 3e49123dd5f8cb3c04184ce53e95f598e82a9f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 21:09:41 +0300 Subject: Fix api users spec for post request with invalid id Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/users_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f0dd0592adb..b002949b41b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -410,9 +410,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end @@ -496,10 +496,10 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "returns a 404 for invalid ID" do + it "returns a 400 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end -- cgit v1.2.1 From dc37ef6139ba96fe0b90f7c0188161b298269fef Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Oct 2016 16:04:05 +0300 Subject: Better wording in API readme Signed-off-by: Dmitriy Zaporozhets --- doc/api/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/README.md b/doc/api/README.md index d47e537debc..9e907689c80 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,9 +355,9 @@ follows: } ``` -## Bad request +## Unknown route -When you try to access API URL that does not exist you will receive 404 Not Found. +When you try to access an API URL that does not exist you will receive 404 Not Found. ``` HTTP/1.1 404 Not Found -- cgit v1.2.1 From 09ccf6cce3e191f5971c96f1ce8b227a8f3682af Mon Sep 17 00:00:00 2001 From: Georg G Date: Mon, 10 Oct 2016 16:18:26 +0200 Subject: Use Linguist::Language[] instead of creating a hash --- CHANGELOG | 1 + app/controllers/projects/graphs_controller.rb | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..e81df2fbf11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.13.0 (unreleased) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 - Add configurable email subject suffix (Fu Xu) + - Use defined colour for a language when available !6748 (nilsding) - Added tooltip to fork count on project show page. (Justin DiPierro) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index f4f2e5841a0..923e7340e69 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -35,14 +35,10 @@ class Projects::GraphsController < Projects::ApplicationController def languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum - colors = Linguist::Language.colors. - select { |lang| @languages.include? lang.name }. - map { |lang| [lang.name, lang.color] }. - to_h @languages = @languages.map do |language| name, share = language - color = colors[name] || "##{Digest::SHA256.hexdigest(name)[0...6]}" + color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}" { value: (share.to_f * 100 / total).round(2), label: name, -- cgit v1.2.1 From c69b81839430500de49d089df2049e778c8b7ef8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Oct 2016 17:37:08 +0300 Subject: Fix duplicate entry in CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fc5e02cfd73..54f73419571 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,7 +82,6 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md - - API: all unknown routing will be handled with 400 Bad Request - API: all unknown routing will be handled with 404 Not Found v 8.12.5 (unreleased) -- cgit v1.2.1 From 4917bbd7ffc9e7e67d93a08650d95d02a0a67081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 17:03:34 +0200 Subject: Speed up specs for GET /projects/:id/events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 8.15s to 4.55s by grouping expectations Signed-off-by: Rémy Coutable --- spec/requests/api/projects_spec.rb | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..85717d274aa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -588,37 +588,39 @@ describe API::API, api: true do before do note = create(:note_on_issue, note: 'What an awesome day!', project: project) EventCreateService.new.leave_note(note, note.author) - get api("/projects/#{project.id}/events", user) end - it { expect(response).to have_http_status(200) } + it 'returns all events' do + get api("/projects/#{project.id}/events", user) - context 'joined event' do - let(:json_event) { json_response[1] } + expect(response).to have_http_status(200) - it { expect(json_event['action_name']).to eq('joined') } - it { expect(json_event['project_id'].to_i).to eq(project.id) } - it { expect(json_event['author_username']).to eq(user3.username) } - it { expect(json_event['author']['name']).to eq(user3.name) } - end + first_event = json_response.first - context 'comment event' do - let(:json_event) { json_response.first } + expect(first_event['action_name']).to eq('commented on') + expect(first_event['note']['body']).to eq('What an awesome day!') - it { expect(json_event['action_name']).to eq('commented on') } - it { expect(json_event['note']['body']).to eq('What an awesome day!') } + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(user3.username) + expect(last_event['author']['name']).to eq(user3.name) end end it 'returns a 404 error if not found' do get api('/projects/42/events', user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'returns a 404 error if user is not a member' do other_user = create(:user) + get api("/projects/#{project.id}/events", other_user) + expect(response).to have_http_status(404) end end -- cgit v1.2.1 From 6606642f8f352267d9f645778a789b79d98a6ca8 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Mon, 10 Oct 2016 16:19:46 +0100 Subject: fixup! Added link to bulk assign issues to MR author. (Issue #18876) --- spec/controllers/projects/merge_requests_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 2a68e5a2c9b..84298f8bef4 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -742,7 +742,7 @@ describe Projects::MergeRequestsController do it 'calls MergeRequests::AssignIssuesService' do expect(MergeRequests::AssignIssuesService).to receive(:new). with(project, user, merge_request: merge_request). - and_return(double(execute: {count: 1})) + and_return(double(execute: { count: 1 })) post_assign_issues end -- cgit v1.2.1 From cc8ad60fc42e9a115da63f0e0eca71c4b2567c35 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 12 Sep 2016 12:19:20 +0200 Subject: Update pipeline graph styles to match mockup --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++++ app/assets/stylesheets/pages/pipelines.scss | 36 +++++++++++++++++------------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 8813bb5dfef..d3ed9757afe 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,4 +1,12 @@ (function() { + + function addMarginToBuild () { + const $secondChild = $('.build:nth-child(2)'); + if ($secondChild.length) { + $secondChild.closest('.stage-column').addClass('left-margin'); + } + } + function toggleGraph() { const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); @@ -21,4 +29,5 @@ } $(document).on('click', '.toggle-pipeline-btn', toggleGraph); + $(document).on('ready', addMarginToBuild); })(); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..e7e1a2a9b18 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,7 +303,13 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 65px; + margin-right: 48px; + + &.left-margin { + &:not(:first-child) { + margin-left: 48px; + } + } li { list-style: none; @@ -321,9 +327,9 @@ .build { border: 1px solid $border-color; position: relative; - padding: 6px 10px; + padding: 8px 10px; border-radius: 30px; - width: 150px; + width: 186px; margin-bottom: 10px; &.playable { @@ -443,9 +449,9 @@ content: ''; position: absolute; top: 50%; - right: -69px; + right: -48px; border-top: 2px solid $border-color; - width: 69px; + width: 48px; height: 1px; } } @@ -457,22 +463,22 @@ top: -47px; position: absolute; border-bottom: 2px solid $border-color; - width: 20px; + width: 25px; height: 65px; } // Right connecting curves &::after { - right: -20px; + right: -25px; border-right: 2px solid $border-color; - border-radius: 0 0 15px; + border-radius: 0 0 20px; } // Left connecting curves &::before { - left: -20px; + left: -25px; border-left: 2px solid $border-color; - border-radius: 0 0 0 15px; + border-radius: 0 0 0 20px; } } @@ -538,20 +544,20 @@ width: 21px; height: 25px; position: absolute; - top: -29px; + top: -30px; border-top: 2px solid $border-color; } &::after { - left: -39px; + left: -44px; border-right: 2px solid $border-color; - border-radius: 0 15px; + border-radius: 0 20px; } &::before { - right: -39px; + right: -44px; border-left: 2px solid $border-color; - border-radius: 15px 0 0; + border-radius: 20px 0 0; } } } -- cgit v1.2.1 From ff0f70c0fe75773d060ecf5cf75d6fc5f879283b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 14 Sep 2016 13:51:36 +0200 Subject: Change length of connecting lines based on number of builds --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++--- app/assets/stylesheets/pages/pipelines.scss | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index d3ed9757afe..f501761b1ec 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,9 +1,12 @@ (function() { function addMarginToBuild () { - const $secondChild = $('.build:nth-child(2)'); - if ($secondChild.length) { - $secondChild.closest('.stage-column').addClass('left-margin'); + const $secondChildBuildNode = $('.build:nth-child(2)'); + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + // const $previousBuildColumn = $secondChildBuildNode.closest('.stage-column').prev('.stage-column'); + if ($secondChildBuildNode.length) { + $secondChildBuildNode.closest('.stage-column').addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e7e1a2a9b18..37df702ea13 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,11 +303,26 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 48px; + + &:not(:last-child) { + margin-right: 44px; + } &.left-margin { &:not(:first-child) { - margin-left: 48px; + margin-left: 44px; + + .left-connector { + &::before { + content: ''; + position: absolute; + top: 50%; + left: -48px; + border-top: 2px solid $border-color; + width: 48px; + height: 1px; + } + } } } @@ -348,7 +363,10 @@ } .build-content { - width: 130px; + width: 164px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; .ci-status-text { width: 110px; -- cgit v1.2.1 From 65e482e7e9b7385e6d8ee72c102eca6e79ee97fa Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Sep 2016 09:44:00 +0200 Subject: Change size of pipeline status icons and dropdowns --- app/assets/stylesheets/pages/pipelines.scss | 47 ++++++++++++++-------- .../projects/ci/builds/_build_pipeline.html.haml | 6 ++- .../commit/_pipeline_status_group.html.haml | 3 +- .../_generic_commit_status_pipeline.html.haml | 6 ++- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 37df702ea13..547b9742dff 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -331,9 +331,9 @@ } .stage-name { - margin-bottom: 15px; + margin: 0 0 15px 10px; font-weight: bold; - width: 150px; + width: 176px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -364,12 +364,17 @@ .build-content { width: 164px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + .ci-status-icon { + + svg { + height: 20px; + width: 20px; + } + } .ci-status-text { - width: 110px; + width: 135px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -397,27 +402,37 @@ color: $layout-link-gray; .ci-status-text { - width: 80px; + width: 112px; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 200px; + width: 168px; left: auto; - right: -214px; + right: -180px; top: -9px; max-height: 245px; overflow-y: scroll; - a:hover { - .ci-status-text { - text-decoration: none; + a { + padding: 7px 8px; + margin: 0 8px; + + &:hover { + .ci-status-text { + text-decoration: none; + } } } + svg { + width: 14px; + height: 14px; + } + .ci-status-text { - width: 145px; + width: 112px; } .arrow { @@ -482,7 +497,7 @@ position: absolute; border-bottom: 2px solid $border-color; width: 25px; - height: 65px; + height: 69px; } // Right connecting curves @@ -504,7 +519,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -10px; + top: -7px; } .curve { display: block; @@ -562,7 +577,7 @@ width: 21px; height: 25px; position: absolute; - top: -30px; + top: -31.5px; border-top: 2px solid $border-color; } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 547bc0c9c19..017d3ff6af2 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -5,8 +5,10 @@ .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) .ci-status-text= subject.name - else - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 4e7a6f1af08..2064242ab54 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,5 +1,6 @@ - group_status = CommitStatus.where(id: subject).status -= render_status_with_link('build', group_status) +%span.ci-status-icon + = render_status_with_link('build', group_status) .dropdown.inline %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 409f4701e4b..0a66d60accc 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,7 +1,9 @@ - if subject.target_url = link_to subject.target_url do - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name - else - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name -- cgit v1.2.1 From eb55ac7d4d13a2c404d24c68cef10675ce29cf3c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 21 Sep 2016 01:45:39 +0100 Subject: Added final changes from handover --- app/assets/stylesheets/framework/variables.scss | 5 +++ app/assets/stylesheets/pages/pipelines.scss | 38 +++++++++++++--------- .../projects/commit/_pipeline_stage.html.haml | 2 +- .../commit/_pipeline_status_group.html.haml | 17 +++++----- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 14ec310de2d..4c34ed3ebf7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -17,8 +17,10 @@ $white-normal: #ededed; $white-dark: #ececec; $gray-light: #fafafa; +$gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; $gray-dark: #ededed; +$gray-darker: #eee; $gray-darkest: #c9c9c9; $green-light: #38ae67; @@ -33,6 +35,8 @@ $blue-medium-light: #3498cb; $blue-medium: #2f8ebf; $blue-medium-dark: #2d86b4; +$blue-light-transparent: rgba(44, 159, 216, 0.05); + $orange-light: #fc8a51; $orange-normal: #e75e40; $orange-dark: #ce5237; @@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f; $gl-font-size: 15px; $gl-title-color: #333; $gl-text-color: #5c5c5c; +$gl-text-color-light: #8c8c8c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 547b9742dff..36c57f3ca30 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -342,11 +342,18 @@ .build { border: 1px solid $border-color; position: relative; - padding: 8px 10px; + padding: 7px 10px 8px; border-radius: 30px; width: 186px; margin-bottom: 10px; + &:hover { + background-color: $gray-lighter; + .dropdown-menu-toggle { + background-color: transparent; + } + } + &.playable { background-color: $gray-light; @@ -366,7 +373,6 @@ width: 164px; .ci-status-icon { - svg { height: 20px; width: 20px; @@ -385,41 +391,40 @@ } a { - color: $layout-link-gray; + color: $gl-text-color-light; text-decoration: none; - - &:hover { - .ci-status-text { - text-decoration: underline; - } - } } .dropdown-menu-toggle { border: none; width: auto; padding: 0; - color: $layout-link-gray; + color: $gl-text-color-light; .ci-status-text { - width: 112px; + max-width: 112px; + width: auto; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 168px; + width: 186px; left: auto; - right: -180px; + right: -197px; top: -9px; max-height: 245px; overflow-y: scroll; a { - padding: 7px 8px; + color: $gl-text-color; + padding: 7px 8px 8px; margin: 0 8px; &:hover { + background-color: $blue-light-transparent; + border-radius: 3px; + .ci-status-text { text-decoration: none; } @@ -465,9 +470,10 @@ } .badge { - background-color: $gray-dark; - color: $layout-link-gray; + background-color: $gray-darker; + color: $gl-text-color-light; font-weight: normal; + margin-left: $btn-xs-side-margin; } } diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 23c5c51fbc2..68d42126bf6 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .build-content + .dropdown.inline.build-content{ type: 'button', data: { toggle: 'dropdown' } } = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 2064242ab54..bff65bff653 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,12 +1,11 @@ - group_status = CommitStatus.where(id: subject).status %span.ci-status-icon = render_status_with_link('build', group_status) -.dropdown.inline - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } - %span.ci-status-text - = name - %span.badge= subject.size - %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow - - subject.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status +%button.dropdown-menu-toggle + %span.ci-status-text + = name + %span.badge= subject.size +%ul.dropdown-menu.grouped-pipeline-dropdown + .arrow + - subject.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From 5defad2d21b6481c07fb4a77f0a56ed7c19ff899 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 29 Sep 2016 22:24:37 +0100 Subject: Finished up margin JS logic --- app/assets/javascripts/dispatcher.js | 3 + app/assets/javascripts/pipeline.js.es6 | 60 ++++++++++---------- app/assets/stylesheets/pages/pipelines.scss | 4 ++ app/views/projects/commit/_pipeline.html.haml | 79 ++++++++++++++------------- 4 files changed, 79 insertions(+), 67 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 8d99b12102d..45494afe7aa 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -126,6 +126,9 @@ new TreeView(); } break; + case 'projects:pipelines:show': + new window.gl.Pipelines(); + break; case 'groups:activity': new Activities(); break; diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index f501761b1ec..6299ba2269d 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,36 +1,40 @@ -(function() { - - function addMarginToBuild () { - const $secondChildBuildNode = $('.build:nth-child(2)'); - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - // const $previousBuildColumn = $secondChildBuildNode.closest('.stage-column').prev('.stage-column'); - if ($secondChildBuildNode.length) { - $secondChildBuildNode.closest('.stage-column').addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); } - } - function toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const $icon = $(this).find('.fa'); + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - const expandIcon = 'fa-caret-down'; - const hideIcon = 'fa-caret-up'; + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - if(graphCollapsed) { - $btnText.text('Expand'); - $icon.removeClass(hideIcon).addClass(expandIcon); - } else { - $btnText.text('Hide'); - $icon.removeClass(expandIcon).addClass(hideIcon); + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph-container').removeClass('hidden'); } } - $(document).on('click', '.toggle-pipeline-btn', toggleGraph); - $(document).on('ready', addMarginToBuild); -})(); + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 36c57f3ca30..f4211ea3b2d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -326,6 +326,10 @@ } } + &.no-margin { + margin: 0; + } + li { list-style: none; } diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..bdf3c0a2aba 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,45 +1,46 @@ -.row-content-block.build-content.middle-block.pipeline-actions - .pull-right - .btn.btn-grouped.btn-white.toggle-pipeline-btn - %span.toggle-btn-text Hide - %span pipeline graph - = icon('caret-up') - - if can?(current_user, :update_pipeline, pipeline.project) - - if pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post +.pipeline-graph-container.hidden + .row-content-block.build-content.middle-block.pipeline-actions + .pull-right + .btn.btn-grouped.btn-white.toggle-pipeline-btn + %span.toggle-btn-text Hide + %span pipeline graph + %span.caret + - if can?(current_user, :update_pipeline, pipeline.project) + - if pipeline.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post - - if pipeline.builds.running_or_pending.any? - = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + - if pipeline.builds.running_or_pending.any? + = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - .oneline.clearfix - - if defined?(pipeline_details) && pipeline_details - Pipeline - = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" - with - = pluralize pipeline.statuses.count(:id), "build" - - if pipeline.ref - for - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" - - if pipeline.duration - in - = time_interval_in_words pipeline.duration + .oneline.clearfix + - if defined?(pipeline_details) && pipeline_details + Pipeline + = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" + with + = pluralize pipeline.statuses.count(:id), "build" + - if pipeline.ref + for + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" + - if pipeline.duration + in + = time_interval_in_words pipeline.duration -.row-content-block.build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses + .row-content-block.build-content.middle-block.pipeline-graph + .pipeline-visualization + %ul.stage-column-list + - stages = pipeline.stages_with_latest_statuses + - stages.each do |stage, statuses| + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + = render "projects/commit/pipeline_stage", statuses: statuses - if pipeline.yaml_errors.present? -- cgit v1.2.1 From c2deaa7e0db808abad86e87bac9524784b02e602 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 4 Oct 2016 13:59:57 -0500 Subject: Move hidden class to graph itself; remove background color from play node & align icon --- app/assets/javascripts/pipeline.js.es6 | 2 +- app/assets/stylesheets/pages/pipelines.scss | 5 ++--- app/views/projects/commit/_pipeline.html.haml | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 6299ba2269d..68a34dda2af 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -31,7 +31,7 @@ if ($('.build', $this).length === 1) $this.addClass('no-margin'); }); } - $('.pipeline-graph-container').removeClass('hidden'); + $('.pipeline-graph').removeClass('hidden'); } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index f4211ea3b2d..4e53c9765df 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -359,11 +359,10 @@ } &.playable { - background-color: $gray-light; svg { - height: 12px; - width: 12px; + height: 13px; + width: 20px; position: relative; top: 1px; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index bdf3c0a2aba..288c06d9b67 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,4 +1,4 @@ -.pipeline-graph-container.hidden +.pipeline-graph-container .row-content-block.build-content.middle-block.pipeline-actions .pull-right .btn.btn-grouped.btn-white.toggle-pipeline-btn @@ -28,7 +28,7 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.pipeline-graph + .row-content-block.build-content.middle-block.pipeline-graph.hidden .pipeline-visualization %ul.stage-column-list - stages = pipeline.stages_with_latest_statuses -- cgit v1.2.1 From 113050c5709a204f58e0395b08e1582b3c862d66 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 13:35:13 -0500 Subject: Fix ul html --- app/assets/stylesheets/pages/pipelines.scss | 1 - app/views/projects/commit/_pipeline_status_group.html.haml | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 4e53c9765df..9ce5bee466f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -422,7 +422,6 @@ a { color: $gl-text-color; padding: 7px 8px 8px; - margin: 0 8px; &:hover { background-color: $blue-light-transparent; diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index bff65bff653..b26de822450 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -6,6 +6,7 @@ = name %span.badge= subject.size %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow + %li.arrow - subject.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From 6a7b673035752890023e630755f27ec0d412a129 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Oct 2016 18:00:06 +0100 Subject: JS review changes - Removed window from window.gl in dispatcher Added page:load event as ready isnt fired by turbolinks Fix dropdown menu link click registration --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/pipeline.js.es6 | 1 + app/assets/stylesheets/pages/pipelines.scss | 4 ++++ app/views/projects/commit/_pipeline_stage.html.haml | 2 +- app/views/projects/commit/_pipeline_status_group.html.haml | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 45494afe7aa..adff73af79c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -127,7 +127,7 @@ } break; case 'projects:pipelines:show': - new window.gl.Pipelines(); + new gl.Pipelines(); break; case 'groups:activity': new Activities(); diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 68a34dda2af..1030447f74a 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -4,6 +4,7 @@ constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); + $(document).off('page:load.addMarginToBuildColumns').on('page:load.addMarginToBuildColumns', this.addMarginToBuildColumns); } toggleGraph() { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9ce5bee466f..0c3c1e404af 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -373,6 +373,9 @@ } .build-content { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; width: 164px; .ci-status-icon { @@ -403,6 +406,7 @@ width: auto; padding: 0; color: $gl-text-color-light; + flex-grow: 1; .ci-status-text { max-width: 112px; diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 68d42126bf6..289aa5178b1 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .dropdown.inline.build-content{ type: 'button', data: { toggle: 'dropdown' } } + .dropdown.inline.build-content = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index b26de822450..8582e9a8772 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status %span.ci-status-icon = render_status_with_link('build', group_status) -%button.dropdown-menu-toggle +%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text = name %span.badge= subject.size -- cgit v1.2.1 From 4e1efca14119a25c441d833268894a632eb269d5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 14:33:55 -0500 Subject: Fix node flex alignment --- app/views/projects/commit/_pipeline_status_group.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 8582e9a8772..6ada719e006 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status -%span.ci-status-icon - = render_status_with_link('build', group_status) %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %span.ci-status-icon + = render_status_with_link('build', group_status) %span.ci-status-text = name %span.badge= subject.size -- cgit v1.2.1 From a57b3fc80c50b951d3a54ce2108608d45826c5ef Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Oct 2016 21:20:25 +0100 Subject: Corrected my correction for turbolinks -.-' Removed extra cell on generic pipeline --- app/assets/javascripts/pipeline.js.es6 | 3 +-- .../projects/generic_commit_statuses/_generic_commit_status.html.haml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 1030447f74a..a29f6ecdd59 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -3,8 +3,7 @@ class Pipelines { constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); - $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); - $(document).off('page:load.addMarginToBuildColumns').on('page:load.addMarginToBuildColumns', this.addMarginToBuildColumns); + this.addMarginToBuildColumns(); } toggleGraph() { diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 331dc1fcc29..80fe6be49b0 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -62,5 +62,3 @@ %td.coverage - if generic_commit_status.try(:coverage) #{generic_commit_status.coverage}% - - %td -- cgit v1.2.1 From 274ac4efdb94efe4d3ea4d5fff193eb2f02db507 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 12:23:00 +0100 Subject: JS review changes and fixed conflicts --- app/assets/javascripts/pipeline.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index a29f6ecdd59..6bf63ee6979 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -10,10 +10,10 @@ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') } -- cgit v1.2.1 From 879a68a7c48c469c2646ecd89412cab9679d8860 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Fri, 7 Oct 2016 14:57:57 +0200 Subject: slight update to lines and curves positioning --- app/assets/stylesheets/pages/pipelines.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 0c3c1e404af..04cce24ce7d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -321,6 +321,7 @@ border-top: 2px solid $border-color; width: 48px; height: 1px; + margin-top: -1px; } } } @@ -498,6 +499,7 @@ border-top: 2px solid $border-color; width: 48px; height: 1px; + margin-top: -1px; } } @@ -505,7 +507,7 @@ &:not(:first-child) { &::after, &::before { content: ''; - top: -47px; + top: -49px; position: absolute; border-bottom: 2px solid $border-color; width: 25px; @@ -531,7 +533,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -7px; + top: -9px; } .curve { display: block; @@ -589,7 +591,7 @@ width: 21px; height: 25px; position: absolute; - top: -31.5px; + top: -32.5px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From 7a28205629962427bfe5a48610ee4890b37f5ae8 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 7 Oct 2016 08:57:30 -0500 Subject: Remove negative margins --- app/assets/stylesheets/pages/pipelines.scss | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 04cce24ce7d..21224447628 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -316,12 +316,11 @@ &::before { content: ''; position: absolute; - top: 50%; + top: 49%; left: -48px; border-top: 2px solid $border-color; width: 48px; height: 1px; - margin-top: -1px; } } } @@ -494,12 +493,11 @@ &::after { content: ''; position: absolute; - top: 50%; + top: 49%; right: -48px; border-top: 2px solid $border-color; width: 48px; height: 1px; - margin-top: -1px; } } @@ -591,7 +589,7 @@ width: 21px; height: 25px; position: absolute; - top: -32.5px; + top: -31px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From f60e0b92b8cba1d70c40094511a87fc0ed62977d Mon Sep 17 00:00:00 2001 From: TMate Software Support Date: Mon, 10 Oct 2016 18:14:29 +0000 Subject: Update migrating_from_svn.md document on migration from SVN to GitLab as suggested by @axil in MR 6549. --- doc/workflow/importing/migrating_from_svn.md | 52 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 76839315c53..fc27a38f735 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -8,21 +8,16 @@ between the two, for more information consult your favorite search engine. There are two approaches to SVN to Git migration: -#### [Git/SVN Mirror](#mirror) +1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: + - Makes the GitLab repository to mirror the SVN project. + - Git and SVN repositories are kept in sync; you can use either one. + - Smoothens the migration process and allows to manage migration risks. - Make GitLab repository mirror SVN project. - - Git and SVN project are kept in sync; use either one or another. - - Smoothens migration process and allows to manage migration risks. +1. [Cut over migration](#cut-over-migration-with-svn2git) which: + - Translates and imports the existing data and history from SVN to Git. + - Is a fire and forget approach, good for smaller teams. -#### [Cut over migration](#cutover) - - Translate existing data and history from SVN to Git. - - Fire and forget approach, good for smaller teams. - -## Smooth migration with a Git/SVN mirror using SubGit +## Smooth migration with a Git/SVN mirror using SubGit #### Prerequisites @@ -37,20 +32,32 @@ at `/opt/subgit-VERSION/bin/subgit` #### Configuration In GitLab create new empty repository. In filesystem it will be located at -`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path. +`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path by default. +For convenice, assign this path to a variable: + +``` +GIT_REPOS_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +``` + +SubGit will keep this repository will be kept in sync with a remote SVN project. +For convenience, assign remote SVN project URL to a variable: + +``` +SVN_PROJECT_URL=http://svn.company.com/repos/project +``` Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran -on behalf of the same user that runs GitLab. +on behalf of the same user that keeps ownership of GitLab Git repositories (`git` by default): ``` -subgit configure --layout auto SVN_PROJECT_URL GIT_REPOS_PATH +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPOS_PATH ``` Adjust authors and branches mappings, if necessary: ``` -edit GIT_REPOS_PATH/subgit/authors.txt -edit GIT_REPOS_PATH/subgit/config +edit $GIT_REPOS_PATH/subgit/authors.txt +edit $GIT_REPOS_PATH/subgit/config ``` For more information regarding SubGit configuration options, refer to @@ -62,7 +69,7 @@ Run `subgit` to perform initial translation of existing SVN revisions into Git repository: ``` -subgit install GIT_REPOS_PATH +subgit install $GIT_REPOS_PATH ``` After initial translation is completed, GitLab Git repository and SVN project @@ -74,7 +81,7 @@ Would you prefer to perform one-time cut over migration with `subgit` use `import` command in place of `install`: ``` -subgit import GIT_REPOS_PATH +subgit import $GIT_REPOS_PATH ``` #### Licensing @@ -87,10 +94,9 @@ progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). #### Support -For any questions related to SVN to GitLab migration with SubGit contact us at [support@subgit.com](mailto:support@subgit.com). We support -all our users. +For any questions related to SVN to GitLab migration with SubGit you can contact SubGit team at [support@subgit.com](mailto:support@subgit.com). -## Cut over migration with svn2git +## Cut over migration with svn2git If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once -- cgit v1.2.1 From eecccf5e206ffdac6d29f757804165654324ea31 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 21:36:30 +0200 Subject: Change img dir name --- doc/gitlab-basics/add-file.md | 10 +++++----- doc/gitlab-basics/add-merge-request.md | 16 ++++++++-------- .../basicsimages/add_new_merge_request.png | Bin 9003 -> 0 bytes doc/gitlab-basics/basicsimages/add_sshkey.png | Bin 1394 -> 0 bytes doc/gitlab-basics/basicsimages/branch_info.png | Bin 7572 -> 0 bytes doc/gitlab-basics/basicsimages/branch_name.png | Bin 2137 -> 0 bytes doc/gitlab-basics/basicsimages/branches.png | Bin 3548 -> 0 bytes doc/gitlab-basics/basicsimages/button-create-mr.png | Bin 5927 -> 0 bytes doc/gitlab-basics/basicsimages/click-on-new-group.png | Bin 1957 -> 0 bytes doc/gitlab-basics/basicsimages/commit_changes.png | Bin 4941 -> 0 bytes doc/gitlab-basics/basicsimages/commit_message.png | Bin 5103 -> 0 bytes doc/gitlab-basics/basicsimages/commits.png | Bin 4112 -> 0 bytes doc/gitlab-basics/basicsimages/compare_branches.png | Bin 1520 -> 0 bytes doc/gitlab-basics/basicsimages/create_file.png | Bin 2451 -> 0 bytes doc/gitlab-basics/basicsimages/create_group.png | Bin 3184 -> 0 bytes doc/gitlab-basics/basicsimages/edit_file.png | Bin 2221 -> 0 bytes doc/gitlab-basics/basicsimages/file_located.png | Bin 3078 -> 0 bytes doc/gitlab-basics/basicsimages/file_name.png | Bin 2412 -> 0 bytes doc/gitlab-basics/basicsimages/find_file.png | Bin 8426 -> 0 bytes doc/gitlab-basics/basicsimages/find_group.png | Bin 5897 -> 0 bytes doc/gitlab-basics/basicsimages/fork.png | Bin 896 -> 0 bytes doc/gitlab-basics/basicsimages/group_info.png | Bin 15479 -> 0 bytes doc/gitlab-basics/basicsimages/groups.png | Bin 4752 -> 0 bytes doc/gitlab-basics/basicsimages/https.png | Bin 2822 -> 0 bytes doc/gitlab-basics/basicsimages/image_file.png | Bin 2796 -> 0 bytes doc/gitlab-basics/basicsimages/issue_title.png | Bin 8311 -> 0 bytes doc/gitlab-basics/basicsimages/issues.png | Bin 4153 -> 0 bytes doc/gitlab-basics/basicsimages/key.png | Bin 1177 -> 0 bytes doc/gitlab-basics/basicsimages/merge_requests.png | Bin 4213 -> 0 bytes doc/gitlab-basics/basicsimages/new_issue.png | Bin 2974 -> 0 bytes doc/gitlab-basics/basicsimages/new_merge_request.png | Bin 3162 -> 0 bytes doc/gitlab-basics/basicsimages/new_project.png | Bin 2234 -> 0 bytes doc/gitlab-basics/basicsimages/newbranch.png | Bin 1244 -> 0 bytes doc/gitlab-basics/basicsimages/paste_sshkey.png | Bin 7699 -> 0 bytes doc/gitlab-basics/basicsimages/profile_settings.png | Bin 1101 -> 0 bytes doc/gitlab-basics/basicsimages/project_info.png | Bin 21041 -> 0 bytes doc/gitlab-basics/basicsimages/public_file_link.png | Bin 3023 -> 0 bytes doc/gitlab-basics/basicsimages/select-group.png | Bin 6034 -> 0 bytes doc/gitlab-basics/basicsimages/select-group2.png | Bin 5040 -> 0 bytes doc/gitlab-basics/basicsimages/select_branch.png | Bin 11207 -> 0 bytes doc/gitlab-basics/basicsimages/select_project.png | Bin 16176 -> 0 bytes doc/gitlab-basics/basicsimages/settings.png | Bin 4149 -> 0 bytes doc/gitlab-basics/basicsimages/shh_keys.png | Bin 4782 -> 0 bytes doc/gitlab-basics/basicsimages/submit_new_issue.png | Bin 8644 -> 0 bytes doc/gitlab-basics/basicsimages/title_description_mr.png | Bin 11919 -> 0 bytes doc/gitlab-basics/basicsimages/white_space.png | Bin 2192 -> 0 bytes doc/gitlab-basics/command-line-commands.md | 4 ++-- doc/gitlab-basics/create-branch.md | 12 ++++++------ doc/gitlab-basics/create-group.md | 10 +++++----- doc/gitlab-basics/create-issue.md | 10 +++++----- doc/gitlab-basics/create-project.md | 4 ++-- doc/gitlab-basics/create-your-ssh-keys.md | 8 ++++---- doc/gitlab-basics/fork-project.md | 4 ++-- doc/gitlab-basics/img/add_new_merge_request.png | Bin 0 -> 9003 bytes doc/gitlab-basics/img/add_sshkey.png | Bin 0 -> 1394 bytes doc/gitlab-basics/img/branch_info.png | Bin 0 -> 7572 bytes doc/gitlab-basics/img/branch_name.png | Bin 0 -> 2137 bytes doc/gitlab-basics/img/branches.png | Bin 0 -> 3548 bytes doc/gitlab-basics/img/button-create-mr.png | Bin 0 -> 5927 bytes doc/gitlab-basics/img/click-on-new-group.png | Bin 0 -> 1957 bytes doc/gitlab-basics/img/commit_changes.png | Bin 0 -> 4941 bytes doc/gitlab-basics/img/commit_message.png | Bin 0 -> 5103 bytes doc/gitlab-basics/img/commits.png | Bin 0 -> 4112 bytes doc/gitlab-basics/img/compare_branches.png | Bin 0 -> 1520 bytes doc/gitlab-basics/img/create_file.png | Bin 0 -> 2451 bytes doc/gitlab-basics/img/create_group.png | Bin 0 -> 3184 bytes doc/gitlab-basics/img/edit_file.png | Bin 0 -> 2221 bytes doc/gitlab-basics/img/file_located.png | Bin 0 -> 3078 bytes doc/gitlab-basics/img/file_name.png | Bin 0 -> 2412 bytes doc/gitlab-basics/img/find_file.png | Bin 0 -> 8426 bytes doc/gitlab-basics/img/find_group.png | Bin 0 -> 5897 bytes doc/gitlab-basics/img/fork.png | Bin 0 -> 896 bytes doc/gitlab-basics/img/group_info.png | Bin 0 -> 15479 bytes doc/gitlab-basics/img/groups.png | Bin 0 -> 4752 bytes doc/gitlab-basics/img/https.png | Bin 0 -> 2822 bytes doc/gitlab-basics/img/image_file.png | Bin 0 -> 2796 bytes doc/gitlab-basics/img/issue_title.png | Bin 0 -> 8311 bytes doc/gitlab-basics/img/issues.png | Bin 0 -> 4153 bytes doc/gitlab-basics/img/key.png | Bin 0 -> 1177 bytes doc/gitlab-basics/img/merge_requests.png | Bin 0 -> 4213 bytes doc/gitlab-basics/img/new_issue.png | Bin 0 -> 2974 bytes doc/gitlab-basics/img/new_merge_request.png | Bin 0 -> 3162 bytes doc/gitlab-basics/img/new_project.png | Bin 0 -> 2234 bytes doc/gitlab-basics/img/newbranch.png | Bin 0 -> 1244 bytes doc/gitlab-basics/img/paste_sshkey.png | Bin 0 -> 7699 bytes doc/gitlab-basics/img/profile_settings.png | Bin 0 -> 1101 bytes doc/gitlab-basics/img/project_info.png | Bin 0 -> 21041 bytes doc/gitlab-basics/img/public_file_link.png | Bin 0 -> 3023 bytes doc/gitlab-basics/img/select-group.png | Bin 0 -> 6034 bytes doc/gitlab-basics/img/select-group2.png | Bin 0 -> 5040 bytes doc/gitlab-basics/img/select_branch.png | Bin 0 -> 11207 bytes doc/gitlab-basics/img/select_project.png | Bin 0 -> 16176 bytes doc/gitlab-basics/img/settings.png | Bin 0 -> 4149 bytes doc/gitlab-basics/img/shh_keys.png | Bin 0 -> 4782 bytes doc/gitlab-basics/img/submit_new_issue.png | Bin 0 -> 8644 bytes doc/gitlab-basics/img/title_description_mr.png | Bin 0 -> 11919 bytes doc/gitlab-basics/img/white_space.png | Bin 0 -> 2192 bytes 97 files changed, 39 insertions(+), 39 deletions(-) delete mode 100644 doc/gitlab-basics/basicsimages/add_new_merge_request.png delete mode 100644 doc/gitlab-basics/basicsimages/add_sshkey.png delete mode 100644 doc/gitlab-basics/basicsimages/branch_info.png delete mode 100644 doc/gitlab-basics/basicsimages/branch_name.png delete mode 100644 doc/gitlab-basics/basicsimages/branches.png delete mode 100644 doc/gitlab-basics/basicsimages/button-create-mr.png delete mode 100644 doc/gitlab-basics/basicsimages/click-on-new-group.png delete mode 100644 doc/gitlab-basics/basicsimages/commit_changes.png delete mode 100644 doc/gitlab-basics/basicsimages/commit_message.png delete mode 100644 doc/gitlab-basics/basicsimages/commits.png delete mode 100644 doc/gitlab-basics/basicsimages/compare_branches.png delete mode 100644 doc/gitlab-basics/basicsimages/create_file.png delete mode 100644 doc/gitlab-basics/basicsimages/create_group.png delete mode 100644 doc/gitlab-basics/basicsimages/edit_file.png delete mode 100644 doc/gitlab-basics/basicsimages/file_located.png delete mode 100644 doc/gitlab-basics/basicsimages/file_name.png delete mode 100644 doc/gitlab-basics/basicsimages/find_file.png delete mode 100644 doc/gitlab-basics/basicsimages/find_group.png delete mode 100644 doc/gitlab-basics/basicsimages/fork.png delete mode 100644 doc/gitlab-basics/basicsimages/group_info.png delete mode 100644 doc/gitlab-basics/basicsimages/groups.png delete mode 100644 doc/gitlab-basics/basicsimages/https.png delete mode 100644 doc/gitlab-basics/basicsimages/image_file.png delete mode 100644 doc/gitlab-basics/basicsimages/issue_title.png delete mode 100644 doc/gitlab-basics/basicsimages/issues.png delete mode 100644 doc/gitlab-basics/basicsimages/key.png delete mode 100644 doc/gitlab-basics/basicsimages/merge_requests.png delete mode 100644 doc/gitlab-basics/basicsimages/new_issue.png delete mode 100644 doc/gitlab-basics/basicsimages/new_merge_request.png delete mode 100644 doc/gitlab-basics/basicsimages/new_project.png delete mode 100644 doc/gitlab-basics/basicsimages/newbranch.png delete mode 100644 doc/gitlab-basics/basicsimages/paste_sshkey.png delete mode 100644 doc/gitlab-basics/basicsimages/profile_settings.png delete mode 100644 doc/gitlab-basics/basicsimages/project_info.png delete mode 100644 doc/gitlab-basics/basicsimages/public_file_link.png delete mode 100644 doc/gitlab-basics/basicsimages/select-group.png delete mode 100644 doc/gitlab-basics/basicsimages/select-group2.png delete mode 100644 doc/gitlab-basics/basicsimages/select_branch.png delete mode 100644 doc/gitlab-basics/basicsimages/select_project.png delete mode 100644 doc/gitlab-basics/basicsimages/settings.png delete mode 100644 doc/gitlab-basics/basicsimages/shh_keys.png delete mode 100644 doc/gitlab-basics/basicsimages/submit_new_issue.png delete mode 100644 doc/gitlab-basics/basicsimages/title_description_mr.png delete mode 100644 doc/gitlab-basics/basicsimages/white_space.png create mode 100644 doc/gitlab-basics/img/add_new_merge_request.png create mode 100644 doc/gitlab-basics/img/add_sshkey.png create mode 100644 doc/gitlab-basics/img/branch_info.png create mode 100644 doc/gitlab-basics/img/branch_name.png create mode 100644 doc/gitlab-basics/img/branches.png create mode 100644 doc/gitlab-basics/img/button-create-mr.png create mode 100644 doc/gitlab-basics/img/click-on-new-group.png create mode 100644 doc/gitlab-basics/img/commit_changes.png create mode 100644 doc/gitlab-basics/img/commit_message.png create mode 100644 doc/gitlab-basics/img/commits.png create mode 100644 doc/gitlab-basics/img/compare_branches.png create mode 100644 doc/gitlab-basics/img/create_file.png create mode 100644 doc/gitlab-basics/img/create_group.png create mode 100644 doc/gitlab-basics/img/edit_file.png create mode 100644 doc/gitlab-basics/img/file_located.png create mode 100644 doc/gitlab-basics/img/file_name.png create mode 100644 doc/gitlab-basics/img/find_file.png create mode 100644 doc/gitlab-basics/img/find_group.png create mode 100644 doc/gitlab-basics/img/fork.png create mode 100644 doc/gitlab-basics/img/group_info.png create mode 100644 doc/gitlab-basics/img/groups.png create mode 100644 doc/gitlab-basics/img/https.png create mode 100644 doc/gitlab-basics/img/image_file.png create mode 100644 doc/gitlab-basics/img/issue_title.png create mode 100644 doc/gitlab-basics/img/issues.png create mode 100644 doc/gitlab-basics/img/key.png create mode 100644 doc/gitlab-basics/img/merge_requests.png create mode 100644 doc/gitlab-basics/img/new_issue.png create mode 100644 doc/gitlab-basics/img/new_merge_request.png create mode 100644 doc/gitlab-basics/img/new_project.png create mode 100644 doc/gitlab-basics/img/newbranch.png create mode 100644 doc/gitlab-basics/img/paste_sshkey.png create mode 100644 doc/gitlab-basics/img/profile_settings.png create mode 100644 doc/gitlab-basics/img/project_info.png create mode 100644 doc/gitlab-basics/img/public_file_link.png create mode 100644 doc/gitlab-basics/img/select-group.png create mode 100644 doc/gitlab-basics/img/select-group2.png create mode 100644 doc/gitlab-basics/img/select_branch.png create mode 100644 doc/gitlab-basics/img/select_project.png create mode 100644 doc/gitlab-basics/img/settings.png create mode 100644 doc/gitlab-basics/img/shh_keys.png create mode 100644 doc/gitlab-basics/img/submit_new_issue.png create mode 100644 doc/gitlab-basics/img/title_description_mr.png create mode 100644 doc/gitlab-basics/img/white_space.png diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index ff10a98e8f5..86aff749ff1 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -6,22 +6,22 @@ To create a file in GitLab, sign in to GitLab. Select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) It's a good idea to [create a branch](create-branch.md), but it's not necessary. Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: -![Create a file](basicsimages/create_file.png) +![Create a file](img/create_file.png) Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : -![File name](basicsimages/file_name.png) +![File name](img/file_name.png) Add all the information that you'd like to include in your file: -![Add information](basicsimages/white_space.png) +![Add information](img/white_space.png) Add a commit message based on what you just added and then click on "commit changes": -![Commit changes](basicsimages/commit_changes.png) +![Commit changes](img/commit_changes.png) diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 236b4248ea2..082d1408aa2 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -6,31 +6,31 @@ To create a new Merge Request, sign in to GitLab. Go to the project where you'd like to merge your changes: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "Merge Requests" on the left side of your screen: -![Merge requests](basicsimages/merge_requests.png) +![Merge requests](img/merge_requests.png) Click on "+ new Merge Request" on the right side of the screen: -![New Merge Request](basicsimages/new_merge_request.png) +![New Merge Request](img/new_merge_request.png) Select a source branch or branch: -![Select a branch](basicsimages/select_branch.png) +![Select a branch](img/select_branch.png) Click on the "compare branches" button: -![Compare branches](basicsimages/compare_branches.png) +![Compare branches](img/compare_branches.png) Add a title and a description to your Merge Request: -![Add a title and description](basicsimages/title_description_mr.png) +![Add a title and description](img/title_description_mr.png) Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: -![Add a new merge request](basicsimages/add_new_merge_request.png) +![Add a new merge request](img/add_new_merge_request.png) Your Merge Request will be ready to be approved and published. @@ -39,4 +39,4 @@ Your Merge Request will be ready to be approved and published. After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. You may automatically create a Merge Request from your recently created branch when clicking on this button: -![Automatic MR button](basicsimages/button-create-mr.png) +![Automatic MR button](img/button-create-mr.png) diff --git a/doc/gitlab-basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png deleted file mode 100644 index e60992c4c6a..00000000000 Binary files a/doc/gitlab-basics/basicsimages/add_new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png deleted file mode 100644 index 89c86018629..00000000000 Binary files a/doc/gitlab-basics/basicsimages/add_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png deleted file mode 100644 index 2264f3c5bf2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branch_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png deleted file mode 100644 index 75fe8313611..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branch_name.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png deleted file mode 100644 index 8621bc05776..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branches.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png deleted file mode 100644 index b52ab148839..00000000000 Binary files a/doc/gitlab-basics/basicsimages/button-create-mr.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png deleted file mode 100644 index 6450deec6fc..00000000000 Binary files a/doc/gitlab-basics/basicsimages/click-on-new-group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png deleted file mode 100644 index a88809c5a3f..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commit_changes.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png deleted file mode 100644 index 4abe4517f98..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commit_message.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png deleted file mode 100644 index 2bfcaf75f01..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commits.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/compare_branches.png b/doc/gitlab-basics/basicsimages/compare_branches.png deleted file mode 100644 index 8a18453dd05..00000000000 Binary files a/doc/gitlab-basics/basicsimages/compare_branches.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png deleted file mode 100644 index 5ebe1b227dd..00000000000 Binary files a/doc/gitlab-basics/basicsimages/create_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png deleted file mode 100644 index 7ecc3baa990..00000000000 Binary files a/doc/gitlab-basics/basicsimages/create_group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png deleted file mode 100644 index 9d3e817d036..00000000000 Binary files a/doc/gitlab-basics/basicsimages/edit_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png deleted file mode 100644 index e357cb5c6ab..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_located.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png deleted file mode 100644 index 01639c77d0d..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_name.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png deleted file mode 100644 index 6f26d26ae18..00000000000 Binary files a/doc/gitlab-basics/basicsimages/find_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png deleted file mode 100644 index 1211510aae9..00000000000 Binary files a/doc/gitlab-basics/basicsimages/find_group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png deleted file mode 100644 index 13ff8345616..00000000000 Binary files a/doc/gitlab-basics/basicsimages/fork.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png deleted file mode 100644 index 2507d6c295b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/group_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png deleted file mode 100644 index ef3dca60cc8..00000000000 Binary files a/doc/gitlab-basics/basicsimages/groups.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png deleted file mode 100644 index e74dbc13f9a..00000000000 Binary files a/doc/gitlab-basics/basicsimages/https.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png deleted file mode 100644 index 7f304b8e1f2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/image_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png deleted file mode 100644 index 60a6f7973be..00000000000 Binary files a/doc/gitlab-basics/basicsimages/issue_title.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png deleted file mode 100644 index 14e9cdb64e1..00000000000 Binary files a/doc/gitlab-basics/basicsimages/issues.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png deleted file mode 100644 index 04400173ce8..00000000000 Binary files a/doc/gitlab-basics/basicsimages/key.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png deleted file mode 100644 index 570164df18b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/merge_requests.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_issue.png b/doc/gitlab-basics/basicsimages/new_issue.png deleted file mode 100644 index 94e7503dd8b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png deleted file mode 100644 index 842f5ebed74..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png deleted file mode 100644 index 421e8bc247b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_project.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png deleted file mode 100644 index d5fcf33c4ea..00000000000 Binary files a/doc/gitlab-basics/basicsimages/newbranch.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png deleted file mode 100644 index 578ebee4440..00000000000 Binary files a/doc/gitlab-basics/basicsimages/paste_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png deleted file mode 100644 index cb3f79f1879..00000000000 Binary files a/doc/gitlab-basics/basicsimages/profile_settings.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png deleted file mode 100644 index e1adb8d48c2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/project_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png deleted file mode 100644 index f60df6807f4..00000000000 Binary files a/doc/gitlab-basics/basicsimages/public_file_link.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png deleted file mode 100644 index 33b978dd899..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select-group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png deleted file mode 100644 index aee22c638db..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select-group2.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png deleted file mode 100644 index f72a3ffb57f..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select_branch.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png deleted file mode 100644 index 3bb832ea8d0..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select_project.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png deleted file mode 100644 index 78637013d9b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/settings.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png deleted file mode 100644 index c87f11a9d3d..00000000000 Binary files a/doc/gitlab-basics/basicsimages/shh_keys.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png deleted file mode 100644 index 78b854c8903..00000000000 Binary files a/doc/gitlab-basics/basicsimages/submit_new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png deleted file mode 100644 index c31d61ec336..00000000000 Binary files a/doc/gitlab-basics/basicsimages/title_description_mr.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png deleted file mode 100644 index eaa969bdcf4..00000000000 Binary files a/doc/gitlab-basics/basicsimages/white_space.png and /dev/null differ diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index addd3b6b6eb..253edc8a7e8 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -6,11 +6,11 @@ In Git, when you copy a project you say you "clone" it. To work on a git project When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). -![Copy the HTTPS or SSH](basicsimages/https.png) +![Copy the HTTPS or SSH](img/https.png) ## On the command line diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 7556b0f663e..8f078c7692f 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -8,19 +8,19 @@ To add changes to your GitLab project, you should create a branch. You can do it To create a new branch in GitLab, sign in and then select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "commits" on the menu on the left side of your screen: -![Commits](basicsimages/commits.png) +![Commits](img/commits.png) Click on the "branches" tab: -![Branches](basicsimages/branches.png) +![Branches](img/branches.png) Click on the "new branch" button on the right side of the screen: -![New branch](basicsimages/newbranch.png) +![New branch](img/newbranch.png) Fill out the information required: @@ -30,10 +30,10 @@ Fill out the information required: 1. Click on the button "create branch" -![Branch info](basicsimages/branch_info.png) +![Branch info](img/branch_info.png) ### Note: You will be able to find and select the name of your branch in the white box next to a project's name: -![Branch name](basicsimages/branch_name.png) +![Branch name](img/branch_name.png) diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index f80ae62e442..05497279959 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -12,11 +12,11 @@ Sign in to [GitLab.com](https://gitlab.com). When you are on your Dashboard, click on "Groups" on the left menu of your screen: -![Go to groups](basicsimages/select-group2.png) +![Go to groups](img/select-group2.png) Click on "New group" on the top right side of your screen: -![New group](basicsimages/click-on-new-group.png) +![New group](img/click-on-new-group.png) Fill out the information required: @@ -28,7 +28,7 @@ Fill out the information required: 1. Click on "create group" -![Group information](basicsimages/group_info.png) +![Group information](img/group_info.png) ## Add a project to a group @@ -36,8 +36,8 @@ There are 2 different ways to add a new project to a group: * Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) -![New project](basicsimages/new_project.png) +![New project](img/new_project.png) * When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen -![Create a group](basicsimages/create_group.png) +![Create a group](img/create_group.png) diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index da9a165b8f5..e11c86cafa0 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -6,22 +6,22 @@ To create an Issue, sign in to GitLab. Go to the project where you'd like to create the Issue: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "Issues" on the left side of your screen: -![Issues](basicsimages/issues.png) +![Issues](img/issues.png) Click on the "+ new issue" button on the right side of your screen: -![New issue](basicsimages/new_issue.png) +![New issue](img/new_issue.png) Add a title and a description to your issue: -![Issue title and description](basicsimages/issue_title.png) +![Issue title and description](img/issue_title.png) You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": -![Submit new issue](basicsimages/submit_new_issue.png) +![Submit new issue](img/submit_new_issue.png) Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index f737dffc024..c67e5908189 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -4,7 +4,7 @@ To create a new project, sign in to GitLab. Go to your Dashboard and click on "new project" on the right side of your screen. -![Create a project](basicsimages/new_project.png) +![Create a project](img/new_project.png) Fill out the required information: @@ -18,4 +18,4 @@ Fill out the required information: 1. Click on "create project" -!![Project information](basicsimages/project_info.png) +!![Project information](img/project_info.png) diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index f31c353f2cf..50ac9c36988 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -12,15 +12,15 @@ After you confirm, go to GitLab and sign in to your account. On the left side menu, click on "profile settings" and then click on "SSH Keys": -![SSH Keys](basicsimages/shh_keys.png) +![SSH Keys](img/shh_keys.png) Then click on the green button "Add SSH Key": -![Add SSH Key](basicsimages/add_sshkey.png) +![Add SSH Key](img/add_sshkey.png) There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: -![Paste SSH Key](basicsimages/paste_sshkey.png) +![Paste SSH Key](img/paste_sshkey.png) ## To generate an SSH Key on your command line @@ -28,6 +28,6 @@ Go to your [command line](start-using-git.md) and follow the [instructions](../s Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. -![Paste SSH Key](basicsimages/key.png) +![Paste SSH Key](img/key.png) Now, you'll be able to use Git over SSH, instead of Git over HTTP. diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 5f8b81ea919..7232fc2c1ae 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -10,10 +10,10 @@ Sign in to GitLab. Select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on the "fork" button on the right side of your screen: -![Fork](basicsimages/fork.png) +![Fork](img/fork.png) Click on the user or group to where you'd like to add the forked project. diff --git a/doc/gitlab-basics/img/add_new_merge_request.png b/doc/gitlab-basics/img/add_new_merge_request.png new file mode 100644 index 00000000000..e60992c4c6a Binary files /dev/null and b/doc/gitlab-basics/img/add_new_merge_request.png differ diff --git a/doc/gitlab-basics/img/add_sshkey.png b/doc/gitlab-basics/img/add_sshkey.png new file mode 100644 index 00000000000..89c86018629 Binary files /dev/null and b/doc/gitlab-basics/img/add_sshkey.png differ diff --git a/doc/gitlab-basics/img/branch_info.png b/doc/gitlab-basics/img/branch_info.png new file mode 100644 index 00000000000..2264f3c5bf2 Binary files /dev/null and b/doc/gitlab-basics/img/branch_info.png differ diff --git a/doc/gitlab-basics/img/branch_name.png b/doc/gitlab-basics/img/branch_name.png new file mode 100644 index 00000000000..75fe8313611 Binary files /dev/null and b/doc/gitlab-basics/img/branch_name.png differ diff --git a/doc/gitlab-basics/img/branches.png b/doc/gitlab-basics/img/branches.png new file mode 100644 index 00000000000..8621bc05776 Binary files /dev/null and b/doc/gitlab-basics/img/branches.png differ diff --git a/doc/gitlab-basics/img/button-create-mr.png b/doc/gitlab-basics/img/button-create-mr.png new file mode 100644 index 00000000000..b52ab148839 Binary files /dev/null and b/doc/gitlab-basics/img/button-create-mr.png differ diff --git a/doc/gitlab-basics/img/click-on-new-group.png b/doc/gitlab-basics/img/click-on-new-group.png new file mode 100644 index 00000000000..6450deec6fc Binary files /dev/null and b/doc/gitlab-basics/img/click-on-new-group.png differ diff --git a/doc/gitlab-basics/img/commit_changes.png b/doc/gitlab-basics/img/commit_changes.png new file mode 100644 index 00000000000..a88809c5a3f Binary files /dev/null and b/doc/gitlab-basics/img/commit_changes.png differ diff --git a/doc/gitlab-basics/img/commit_message.png b/doc/gitlab-basics/img/commit_message.png new file mode 100644 index 00000000000..4abe4517f98 Binary files /dev/null and b/doc/gitlab-basics/img/commit_message.png differ diff --git a/doc/gitlab-basics/img/commits.png b/doc/gitlab-basics/img/commits.png new file mode 100644 index 00000000000..2bfcaf75f01 Binary files /dev/null and b/doc/gitlab-basics/img/commits.png differ diff --git a/doc/gitlab-basics/img/compare_branches.png b/doc/gitlab-basics/img/compare_branches.png new file mode 100644 index 00000000000..8a18453dd05 Binary files /dev/null and b/doc/gitlab-basics/img/compare_branches.png differ diff --git a/doc/gitlab-basics/img/create_file.png b/doc/gitlab-basics/img/create_file.png new file mode 100644 index 00000000000..5ebe1b227dd Binary files /dev/null and b/doc/gitlab-basics/img/create_file.png differ diff --git a/doc/gitlab-basics/img/create_group.png b/doc/gitlab-basics/img/create_group.png new file mode 100644 index 00000000000..7ecc3baa990 Binary files /dev/null and b/doc/gitlab-basics/img/create_group.png differ diff --git a/doc/gitlab-basics/img/edit_file.png b/doc/gitlab-basics/img/edit_file.png new file mode 100644 index 00000000000..9d3e817d036 Binary files /dev/null and b/doc/gitlab-basics/img/edit_file.png differ diff --git a/doc/gitlab-basics/img/file_located.png b/doc/gitlab-basics/img/file_located.png new file mode 100644 index 00000000000..e357cb5c6ab Binary files /dev/null and b/doc/gitlab-basics/img/file_located.png differ diff --git a/doc/gitlab-basics/img/file_name.png b/doc/gitlab-basics/img/file_name.png new file mode 100644 index 00000000000..01639c77d0d Binary files /dev/null and b/doc/gitlab-basics/img/file_name.png differ diff --git a/doc/gitlab-basics/img/find_file.png b/doc/gitlab-basics/img/find_file.png new file mode 100644 index 00000000000..6f26d26ae18 Binary files /dev/null and b/doc/gitlab-basics/img/find_file.png differ diff --git a/doc/gitlab-basics/img/find_group.png b/doc/gitlab-basics/img/find_group.png new file mode 100644 index 00000000000..1211510aae9 Binary files /dev/null and b/doc/gitlab-basics/img/find_group.png differ diff --git a/doc/gitlab-basics/img/fork.png b/doc/gitlab-basics/img/fork.png new file mode 100644 index 00000000000..13ff8345616 Binary files /dev/null and b/doc/gitlab-basics/img/fork.png differ diff --git a/doc/gitlab-basics/img/group_info.png b/doc/gitlab-basics/img/group_info.png new file mode 100644 index 00000000000..2507d6c295b Binary files /dev/null and b/doc/gitlab-basics/img/group_info.png differ diff --git a/doc/gitlab-basics/img/groups.png b/doc/gitlab-basics/img/groups.png new file mode 100644 index 00000000000..ef3dca60cc8 Binary files /dev/null and b/doc/gitlab-basics/img/groups.png differ diff --git a/doc/gitlab-basics/img/https.png b/doc/gitlab-basics/img/https.png new file mode 100644 index 00000000000..e74dbc13f9a Binary files /dev/null and b/doc/gitlab-basics/img/https.png differ diff --git a/doc/gitlab-basics/img/image_file.png b/doc/gitlab-basics/img/image_file.png new file mode 100644 index 00000000000..7f304b8e1f2 Binary files /dev/null and b/doc/gitlab-basics/img/image_file.png differ diff --git a/doc/gitlab-basics/img/issue_title.png b/doc/gitlab-basics/img/issue_title.png new file mode 100644 index 00000000000..60a6f7973be Binary files /dev/null and b/doc/gitlab-basics/img/issue_title.png differ diff --git a/doc/gitlab-basics/img/issues.png b/doc/gitlab-basics/img/issues.png new file mode 100644 index 00000000000..14e9cdb64e1 Binary files /dev/null and b/doc/gitlab-basics/img/issues.png differ diff --git a/doc/gitlab-basics/img/key.png b/doc/gitlab-basics/img/key.png new file mode 100644 index 00000000000..04400173ce8 Binary files /dev/null and b/doc/gitlab-basics/img/key.png differ diff --git a/doc/gitlab-basics/img/merge_requests.png b/doc/gitlab-basics/img/merge_requests.png new file mode 100644 index 00000000000..570164df18b Binary files /dev/null and b/doc/gitlab-basics/img/merge_requests.png differ diff --git a/doc/gitlab-basics/img/new_issue.png b/doc/gitlab-basics/img/new_issue.png new file mode 100644 index 00000000000..94e7503dd8b Binary files /dev/null and b/doc/gitlab-basics/img/new_issue.png differ diff --git a/doc/gitlab-basics/img/new_merge_request.png b/doc/gitlab-basics/img/new_merge_request.png new file mode 100644 index 00000000000..842f5ebed74 Binary files /dev/null and b/doc/gitlab-basics/img/new_merge_request.png differ diff --git a/doc/gitlab-basics/img/new_project.png b/doc/gitlab-basics/img/new_project.png new file mode 100644 index 00000000000..421e8bc247b Binary files /dev/null and b/doc/gitlab-basics/img/new_project.png differ diff --git a/doc/gitlab-basics/img/newbranch.png b/doc/gitlab-basics/img/newbranch.png new file mode 100644 index 00000000000..d5fcf33c4ea Binary files /dev/null and b/doc/gitlab-basics/img/newbranch.png differ diff --git a/doc/gitlab-basics/img/paste_sshkey.png b/doc/gitlab-basics/img/paste_sshkey.png new file mode 100644 index 00000000000..578ebee4440 Binary files /dev/null and b/doc/gitlab-basics/img/paste_sshkey.png differ diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png new file mode 100644 index 00000000000..cb3f79f1879 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings.png differ diff --git a/doc/gitlab-basics/img/project_info.png b/doc/gitlab-basics/img/project_info.png new file mode 100644 index 00000000000..e1adb8d48c2 Binary files /dev/null and b/doc/gitlab-basics/img/project_info.png differ diff --git a/doc/gitlab-basics/img/public_file_link.png b/doc/gitlab-basics/img/public_file_link.png new file mode 100644 index 00000000000..f60df6807f4 Binary files /dev/null and b/doc/gitlab-basics/img/public_file_link.png differ diff --git a/doc/gitlab-basics/img/select-group.png b/doc/gitlab-basics/img/select-group.png new file mode 100644 index 00000000000..33b978dd899 Binary files /dev/null and b/doc/gitlab-basics/img/select-group.png differ diff --git a/doc/gitlab-basics/img/select-group2.png b/doc/gitlab-basics/img/select-group2.png new file mode 100644 index 00000000000..aee22c638db Binary files /dev/null and b/doc/gitlab-basics/img/select-group2.png differ diff --git a/doc/gitlab-basics/img/select_branch.png b/doc/gitlab-basics/img/select_branch.png new file mode 100644 index 00000000000..f72a3ffb57f Binary files /dev/null and b/doc/gitlab-basics/img/select_branch.png differ diff --git a/doc/gitlab-basics/img/select_project.png b/doc/gitlab-basics/img/select_project.png new file mode 100644 index 00000000000..3bb832ea8d0 Binary files /dev/null and b/doc/gitlab-basics/img/select_project.png differ diff --git a/doc/gitlab-basics/img/settings.png b/doc/gitlab-basics/img/settings.png new file mode 100644 index 00000000000..78637013d9b Binary files /dev/null and b/doc/gitlab-basics/img/settings.png differ diff --git a/doc/gitlab-basics/img/shh_keys.png b/doc/gitlab-basics/img/shh_keys.png new file mode 100644 index 00000000000..c87f11a9d3d Binary files /dev/null and b/doc/gitlab-basics/img/shh_keys.png differ diff --git a/doc/gitlab-basics/img/submit_new_issue.png b/doc/gitlab-basics/img/submit_new_issue.png new file mode 100644 index 00000000000..78b854c8903 Binary files /dev/null and b/doc/gitlab-basics/img/submit_new_issue.png differ diff --git a/doc/gitlab-basics/img/title_description_mr.png b/doc/gitlab-basics/img/title_description_mr.png new file mode 100644 index 00000000000..c31d61ec336 Binary files /dev/null and b/doc/gitlab-basics/img/title_description_mr.png differ diff --git a/doc/gitlab-basics/img/white_space.png b/doc/gitlab-basics/img/white_space.png new file mode 100644 index 00000000000..eaa969bdcf4 Binary files /dev/null and b/doc/gitlab-basics/img/white_space.png differ -- cgit v1.2.1 From d4fab17d7c8c2b233248295755a6277fdee09c9f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 7 Oct 2016 22:48:23 -0700 Subject: Fix Error 500 when viewing old merge requests with bad diff data Customers running old versions of GitLab may have MergeRequestDiffs with the text ["--broken diff"] due to text generated by gitlab_git 1.0.3. To avoid the Error 500, verify that each element is a type that gitlab_git will accept before attempting to create a DiffCollection. Closes #20776 --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 14 +++++++++++++- spec/models/merge_request_diff_spec.rb | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..dfb953fabd9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).to be_empty + end + end + context 'when the raw diffs exist' do it 'returns the diffs' do expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) -- cgit v1.2.1 From f8df2bc61f205da36b50961928509ca087e7b290 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 22:07:17 +0200 Subject: Refactor SSH key addition in GitLab basics --- doc/gitlab-basics/create-your-ssh-keys.md | 38 ++++++++++++--------- doc/gitlab-basics/img/add_sshkey.png | Bin 1394 -> 0 bytes doc/gitlab-basics/img/key.png | Bin 1177 -> 0 bytes doc/gitlab-basics/img/paste_sshkey.png | Bin 7699 -> 0 bytes doc/gitlab-basics/img/profile_settings.png | Bin 1101 -> 5975 bytes .../img/profile_settings_ssh_keys.png | Bin 0 -> 42977 bytes .../img/profile_settings_ssh_keys_paste_pub.png | Bin 0 -> 37486 bytes .../img/profile_settings_ssh_keys_single_key.png | Bin 0 -> 18498 bytes .../img/profile_settings_ssh_keys_title.png | Bin 0 -> 2362 bytes doc/gitlab-basics/img/shh_keys.png | Bin 4782 -> 0 bytes 10 files changed, 21 insertions(+), 17 deletions(-) delete mode 100644 doc/gitlab-basics/img/add_sshkey.png delete mode 100644 doc/gitlab-basics/img/key.png delete mode 100644 doc/gitlab-basics/img/paste_sshkey.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_title.png delete mode 100644 doc/gitlab-basics/img/shh_keys.png diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index 50ac9c36988..b6ebe374de3 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -1,33 +1,37 @@ # How to create your SSH Keys -You need to connect your computer to your GitLab account through SSH Keys. They are unique for every computer that you link your GitLab account with. +1. The first thing you need to do is go to your [command line](start-using-git.md) + and follow the [instructions](../ssh/README.md) to generate your SSH key pair. -## Generate your SSH Key +1. Once you do that, login to GitLab with your credentials. +1. On the upper right corner, click on your avatar and go to your **Profile settings**. -Create an account on GitLab. Sign up and check your email for your confirmation link. + ![Profile settings dropdown](img/profile_settings.png) -After you confirm, go to GitLab and sign in to your account. +1. Navigate to the **SSH keys** tab. -## Add your SSH Key + ![SSH Keys](img/profile_settings_ssh_keys.png) -On the left side menu, click on "profile settings" and then click on "SSH Keys": +3. Paste your **public** key that you generated in the first step in the 'Key' + box. -![SSH Keys](img/shh_keys.png) + ![Paste SSH public key](img/profile_settings_ssh_keys_paste_pub.png) -Then click on the green button "Add SSH Key": +1. Optionally, give it a descriptive title so that you can recognize it in the + event you add multiple keys. -![Add SSH Key](img/add_sshkey.png) + ![SSH key title](img/profile_settings_ssh_keys_title.png) -There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: +1. Finally, click on **Add key** to add it to GitLab. You will be able to see + its fingerprint, its title and creation date. -![Paste SSH Key](img/paste_sshkey.png) + ![SSH key single page](img/profile_settings_ssh_keys_single_key.png) -## To generate an SSH Key on your command line -Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it. +>**Note:** +Once you add a key, you cannot edit it, only remove it. In case the paste +didn't work, you will have to remove the offending key and re-add it. -Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. +--- -![Paste SSH Key](img/key.png) - -Now, you'll be able to use Git over SSH, instead of Git over HTTP. +Congratulations! You are now ready to use Git over SSH, instead of Git over HTTP! diff --git a/doc/gitlab-basics/img/add_sshkey.png b/doc/gitlab-basics/img/add_sshkey.png deleted file mode 100644 index 89c86018629..00000000000 Binary files a/doc/gitlab-basics/img/add_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/img/key.png b/doc/gitlab-basics/img/key.png deleted file mode 100644 index 04400173ce8..00000000000 Binary files a/doc/gitlab-basics/img/key.png and /dev/null differ diff --git a/doc/gitlab-basics/img/paste_sshkey.png b/doc/gitlab-basics/img/paste_sshkey.png deleted file mode 100644 index 578ebee4440..00000000000 Binary files a/doc/gitlab-basics/img/paste_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png index cb3f79f1879..f0abd478849 100644 Binary files a/doc/gitlab-basics/img/profile_settings.png and b/doc/gitlab-basics/img/profile_settings.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys.png b/doc/gitlab-basics/img/profile_settings_ssh_keys.png new file mode 100644 index 00000000000..2c9a42fe10c Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png new file mode 100644 index 00000000000..cd7add6937f Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png new file mode 100644 index 00000000000..095beb02be8 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png new file mode 100644 index 00000000000..4b998a7f948 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png differ diff --git a/doc/gitlab-basics/img/shh_keys.png b/doc/gitlab-basics/img/shh_keys.png deleted file mode 100644 index c87f11a9d3d..00000000000 Binary files a/doc/gitlab-basics/img/shh_keys.png and /dev/null differ -- cgit v1.2.1 From 6de2990c998169e165c0bc9b0a7e0ab9099048e4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 22:18:06 +0200 Subject: Use new image for gitlab-basics/command-line-commands.md [ci skip] --- doc/gitlab-basics/README.md | 2 +- doc/gitlab-basics/command-line-commands.md | 26 ++++++++++++++++++++------ doc/gitlab-basics/img/https.png | Bin 2822 -> 0 bytes doc/gitlab-basics/img/project_clone_url.png | Bin 0 -> 40490 bytes 4 files changed, 21 insertions(+), 7 deletions(-) delete mode 100644 doc/gitlab-basics/img/https.png create mode 100644 doc/gitlab-basics/img/project_clone_url.png diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 3aa83975ace..38843f36b2e 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,9 +2,9 @@ Step-by-step guides on the basics of working with Git and GitLab. +- [Command Line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) -- [Command Line basics](command-line-commands.md) - [Create a project](create-project.md) - [Create a group](create-group.md) - [Create a branch](create-branch.md) diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index 253edc8a7e8..3b075ff5fc0 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -4,18 +4,21 @@ In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab. -When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. +When you are on your Dashboard, click on the project that you'd like to clone. +To work in the project, you can copy a link to the Git repository through a SSH +or a HTTPS protocol. SSH is easier to use after it's been +[setup](create-your-ssh-keys.md). While you are at the **Project** tab, select +HTTPS or SSH from the dropdown menu and copy the link using the 'Copy to clipboard' +button (you'll have to paste it on your shell in the next step). -![Select a project](img/select_project.png) - -To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). - -![Copy the HTTPS or SSH](img/https.png) +![Copy the HTTPS or SSH](img/project_clone_url.png) ## On the command line ### Clone your project + Go to your computer's shell and type the following command: + ``` git clone PASTE HTTPS OR SSH HERE ``` @@ -23,26 +26,31 @@ git clone PASTE HTTPS OR SSH HERE A clone of the project will be created in your computer. ### Go into a project, directory or file to work in it + ``` cd NAME-OF-PROJECT-OR-FILE ``` ### Go back one directory or file + ``` cd ../ ``` ### View what’s in the directory that you are in + ``` ls ``` ### Create a directory + ``` mkdir NAME-OF-YOUR-DIRECTORY ``` ### Create a README.md or file in directory + ``` touch README.md nano README.md @@ -53,27 +61,33 @@ nano README.md ``` ### Remove a file + ``` rm NAME-OF-FILE ``` ### Remove a directory and all of its contents + ``` rm -rf NAME-OF-DIRECTORY ``` ### View history in the command line + ``` history ``` ### Carry out commands for which the account you are using lacks authority + You will be asked for an administrator’s password. + ``` sudo ``` ### Tell where you are + ``` pwd ``` diff --git a/doc/gitlab-basics/img/https.png b/doc/gitlab-basics/img/https.png deleted file mode 100644 index e74dbc13f9a..00000000000 Binary files a/doc/gitlab-basics/img/https.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_clone_url.png b/doc/gitlab-basics/img/project_clone_url.png new file mode 100644 index 00000000000..eed430e1036 Binary files /dev/null and b/doc/gitlab-basics/img/project_clone_url.png differ -- cgit v1.2.1 From 0a79e5cd44e1d83f0cd18ffb538da25e6b2deae3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:21:08 -0500 Subject: Replace generic table with --- app/assets/stylesheets/pages/environments.scss | 4 ++-- app/assets/stylesheets/pages/merge_requests.scss | 4 ++-- app/assets/stylesheets/pages/pipelines.scss | 4 ++-- app/views/admin/runners/show.html.haml | 2 +- app/views/projects/builds/_table.html.haml | 2 +- app/views/projects/commit/_pipeline.html.haml | 2 +- app/views/projects/commit/_pipelines_list.haml | 2 +- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/environments/show.html.haml | 2 +- app/views/projects/pipelines/index.html.haml | 2 +- spec/features/merge_requests/created_from_fork_spec.rb | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 3f19e920166..820cc0fc991 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -52,13 +52,13 @@ } } -.table.builds.environments { +.table.ci-table.environments { .icon-container { width: 20px; text-align: center; } - + .branch-commit { .commit-id { margin-right: 0; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 043f3f3afe1..9676c2d1026 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -264,7 +264,7 @@ line-height: 31px; } -.builds { +.ci-table { .table-holder { overflow-x: auto; } @@ -357,7 +357,7 @@ } .table-holder { - .builds { + .ci-table { th { background-color: $white-light; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..bad4eeaa2cf 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -20,7 +20,7 @@ margin: 4px; } - .table.builds { + .table.ci-table { min-width: 1200px; .branch-commit { @@ -44,7 +44,7 @@ overflow: auto; } -.table.builds { +.table.ci-table { min-width: 900px; &.pipeline { diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index a5e82e55cc1..10fea1996aa 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -71,7 +71,7 @@ .col-md-6 %h4 Recent builds served by this Runner - %table.table.builds.runner-builds + %table.table.ci-table.runner-builds %thead %tr %th Build diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index f3747ba2a21..9b91ae688ae 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -5,7 +5,7 @@ .nothing-here-block No builds to show - else .table-holder - %table.table.builds + %table.table.ci-table %thead %tr %th Status diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..b5e5e8db9eb 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -55,7 +55,7 @@ \.gitlab-ci.yml not found in this commit .table-holder.pipeline-holder - %table.table.builds.pipeline + %table.table.ci-table.pipeline %thead %tr %th Status diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 998812793a2..640651e93f5 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -4,7 +4,7 @@ .nothing-here-block No pipelines to show - else .table-holder - %table.table.builds + %table.table.ci-table %tbody %th Status %th Pipeline diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index ab801409722..721ba156334 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -24,7 +24,7 @@ New environment - else .table-holder - %table.table.builds.environments + %table.table.ci-table.environments %tbody %th Environment %th Last Deployment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 7a8d196cf4e..90c59223a35 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -24,7 +24,7 @@ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" - else .table-holder - %table.table.builds.environments + %table.table.ci-table.environments %thead %tr %th ID diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 2d1df095bfa..9eeef5f57b4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -43,7 +43,7 @@ .nothing-here-block No pipelines to show - else .table-holder - %table.table.builds + %table.table.ci-table %thead %th.col-xs-1.col-sm-1 Status %th.col-xs-2.col-sm-4 Pipeline diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb index 4d5d4aa121a..a506624b30d 100644 --- a/spec/features/merge_requests/created_from_fork_spec.rb +++ b/spec/features/merge_requests/created_from_fork_spec.rb @@ -45,7 +45,7 @@ feature 'Merge request created from fork' do page.within('.merge-request-tabs') { click_link 'Builds' } wait_for_ajax - page.within('table.builds') do + page.within('table.ci-table') do expect(page).to have_content 'rspec' expect(page).to have_content 'spinach' end -- cgit v1.2.1 From 0012b74e01429b98d2c42f68f50dc6ce6ad2b70f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:31:24 -0500 Subject: Set height on build page rows --- app/assets/stylesheets/pages/pipelines.scss | 7 +++++++ app/views/projects/builds/_table.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index bad4eeaa2cf..149c29858fb 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -51,6 +51,13 @@ min-width: 650px; } + &.builds-page { + + tr { + height: 71px; + } + } + tr { th { padding: 16px 8px; diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index 9b91ae688ae..36294c89fa8 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -5,7 +5,7 @@ .nothing-here-block No builds to show - else .table-holder - %table.table.ci-table + %table.table.ci-table.builds-page %thead %tr %th Status -- cgit v1.2.1 From 64609ba84845969ef084aee3058ce2fbea582a3f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 10 Oct 2016 16:43:40 -0500 Subject: Set height on MR builds rows --- app/assets/stylesheets/pages/merge_requests.scss | 6 ----- app/assets/stylesheets/pages/pipelines.scss | 30 +++++++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 9676c2d1026..71cf17fdb41 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -264,12 +264,6 @@ line-height: 31px; } -.ci-table { - .table-holder { - overflow-x: auto; - } -} - .panel-new-merge-request { .panel-heading { padding: 5px 10px; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 149c29858fb..84af2a01997 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -574,19 +574,31 @@ } } -.pipelines.tab-pane { +.tab-pane { - .content-list.pipelines { - overflow: auto; - } + &.pipelines { - .stage { - max-width: 100px; - width: 100px; + .content-list.pipelines { + overflow: auto; + } + + .stage { + max-width: 100px; + width: 100px; + } + + .pipeline-actions { + min-width: initial; + } } - .pipeline-actions { - min-width: initial; + &.builds { + + .ci-table { + tr { + height: 71px; + } + } } } -- cgit v1.2.1 From ac93758a25e307573dcf79a76318f5b45aec5d07 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 10 Oct 2016 16:52:48 -0700 Subject: Bump mail_room to v0.8.1 to fix thread cleanup issue Closes #20273 --- CHANGELOG | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..49804b10c0b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page + - Bump mail_room to v0.8.1 to fix thread cleanup issue - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call diff --git a/Gemfile.lock b/Gemfile.lock index b98c3acf948..ccab330993a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) -- cgit v1.2.1 From f19766a3c2223b05e1f6de1746a44e7173387988 Mon Sep 17 00:00:00 2001 From: Mitchell Hentges Date: Thu, 22 Sep 2016 11:15:00 -0700 Subject: Ensure that whitespace doesn't case adding members to fail --- CHANGELOG | 1 + app/assets/javascripts/users_select.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc06303aecf..d94305a66d1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page + - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..d966277a8b2 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -261,10 +261,11 @@ } } if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { + var trimmed = query.term.trim(); emailUser = { name: "Invite \"" + query.term + "\"", - username: query.term, - id: query.term + username: trimmed, + id: trimmed }; data.results.unshift(emailUser); } -- cgit v1.2.1 From ef696f592fd1eed038c789379837554237ff5974 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 11 Oct 2016 02:58:26 +0100 Subject: Add `robots.txt` to the list of reserved namespaces --- app/validators/namespace_validator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 4dc3b2ab9a0..2821ecf0a88 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -24,6 +24,7 @@ class NamespaceValidator < ActiveModel::EachValidator projects public repository + robots.txt s search services -- cgit v1.2.1 From c003c894223f76635783b0c878006bff5800f2fe Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 06:41:40 +0200 Subject: Add new images for GitLab basics "create project" --- doc/gitlab-basics/create-project.md | 27 ++++++++++++--------- .../img/create_new_project_button.png | Bin 0 -> 10050 bytes doc/gitlab-basics/img/create_new_project_info.png | Bin 0 -> 49451 bytes doc/gitlab-basics/img/new_project.png | Bin 2234 -> 0 bytes doc/gitlab-basics/img/project_info.png | Bin 21041 -> 0 bytes 5 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 doc/gitlab-basics/img/create_new_project_button.png create mode 100644 doc/gitlab-basics/img/create_new_project_info.png delete mode 100644 doc/gitlab-basics/img/new_project.png delete mode 100644 doc/gitlab-basics/img/project_info.png diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index c67e5908189..3f45a631b3a 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -1,21 +1,24 @@ # How to create a project in GitLab -To create a new project, sign in to GitLab. +There are two ways to create a new project in GitLab. -Go to your Dashboard and click on "new project" on the right side of your screen. +1. While in your dashboard, you can create a new project using the **New project** + green button or you can use the cross icon in the upper right corner next to + your avatar which is always visible. -![Create a project](img/new_project.png) + ![Create a project](img/create_new_project_button.png) -Fill out the required information: +1. From there you can see several options. -1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores) + ![Project information](img/create_new_project_info.png) -1. Your project's description +1. Fill out the information: -1. Select a [visibility level](https://gitlab.com/help/public_access/public_access) + 1. "Project name" is the name of your project (you can't use spaces, but you + can use hyphens or underscores). + 1. The "Project description" is optional and will be shown in your project's + dashboard so others can briefly understand what your project is about. + 1. Select a [visibility level](../public_access/public_access.md). + 1. You can also [import your existing projects](../workflow/importing/README.md). -1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html) - -1. Click on "create project" - -!![Project information](img/project_info.png) +1. Finally, click **Create project**. diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png new file mode 100644 index 00000000000..e7c794d943f Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_button.png differ diff --git a/doc/gitlab-basics/img/create_new_project_info.png b/doc/gitlab-basics/img/create_new_project_info.png new file mode 100644 index 00000000000..16d56f0707f Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_info.png differ diff --git a/doc/gitlab-basics/img/new_project.png b/doc/gitlab-basics/img/new_project.png deleted file mode 100644 index 421e8bc247b..00000000000 Binary files a/doc/gitlab-basics/img/new_project.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_info.png b/doc/gitlab-basics/img/project_info.png deleted file mode 100644 index e1adb8d48c2..00000000000 Binary files a/doc/gitlab-basics/img/project_info.png and /dev/null differ -- cgit v1.2.1 From ec102fe8f1cce1a5b971807232550e3b1e0f9ae6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:11:55 +0200 Subject: New images for GitLab basics "Create group" docs --- doc/gitlab-basics/create-group.md | 51 +++++++++++---------- doc/gitlab-basics/img/click-on-new-group.png | Bin 1957 -> 0 bytes doc/gitlab-basics/img/create_group.png | Bin 3184 -> 0 bytes doc/gitlab-basics/img/create_new_group_info.png | Bin 0 -> 53103 bytes doc/gitlab-basics/img/create_new_group_sidebar.png | Bin 0 -> 5396 bytes .../img/create_new_project_from_group.png | Bin 0 -> 6545 bytes doc/gitlab-basics/img/group_info.png | Bin 15479 -> 0 bytes doc/gitlab-basics/img/select-group2.png | Bin 5040 -> 0 bytes doc/gitlab-basics/img/select_group_dropdown.png | Bin 0 -> 8038 bytes 9 files changed, 28 insertions(+), 23 deletions(-) delete mode 100644 doc/gitlab-basics/img/click-on-new-group.png delete mode 100644 doc/gitlab-basics/img/create_group.png create mode 100644 doc/gitlab-basics/img/create_new_group_info.png create mode 100644 doc/gitlab-basics/img/create_new_group_sidebar.png create mode 100644 doc/gitlab-basics/img/create_new_project_from_group.png delete mode 100644 doc/gitlab-basics/img/group_info.png delete mode 100644 doc/gitlab-basics/img/select-group2.png create mode 100644 doc/gitlab-basics/img/select_group_dropdown.png diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index 05497279959..64274ccd5eb 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,43 +1,48 @@ # How to create a group in GitLab -## Create a group - Your projects in GitLab can be organized in 2 different ways: -under your own namespace for single projects, such as ´your-name/project-1'; or under groups. -If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects. - -To create a group, follow the instructions below: +under your own namespace for single projects, such as `your-name/project-1` or +under groups. -Sign in to [GitLab.com](https://gitlab.com). +If you organize your projects under a group, it works like a folder. You can +manage your group members' permissions and access to the projects. -When you are on your Dashboard, click on "Groups" on the left menu of your screen: +--- -![Go to groups](img/select-group2.png) +To create a group: -Click on "New group" on the top right side of your screen: +1. Expand the left sidebar by clicking the three bars at the upper left corner + and then navigate to **Groups**. -![New group](img/click-on-new-group.png) + ![Go to groups](img/create_new_group_sidebar.png) -Fill out the information required: +1. Once in your groups dashboard, click on **New group**. -1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores) + ![Create new group information](img/create_new_group_info.png) -1. Add details or a group description +1. Fill out the needed information: -1. You can choose a group avatar if you'd like + 1. Set the "Group path" which will be the namespace under which your projects + will be hosted (path can contain only letters, digits, underscores, dashes + and dots; it cannot start with dashes or end in dot). + 1. Optionally, you can add a description so that others can briefly understand + what this group is about. + 1. Optionally, choose and avatar for your project. + 1. Choose the [visibility level](../public_access/public_access.md). -1. Click on "create group" +1. Finally, click the **Create group** button. -![Group information](img/group_info.png) - -## Add a project to a group +## Add a new project to a group There are 2 different ways to add a new project to a group: -* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) +- Select a group and then click on the **New project** button. + + ![New project](img/create_new_project_from_group.png) -![New project](img/new_project.png) + You can then continue on [creating a project](create-project.md). -* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen +- While you are [creating a project](create-project.md), select a group namespace + you've already created from the dropdown menu. -![Create a group](img/create_group.png) + ![Select group](img/select_group_dropdown.png) diff --git a/doc/gitlab-basics/img/click-on-new-group.png b/doc/gitlab-basics/img/click-on-new-group.png deleted file mode 100644 index 6450deec6fc..00000000000 Binary files a/doc/gitlab-basics/img/click-on-new-group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_group.png b/doc/gitlab-basics/img/create_group.png deleted file mode 100644 index 7ecc3baa990..00000000000 Binary files a/doc/gitlab-basics/img/create_group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/gitlab-basics/img/create_new_group_info.png new file mode 100644 index 00000000000..c8eddfd1bbb Binary files /dev/null and b/doc/gitlab-basics/img/create_new_group_info.png differ diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png new file mode 100644 index 00000000000..28017ee02e0 Binary files /dev/null and b/doc/gitlab-basics/img/create_new_group_sidebar.png differ diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/gitlab-basics/img/create_new_project_from_group.png new file mode 100644 index 00000000000..6d41d17f9ca Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_from_group.png differ diff --git a/doc/gitlab-basics/img/group_info.png b/doc/gitlab-basics/img/group_info.png deleted file mode 100644 index 2507d6c295b..00000000000 Binary files a/doc/gitlab-basics/img/group_info.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select-group2.png b/doc/gitlab-basics/img/select-group2.png deleted file mode 100644 index aee22c638db..00000000000 Binary files a/doc/gitlab-basics/img/select-group2.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/gitlab-basics/img/select_group_dropdown.png new file mode 100644 index 00000000000..7d8b89c2df9 Binary files /dev/null and b/doc/gitlab-basics/img/select_group_dropdown.png differ -- cgit v1.2.1 From 54642e5245df774eefc1a1bd82b9c033defb5bc1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:21:47 +0200 Subject: Reference the web editor docs in GitLab basics "Create a branch" --- doc/gitlab-basics/create-branch.md | 43 ++++----------------- .../img/web_editor_new_branch_from_issue.png | Bin 0 -> 4728 bytes doc/user/project/repository/web_editor.md | 4 +- 3 files changed, 10 insertions(+), 37 deletions(-) create mode 100644 doc/user/project/repository/img/web_editor_new_branch_from_issue.png diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 8f078c7692f..ad94f0dad29 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -2,38 +2,11 @@ A branch is an independent line of development. -New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project. - -To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab. - -To create a new branch in GitLab, sign in and then select a project on the right side of your screen: - -![Select a project](img/select_project.png) - -Click on "commits" on the menu on the left side of your screen: - -![Commits](img/commits.png) - -Click on the "branches" tab: - -![Branches](img/branches.png) - -Click on the "new branch" button on the right side of the screen: - -![New branch](img/newbranch.png) - -Fill out the information required: - -1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores) - -1. On the "create from" space, add the the name of the branch you want to branch off from - -1. Click on the button "create branch" - -![Branch info](img/branch_info.png) - -### Note: - -You will be able to find and select the name of your branch in the white box next to a project's name: - -![Branch name](img/branch_name.png) +New commits are recorded in the history for the current branch, which results +in taking the source from someone’s repository (the place where the history of +your work is stored) at certain point in time, and apply your own changes to it +in the history of the project. + +To add changes to your GitLab project, you should create a branch. You can do +it in your [terminal](basic-git-commands.md) or by +[using the web interface](../user/project/repository/web_editor.md#create-a-new-branch). diff --git a/doc/user/project/repository/img/web_editor_new_branch_from_issue.png b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png new file mode 100644 index 00000000000..b0a63ddf0ab Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png differ diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index 993c6bfb7e9..675e89e4247 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -97,11 +97,11 @@ There are multiple ways to create a branch from GitLab's web interface. In case your development workflow dictates to have an issue for every merge request, you can quickly create a branch right on the issue page which will be -tied with the issue itself. You can see a **New Branch** button after the issue +tied with the issue itself. You can see a **New branch** button after the issue description, unless there is already a branch with the same name or a referenced merge request. -![New Branch Button](img/new_branch_from_issue.png) +![New Branch Button](img/web_editor_new_branch_from_issue.png) Once you click it, a new branch will be created that diverges from the default branch of your project, by default `master`. The branch name will be based on -- cgit v1.2.1 From dd9e5ba16598eceb61a2d27a4519710b84d01397 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 11 Oct 2016 05:33:55 +0000 Subject: Fix step number and token param in URL example. Fix gitlab-rails command code formatting. --- doc/administration/troubleshooting/debug.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md index d127d7b85e5..d8dce4388e1 100644 --- a/doc/administration/troubleshooting/debug.md +++ b/doc/administration/troubleshooting/debug.md @@ -144,14 +144,14 @@ separate Rails process to debug the issue: 1. Obtain the private token for your user (Profile Settings -> Account). 1. Bring up the GitLab Rails console. For omnibus users, run: - ```` + ``` sudo gitlab-rails console ``` 1. At the Rails console, run: ```ruby - [1] pry(main)> app.get '/private_token?' + [1] pry(main)> app.get '/?private_token=' ``` For example: -- cgit v1.2.1 From 7d703a4dbbce44fda3cc0cfed7c48c27f496b6a0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:41:28 +0200 Subject: New images for GitLab basics "Fork project" docs --- doc/gitlab-basics/fork-project.md | 19 ++++++++++--------- doc/gitlab-basics/img/fork.png | Bin 896 -> 0 bytes doc/gitlab-basics/img/fork_choose_namespace.png | Bin 0 -> 39253 bytes doc/gitlab-basics/img/fork_new.png | Bin 0 -> 25540 bytes doc/gitlab-basics/img/select_project.png | Bin 16176 -> 0 bytes 5 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 doc/gitlab-basics/img/fork.png create mode 100644 doc/gitlab-basics/img/fork_choose_namespace.png create mode 100644 doc/gitlab-basics/img/fork_new.png delete mode 100644 doc/gitlab-basics/img/select_project.png diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 7232fc2c1ae..6c232fe6086 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -1,19 +1,20 @@ # How to fork a project -A fork is a copy of an original repository that you can put somewhere else -or where you can experiment and apply changes that you can later decide if +A fork is a copy of an original repository that you can put in another namespace +where you can experiment and apply changes that you can later decide if publishing or not, without affecting your original project. It takes just a few steps to fork a project in GitLab. -Sign in to GitLab. +1. Go to a project's dashboard under the **Project** tab and click on the + **Fork** button. -Select a project on the right side of your screen: + ![Click on Fork button](img/fork_new.png) -![Select a project](img/select_project.png) +1. You will be asked where to fork the repository. Click on the user or group + to where you'd like to add the forked project. -Click on the "fork" button on the right side of your screen: + ![Choose namespace](img/fork_choose_namespace.png) -![Fork](img/fork.png) - -Click on the user or group to where you'd like to add the forked project. +1. After a few moments, depending on the repository's size, the forking will + complete. diff --git a/doc/gitlab-basics/img/fork.png b/doc/gitlab-basics/img/fork.png deleted file mode 100644 index 13ff8345616..00000000000 Binary files a/doc/gitlab-basics/img/fork.png and /dev/null differ diff --git a/doc/gitlab-basics/img/fork_choose_namespace.png b/doc/gitlab-basics/img/fork_choose_namespace.png new file mode 100644 index 00000000000..82c9c3bd39e Binary files /dev/null and b/doc/gitlab-basics/img/fork_choose_namespace.png differ diff --git a/doc/gitlab-basics/img/fork_new.png b/doc/gitlab-basics/img/fork_new.png new file mode 100644 index 00000000000..41885223286 Binary files /dev/null and b/doc/gitlab-basics/img/fork_new.png differ diff --git a/doc/gitlab-basics/img/select_project.png b/doc/gitlab-basics/img/select_project.png deleted file mode 100644 index 3bb832ea8d0..00000000000 Binary files a/doc/gitlab-basics/img/select_project.png and /dev/null differ -- cgit v1.2.1 From 7374f876dcfd2c113d16367fa62ced23934130c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:44:26 +0200 Subject: Reference the web editor docs in GitLab basics "Add a file" --- doc/gitlab-basics/add-file.md | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index 86aff749ff1..e9fbcbc23a9 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -1,27 +1,5 @@ # How to add a file -You can create a file in your [shell](command-line-commands.md) or in GitLab. - -To create a file in GitLab, sign in to GitLab. - -Select a project on the right side of your screen: - -![Select a project](img/select_project.png) - -It's a good idea to [create a branch](create-branch.md), but it's not necessary. - -Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: - -![Create a file](img/create_file.png) - -Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : - -![File name](img/file_name.png) - -Add all the information that you'd like to include in your file: - -![Add information](img/white_space.png) - -Add a commit message based on what you just added and then click on "commit changes": - -![Commit changes](img/commit_changes.png) +You can create a file in your [terminal](command-line-commands.md) and push +to GitLab or you can use the +[web interface](../user/project/repository/web_editor.md#create-a-file). -- cgit v1.2.1 From 73fff8d9e33d31b0958b7ef889a6ad3ff6dbf4c5 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 11 Oct 2016 10:58:46 +0500 Subject: Build instead create in label_link model spec --- spec/models/label_link_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 5e6f8ca1528..c18ed8574b1 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe LabelLink, models: true do - let(:label) { create(:label_link) } - it { expect(label).to be_valid } + it { expect(build(:label_link)).to be_valid } it { is_expected.to belong_to(:label) } it { is_expected.to belong_to(:target) } -- cgit v1.2.1 From 85df1bf02dbcd180ad7db0c0e3c609671dac1a11 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 11 Oct 2016 11:02:53 +0500 Subject: Remove empty describe block on key spec model --- spec/models/key_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index fd4a2beff58..7fc6ed1dd54 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -5,9 +5,6 @@ describe Key, models: true do it { is_expected.to belong_to(:user) } end - describe "Mass assignment" do - end - describe "Validation" do it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_presence_of(:key) } -- cgit v1.2.1 From 987c21f51ea67f1bd00fe50e61941920bc1feaa4 Mon Sep 17 00:00:00 2001 From: Manthan Mallikarjun Date: Mon, 4 Jul 2016 10:20:16 -0700 Subject: Add an example for testing a phoenix application with Gitlab CI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/ci/examples/README.md | 1 + doc/ci/examples/test-phoenix-application.md | 52 +++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 doc/ci/examples/test-phoenix-application.md diff --git a/CHANGELOG b/CHANGELOG index 06af4e4d6f4..d9c282257b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.13.0 (unreleased) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 40f0165deef..08fbd9afa2f 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -11,6 +11,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) - [Test a Scala application](test-scala-application.md) +- [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md new file mode 100644 index 00000000000..78cab2c0aeb --- /dev/null +++ b/doc/ci/examples/test-phoenix-application.md @@ -0,0 +1,52 @@ +## Test a Phoenix application + +This example demonstrates the integration of Gitlab CI with Phoenix, elixir and +postgres. + +### Add `.gitlab-ci.yml` file to project + +The following `.gitlab-ci.yml` should be added in the root of your +repository to trigger CI: + +```yaml +image: elixir:1.3.1 + +services: + - postgres:9.5.3 + +variables: + MIX_ENV: "test" + +before_script: + # Setup phoenix dependencies + - apt-get update + - apt-get install -y postgresql-client + - mix local.hex --force + - mix deps.get --only test + - mix ecto.reset + +test: + script: + - mix test +``` + +The variables will set the Mix environment to test. The +before_script will install `psql`, and other phoenix dependencies and will also +run your migrations. + +Finally, the test script will run your tests. + +### Update the Config Settings + +In `config/test.exs`, update the database hostname: +``` +config :my_app, MyApp.Repo, + hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), +``` + +### Add the Migrations Folder + +If you do not have any migrations yet, you will need to create an empty +`.gitkeep` file in `priv/repo/migrations`. + +**Source**: https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 -- cgit v1.2.1 From 8a3f389df620570e2b51f088573b90644c53348e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 09:36:03 +0200 Subject: Improve a bit the example .gitlab-ci.yml for Phoenix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/ci/examples/test-phoenix-application.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md index 78cab2c0aeb..150698ca04b 100644 --- a/doc/ci/examples/test-phoenix-application.md +++ b/doc/ci/examples/test-phoenix-application.md @@ -1,7 +1,7 @@ ## Test a Phoenix application -This example demonstrates the integration of Gitlab CI with Phoenix, elixir and -postgres. +This example demonstrates the integration of Gitlab CI with Phoenix, Elixir and +Postgres. ### Add `.gitlab-ci.yml` file to project @@ -9,10 +9,10 @@ The following `.gitlab-ci.yml` should be added in the root of your repository to trigger CI: ```yaml -image: elixir:1.3.1 +image: elixir:1.3 services: - - postgres:9.5.3 + - postgres:9.6 variables: MIX_ENV: "test" @@ -30,16 +30,17 @@ test: - mix test ``` -The variables will set the Mix environment to test. The -before_script will install `psql`, and other phoenix dependencies and will also +The variables will set the Mix environment to "test". The +`before_script` will install `psql`, some Phoenix dependencies, and will also run your migrations. -Finally, the test script will run your tests. +Finally, the test `script` will run your tests. ### Update the Config Settings In `config/test.exs`, update the database hostname: -``` + +```elixir config :my_app, MyApp.Repo, hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), ``` @@ -49,4 +50,7 @@ config :my_app, MyApp.Repo, If you do not have any migrations yet, you will need to create an empty `.gitkeep` file in `priv/repo/migrations`. -**Source**: https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +### Sources + +- https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +- https://davejlong.com/ci-with-phoenix-and-gitlab/ -- cgit v1.2.1 From ebba49149395ba6fb0f14c3aa9c46a496b234dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 18:35:36 +0200 Subject: Add a new gitlab:users:clear_all_authentication_tokens task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/raketasks/user_management.md | 15 ++++++++++++++ lib/tasks/gitlab/users.rake | 11 +++++++++++ spec/tasks/gitlab/users_rake_spec.rb | 38 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 lib/tasks/gitlab/users.rake create mode 100644 spec/tasks/gitlab/users_rake_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 06af4e4d6f4..1a04e42c803 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,6 +76,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 8a5e2d6e16b..044b104f5c2 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -70,3 +70,18 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users # installation from source bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production ``` + +## Clear authentication tokens for all users. Important! Data loss! + +Clear authentication tokens for all users in the GitLab database. This +task is useful if your users' authentication tokens might have been exposed in +any way. All the existing tokens will become invalid, and new tokens are +automatically generated upon sign-in or user modification. + +``` +# omnibus-gitlab +sudo gitlab-rake gitlab:users:clear_all_authentication_tokens + +# installation from source +bundle exec rake gitlab:users:clear_all_authentication_tokens RAILS_ENV=production +``` diff --git a/lib/tasks/gitlab/users.rake b/lib/tasks/gitlab/users.rake new file mode 100644 index 00000000000..3a16ace60bd --- /dev/null +++ b/lib/tasks/gitlab/users.rake @@ -0,0 +1,11 @@ +namespace :gitlab do + namespace :users do + desc "GitLab | Clear the authentication token for all users" + task clear_all_authentication_tokens: :environment do |t, args| + # Do small batched updates because these updates will be slow and locking + User.select(:id).find_in_batches(batch_size: 100) do |batch| + User.where(id: batch.map(&:id)).update_all(authentication_token: nil) + end + end + end +end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb new file mode 100644 index 00000000000..e6ebef82b78 --- /dev/null +++ b/spec/tasks/gitlab/users_rake_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:users namespace rake task' do + let(:enable_registry) { true } + + before :all do + Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/users' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + def run_rake_task(task_name) + Rake::Task[task_name].reenable + Rake.application.invoke_task task_name + end + + describe 'clear_all_authentication_tokens' do + before do + # avoid writing task output to spec progress + allow($stdout).to receive :write + end + + context 'gitlab version' do + it 'clears the authentication token for all users' do + create_list(:user, 2) + + expect(User.pluck(:authentication_token)).to all(be_present) + + run_rake_task('gitlab:users:clear_all_authentication_tokens') + + expect(User.pluck(:authentication_token)).to all(be_nil) + end + end + end +end -- cgit v1.2.1 From 8d8282b42e765d20532d32b0598f42ea707f31f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 10:21:22 +0200 Subject: Add a safeguard in User#set_projects_limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/user.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 892ac28d5b3..f367f4616fb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -589,6 +589,11 @@ class User < ActiveRecord::Base end def set_projects_limit + # `User.select(:id)` raises + # `ActiveModel::MissingAttributeError: missing attribute: projects_limit` + # without this safeguard! + return unless self.has_attribute?(:projects_limit) + connection_default_value_defined = new_record? && !projects_limit_changed? return unless self.projects_limit.nil? || connection_default_value_defined -- cgit v1.2.1 From 6c1690fcc03406ad3230cb05ba8701289a25ba28 Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Tue, 13 Sep 2016 23:05:16 +0200 Subject: Allow empty merge requests --- CHANGELOG | 1 + app/assets/stylesheets/pages/merge_requests.scss | 12 ++++++ app/services/merge_requests/build_service.rb | 23 +++++----- .../projects/merge_requests/_new_compare.html.haml | 13 ------ .../projects/merge_requests/_new_submit.html.haml | 50 ++++++++++++---------- .../shared/icons/_illustration_no_commits.svg | 1 + spec/services/merge_requests/build_service_spec.rb | 20 ++++++++- 7 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 app/views/shared/icons/_illustration_no_commits.svg diff --git a/CHANGELOG b/CHANGELOG index 72e146f4f3f..81c0bfbe3e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,7 @@ v 8.13.0 (unreleased) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..2158dc0cfe8 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -204,6 +204,18 @@ word-break: break-all; } +.commits-empty { + text-align: center; + + h4 { + padding-top: 20px; + padding-bottom: 10px; + } + svg { + width: 230px; + } +} + .mr-list { .merge-request { padding: 10px 15px; diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index e57791f6818..404f75616b5 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -4,7 +4,7 @@ module MergeRequests merge_request = MergeRequest.new(params) # Set MR attributes - merge_request.can_be_created = false + merge_request.can_be_created = true merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project @@ -22,6 +22,12 @@ module MergeRequests return build_failed(merge_request, message) end + if merge_request.source_project == merge_request.target_project && + merge_request.target_branch == merge_request.source_branch + + return build_failed(merge_request, 'You must select different branches') + end + compare = CompareService.new.execute( merge_request.source_project, merge_request.source_branch, @@ -29,17 +35,8 @@ module MergeRequests merge_request.target_branch, ) - commits = compare.commits - - # At this point we decide if merge request can be created - # If we have at least one commit to merge -> creation allowed - if commits.present? - merge_request.compare_commits = commits - merge_request.can_be_created = true - merge_request.compare = compare - else - merge_request.can_be_created = false - end + merge_request.compare_commits = compare.commits + merge_request.compare = compare set_title_and_description(merge_request) end @@ -89,6 +86,8 @@ module MergeRequests end end + merge_request.title = merge_request.wip_title if commits.empty? + merge_request end diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index de39964fca8..466ec1475d8 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -65,19 +65,6 @@ - if @merge_request.errors.any? = form_errors(@merge_request) - - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - .light-well.append-bottom-default - .center - %h4 - There isn't anything to merge. - %p.slead - - if @merge_request.source_branch == @merge_request.target_branch - You'll need to use different branch names to get a valid comparison. - - else - %span.label-branch #{@merge_request.source_branch} - and - %span.label-branch #{@merge_request.target_branch} - are the same. = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :javascript diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 88d8013a0d1..da6927879a4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,29 +18,35 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.merge-request-tabs.nav-links.no-top.no-bottom - %li.commits-tab.active - = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do - Commits - %span.badge= @commits.size - - if @pipeline - %li.builds-tab - = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do - Builds - %span.badge= @statuses.size - %li.diffs-tab - = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do - Changes - %span.badge= @merge_request.diff_size + - if @commits.empty? + .commits-empty + %h4 + There are no commits yet. + = custom_icon ('illustration_no_commits') + - else + %ul.merge-request-tabs.nav-links.no-top.no-bottom + %li.commits-tab.active + = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do + Commits + %span.badge= @commits.size + - if @pipeline + %li.builds-tab + = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size + %li.diffs-tab + = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do + Changes + %span.badge= @merge_request.diff_size - .tab-content - #commits.commits.tab-pane.active - = render "projects/merge_requests/show/commits" - #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX - - if @pipeline - #builds.builds.tab-pane - = render "projects/merge_requests/show/builds" + .tab-content + #commits.commits.tab-pane.active + = render "projects/merge_requests/show/commits" + #diffs.diffs.tab-pane + - # This tab is always loaded via AJAX + - if @pipeline + #builds.builds.tab-pane + = render "projects/merge_requests/show/builds" .mr-loading-status = spinner diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg new file mode 100644 index 00000000000..4f9d9add60d --- /dev/null +++ b/app/views/shared/icons/_illustration_no_commits.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 0d586e2216b..3a3f07ddcb9 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -52,12 +52,28 @@ describe MergeRequests::BuildService, services: true do end end - context 'no commits in the diff' do - let(:commits) { [] } + context 'same source and target branch' do + let(:source_branch) { 'master' } it 'forbids the merge request from being created' do expect(merge_request.can_be_created).to eq(false) end + + it 'adds an error message to the merge request' do + expect(merge_request.errors).to contain_exactly('You must select different branches') + end + end + + context 'no commits in the diff' do + let(:commits) { [] } + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end + + it 'adds a WIP prefix to the merge request title' do + expect(merge_request.title).to eq('WIP: Feature branch') + end end context 'one commit in the diff' do -- cgit v1.2.1 From 815bf9b5892e215dec31ab0a45a5cc75b091e2af Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:07:37 +0200 Subject: Add new images in GitLab basics "Create issue" docs --- doc/gitlab-basics/create-issue.md | 33 ++++++++++++++++------------- doc/gitlab-basics/img/issue_title.png | Bin 8311 -> 0 bytes doc/gitlab-basics/img/issues.png | Bin 4153 -> 0 bytes doc/gitlab-basics/img/new_issue.png | Bin 2974 -> 0 bytes doc/gitlab-basics/img/new_issue_button.png | Bin 0 -> 3070 bytes doc/gitlab-basics/img/new_issue_page.png | Bin 0 -> 53268 bytes doc/gitlab-basics/img/newbranch.png | Bin 1244 -> 0 bytes doc/gitlab-basics/img/project_navbar.png | Bin 0 -> 5745 bytes doc/gitlab-basics/img/submit_new_issue.png | Bin 8644 -> 0 bytes 9 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 doc/gitlab-basics/img/issue_title.png delete mode 100644 doc/gitlab-basics/img/issues.png delete mode 100644 doc/gitlab-basics/img/new_issue.png create mode 100644 doc/gitlab-basics/img/new_issue_button.png create mode 100644 doc/gitlab-basics/img/new_issue_page.png delete mode 100644 doc/gitlab-basics/img/newbranch.png create mode 100644 doc/gitlab-basics/img/project_navbar.png delete mode 100644 doc/gitlab-basics/img/submit_new_issue.png diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index e11c86cafa0..13e5a738c89 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -1,27 +1,30 @@ # How to create an Issue in GitLab -The Issue Tracker is a good place to add things that need to be improved or solved in a project. +The issue tracker is a good place to add things that need to be improved or +solved in a project. -To create an Issue, sign in to GitLab. +--- -Go to the project where you'd like to create the Issue: +1. Go to the project where you'd like to create the issue and navigate to the + **Issues** tab on top. -![Select a project](img/select_project.png) + ![Issues](img/project_navbar.png) -Click on "Issues" on the left side of your screen: +1. Click on the **New issue** button on the right side of your screen. -![Issues](img/issues.png) + ![New issue](img/new_issue_button.png) -Click on the "+ new issue" button on the right side of your screen: +1. At the very minimum, add a title and a description to your issue. + You may assign it to a user, add a milestone or add labels (all optional). -![New issue](img/new_issue.png) + ![Issue title and description](img/new_issue_page.png) -Add a title and a description to your issue: +1. When ready, click on **Submit issue**. -![Issue title and description](img/issue_title.png) +--- -You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": - -![Submit new issue](img/submit_new_issue.png) - -Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). +Your Issue will now be added to the issue tracker of the project you opened it +at and will be ready to be reviewed. You can comment on it and mention the +people involved. You can also link issues to the merge requests where the issues +are solved. To do this, you can use an +[issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/img/issue_title.png b/doc/gitlab-basics/img/issue_title.png deleted file mode 100644 index 60a6f7973be..00000000000 Binary files a/doc/gitlab-basics/img/issue_title.png and /dev/null differ diff --git a/doc/gitlab-basics/img/issues.png b/doc/gitlab-basics/img/issues.png deleted file mode 100644 index 14e9cdb64e1..00000000000 Binary files a/doc/gitlab-basics/img/issues.png and /dev/null differ diff --git a/doc/gitlab-basics/img/new_issue.png b/doc/gitlab-basics/img/new_issue.png deleted file mode 100644 index 94e7503dd8b..00000000000 Binary files a/doc/gitlab-basics/img/new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/img/new_issue_button.png b/doc/gitlab-basics/img/new_issue_button.png new file mode 100644 index 00000000000..46b626bed65 Binary files /dev/null and b/doc/gitlab-basics/img/new_issue_button.png differ diff --git a/doc/gitlab-basics/img/new_issue_page.png b/doc/gitlab-basics/img/new_issue_page.png new file mode 100644 index 00000000000..843504130b7 Binary files /dev/null and b/doc/gitlab-basics/img/new_issue_page.png differ diff --git a/doc/gitlab-basics/img/newbranch.png b/doc/gitlab-basics/img/newbranch.png deleted file mode 100644 index d5fcf33c4ea..00000000000 Binary files a/doc/gitlab-basics/img/newbranch.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_navbar.png b/doc/gitlab-basics/img/project_navbar.png new file mode 100644 index 00000000000..97cf3cd9702 Binary files /dev/null and b/doc/gitlab-basics/img/project_navbar.png differ diff --git a/doc/gitlab-basics/img/submit_new_issue.png b/doc/gitlab-basics/img/submit_new_issue.png deleted file mode 100644 index 78b854c8903..00000000000 Binary files a/doc/gitlab-basics/img/submit_new_issue.png and /dev/null differ -- cgit v1.2.1 From 54fd5e830a867bd20d263bce8b37385dd7bd7b6e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:29:16 +0200 Subject: New images for GitLab basics "Create MR" docs --- doc/gitlab-basics/add-merge-request.md | 49 +++++++++------------ doc/gitlab-basics/img/button-create-mr.png | Bin 5927 -> 0 bytes doc/gitlab-basics/img/compare_branches.png | Bin 1520 -> 0 bytes doc/gitlab-basics/img/merge_request_new.png | Bin 0 -> 3596 bytes doc/gitlab-basics/img/merge_request_page.png | Bin 0 -> 91432 bytes .../img/merge_request_select_branch.png | Bin 0 -> 50707 bytes doc/gitlab-basics/img/new_merge_request.png | Bin 3162 -> 0 bytes doc/gitlab-basics/img/select_branch.png | Bin 11207 -> 0 bytes 8 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 doc/gitlab-basics/img/button-create-mr.png delete mode 100644 doc/gitlab-basics/img/compare_branches.png create mode 100644 doc/gitlab-basics/img/merge_request_new.png create mode 100644 doc/gitlab-basics/img/merge_request_page.png create mode 100644 doc/gitlab-basics/img/merge_request_select_branch.png delete mode 100644 doc/gitlab-basics/img/new_merge_request.png delete mode 100644 doc/gitlab-basics/img/select_branch.png diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 082d1408aa2..bf01fe51dc3 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -1,42 +1,33 @@ # How to create a merge request -Merge Requests are useful to integrate separate changes that you've made to a project, on different branches. +Merge requests are useful to integrate separate changes that you've made to a +project, on different branches. This is a brief guide on how to create a merge +request. For more information, check the +[merge requests documentation](../user/project/merge_requests.md). -To create a new Merge Request, sign in to GitLab. +--- -Go to the project where you'd like to merge your changes: +1. Before you start, you should have already [created a branch](create-branch.md) + and [pushed your changes](basic-git-commands.md) to GitLab. -![Select a project](img/select_project.png) +1. You can then go to the project where you'd like to merge your changes and + click on the **Merge requests** tab. -Click on "Merge Requests" on the left side of your screen: + ![Merge requests](img/project_navbar.png) -![Merge requests](img/merge_requests.png) +1. Click on **New merge request** on the right side of the screen. -Click on "+ new Merge Request" on the right side of the screen: + ![New Merge Request](img/merge_request_new.png) -![New Merge Request](img/new_merge_request.png) +1. Select a source branch and click on the **Compare branches and continue** button. -Select a source branch or branch: + ![Select a branch](img/merge_request_select_branch.png) -![Select a branch](img/select_branch.png) +1. At a minimum, add a title and a description to your merge request. Optionally, + select a user to review your merge request and to accept or close it. You may + also select a milestone and labels. -Click on the "compare branches" button: + ![New merge request page](img/merge_request_page.png) -![Compare branches](img/compare_branches.png) - -Add a title and a description to your Merge Request: - -![Add a title and description](img/title_description_mr.png) - -Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: - -![Add a new merge request](img/add_new_merge_request.png) - -Your Merge Request will be ready to be approved and published. - -### Note - -After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. -You may automatically create a Merge Request from your recently created branch when clicking on this button: - -![Automatic MR button](img/button-create-mr.png) +1. When ready, click on the **Submit merge request** button. Your merge request + will be ready to be approved and published. diff --git a/doc/gitlab-basics/img/button-create-mr.png b/doc/gitlab-basics/img/button-create-mr.png deleted file mode 100644 index b52ab148839..00000000000 Binary files a/doc/gitlab-basics/img/button-create-mr.png and /dev/null differ diff --git a/doc/gitlab-basics/img/compare_branches.png b/doc/gitlab-basics/img/compare_branches.png deleted file mode 100644 index 8a18453dd05..00000000000 Binary files a/doc/gitlab-basics/img/compare_branches.png and /dev/null differ diff --git a/doc/gitlab-basics/img/merge_request_new.png b/doc/gitlab-basics/img/merge_request_new.png new file mode 100644 index 00000000000..0aba5743f01 Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_new.png differ diff --git a/doc/gitlab-basics/img/merge_request_page.png b/doc/gitlab-basics/img/merge_request_page.png new file mode 100644 index 00000000000..68c3bbf9444 Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_page.png differ diff --git a/doc/gitlab-basics/img/merge_request_select_branch.png b/doc/gitlab-basics/img/merge_request_select_branch.png new file mode 100644 index 00000000000..516436ff6cc Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_select_branch.png differ diff --git a/doc/gitlab-basics/img/new_merge_request.png b/doc/gitlab-basics/img/new_merge_request.png deleted file mode 100644 index 842f5ebed74..00000000000 Binary files a/doc/gitlab-basics/img/new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select_branch.png b/doc/gitlab-basics/img/select_branch.png deleted file mode 100644 index f72a3ffb57f..00000000000 Binary files a/doc/gitlab-basics/img/select_branch.png and /dev/null differ -- cgit v1.2.1 From 777ca780275d7177dffa7d95e1e863192537da68 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:30:35 +0200 Subject: Rearrange GitLab basics READMEs [ci skip] --- doc/README.md | 2 +- doc/gitlab-basics/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/README.md b/doc/README.md index 9017b143260..99fbfc3438d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. -- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. +- [GitLab basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md). - [Markdown](user/markdown.md) GitLab's advanced formatting system. diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 38843f36b2e..746ccb89705 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -11,5 +11,5 @@ Step-by-step guides on the basics of working with Git and GitLab. - [Fork a project](fork-project.md) - [Add a file](add-file.md) - [Add an image](add-image.md) -- [Create a Merge Request](add-merge-request.md) -- [Create an Issue](create-issue.md) +- [Create an issue](create-issue.md) +- [Create a merge request](add-merge-request.md) -- cgit v1.2.1 From d87df157987e5ba2690fc1a7937e557821efb997 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Tue, 11 Oct 2016 11:34:20 +0200 Subject: changed the scss for the top line connectors to be exactly centered --- app/assets/stylesheets/pages/pipelines.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 21224447628..05f59279637 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -316,7 +316,7 @@ &::before { content: ''; position: absolute; - top: 49%; + top: 48%; left: -48px; border-top: 2px solid $border-color; width: 48px; @@ -493,7 +493,7 @@ &::after { content: ''; position: absolute; - top: 49%; + top: 48%; right: -48px; border-top: 2px solid $border-color; width: 48px; @@ -589,7 +589,7 @@ width: 21px; height: 25px; position: absolute; - top: -31px; + top: -32px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From f7d2a97a84908c3cddf2d4fc550fbc2c7857af81 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 17:28:09 +0100 Subject: Fixes padding in all clipboard icons that have .btn class --- app/helpers/button_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index a695aceea76..353e7fd33fd 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -22,7 +22,7 @@ module ButtonHelper class: "btn #{css_class}", data: data, type: :button, - title: "Copy to Clipboard" + title: 'Copy to Clipboard' end def http_clone_button(project, placement = 'right', append_link: true) -- cgit v1.2.1 From 4269e303515386f38a135fd07ad38955c027e424 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 20:25:14 +0100 Subject: Adds entry to CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 56c3ee90541..6afcbf28013 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -88,6 +88,7 @@ v 8.13.0 (unreleased) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) + - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found -- cgit v1.2.1 From b0646c4f23aff0343c2fd455c7dc5de52ca6a362 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 11 Oct 2016 10:17:56 +0100 Subject: Adds btn-transparent class in correct place due to changes in master --- app/helpers/button_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 353e7fd33fd..85e1dc33ee8 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,7 +15,7 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) - css_class = data[:class] || 'btn-clipboard' + css_class = data[:class] || 'btn-clipboard btn-transparent' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), -- cgit v1.2.1 From e98d4940fe8f5315f7467a13fcdd952ace73f9e8 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 13:14:08 +0200 Subject: Remove redundant images [ci skip] --- doc/gitlab-basics/README.md | 2 +- doc/gitlab-basics/img/add_new_merge_request.png | Bin 9003 -> 0 bytes doc/gitlab-basics/img/branch_info.png | Bin 7572 -> 0 bytes doc/gitlab-basics/img/branch_name.png | Bin 2137 -> 0 bytes doc/gitlab-basics/img/branches.png | Bin 3548 -> 0 bytes doc/gitlab-basics/img/commit_changes.png | Bin 4941 -> 0 bytes doc/gitlab-basics/img/commit_message.png | Bin 5103 -> 0 bytes doc/gitlab-basics/img/commits.png | Bin 4112 -> 0 bytes doc/gitlab-basics/img/create_file.png | Bin 2451 -> 0 bytes doc/gitlab-basics/img/edit_file.png | Bin 2221 -> 0 bytes doc/gitlab-basics/img/file_located.png | Bin 3078 -> 0 bytes doc/gitlab-basics/img/file_name.png | Bin 2412 -> 0 bytes doc/gitlab-basics/img/find_file.png | Bin 8426 -> 0 bytes doc/gitlab-basics/img/find_group.png | Bin 5897 -> 0 bytes doc/gitlab-basics/img/groups.png | Bin 4752 -> 0 bytes doc/gitlab-basics/img/image_file.png | Bin 2796 -> 0 bytes doc/gitlab-basics/img/merge_requests.png | Bin 4213 -> 0 bytes doc/gitlab-basics/img/select-group.png | Bin 6034 -> 0 bytes doc/gitlab-basics/img/settings.png | Bin 4149 -> 0 bytes doc/gitlab-basics/img/title_description_mr.png | Bin 11919 -> 0 bytes doc/gitlab-basics/img/white_space.png | Bin 2192 -> 0 bytes 21 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 doc/gitlab-basics/img/add_new_merge_request.png delete mode 100644 doc/gitlab-basics/img/branch_info.png delete mode 100644 doc/gitlab-basics/img/branch_name.png delete mode 100644 doc/gitlab-basics/img/branches.png delete mode 100644 doc/gitlab-basics/img/commit_changes.png delete mode 100644 doc/gitlab-basics/img/commit_message.png delete mode 100644 doc/gitlab-basics/img/commits.png delete mode 100644 doc/gitlab-basics/img/create_file.png delete mode 100644 doc/gitlab-basics/img/edit_file.png delete mode 100644 doc/gitlab-basics/img/file_located.png delete mode 100644 doc/gitlab-basics/img/file_name.png delete mode 100644 doc/gitlab-basics/img/find_file.png delete mode 100644 doc/gitlab-basics/img/find_group.png delete mode 100644 doc/gitlab-basics/img/groups.png delete mode 100644 doc/gitlab-basics/img/image_file.png delete mode 100644 doc/gitlab-basics/img/merge_requests.png delete mode 100644 doc/gitlab-basics/img/select-group.png delete mode 100644 doc/gitlab-basics/img/settings.png delete mode 100644 doc/gitlab-basics/img/title_description_mr.png delete mode 100644 doc/gitlab-basics/img/white_space.png diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 746ccb89705..d7e3aa35bdd 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,7 +2,7 @@ Step-by-step guides on the basics of working with Git and GitLab. -- [Command Line basics](command-line-commands.md) +- [Command line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) - [Create a project](create-project.md) diff --git a/doc/gitlab-basics/img/add_new_merge_request.png b/doc/gitlab-basics/img/add_new_merge_request.png deleted file mode 100644 index e60992c4c6a..00000000000 Binary files a/doc/gitlab-basics/img/add_new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branch_info.png b/doc/gitlab-basics/img/branch_info.png deleted file mode 100644 index 2264f3c5bf2..00000000000 Binary files a/doc/gitlab-basics/img/branch_info.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branch_name.png b/doc/gitlab-basics/img/branch_name.png deleted file mode 100644 index 75fe8313611..00000000000 Binary files a/doc/gitlab-basics/img/branch_name.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branches.png b/doc/gitlab-basics/img/branches.png deleted file mode 100644 index 8621bc05776..00000000000 Binary files a/doc/gitlab-basics/img/branches.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commit_changes.png b/doc/gitlab-basics/img/commit_changes.png deleted file mode 100644 index a88809c5a3f..00000000000 Binary files a/doc/gitlab-basics/img/commit_changes.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commit_message.png b/doc/gitlab-basics/img/commit_message.png deleted file mode 100644 index 4abe4517f98..00000000000 Binary files a/doc/gitlab-basics/img/commit_message.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commits.png b/doc/gitlab-basics/img/commits.png deleted file mode 100644 index 2bfcaf75f01..00000000000 Binary files a/doc/gitlab-basics/img/commits.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_file.png b/doc/gitlab-basics/img/create_file.png deleted file mode 100644 index 5ebe1b227dd..00000000000 Binary files a/doc/gitlab-basics/img/create_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/edit_file.png b/doc/gitlab-basics/img/edit_file.png deleted file mode 100644 index 9d3e817d036..00000000000 Binary files a/doc/gitlab-basics/img/edit_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/file_located.png b/doc/gitlab-basics/img/file_located.png deleted file mode 100644 index e357cb5c6ab..00000000000 Binary files a/doc/gitlab-basics/img/file_located.png and /dev/null differ diff --git a/doc/gitlab-basics/img/file_name.png b/doc/gitlab-basics/img/file_name.png deleted file mode 100644 index 01639c77d0d..00000000000 Binary files a/doc/gitlab-basics/img/file_name.png and /dev/null differ diff --git a/doc/gitlab-basics/img/find_file.png b/doc/gitlab-basics/img/find_file.png deleted file mode 100644 index 6f26d26ae18..00000000000 Binary files a/doc/gitlab-basics/img/find_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/find_group.png b/doc/gitlab-basics/img/find_group.png deleted file mode 100644 index 1211510aae9..00000000000 Binary files a/doc/gitlab-basics/img/find_group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/groups.png b/doc/gitlab-basics/img/groups.png deleted file mode 100644 index ef3dca60cc8..00000000000 Binary files a/doc/gitlab-basics/img/groups.png and /dev/null differ diff --git a/doc/gitlab-basics/img/image_file.png b/doc/gitlab-basics/img/image_file.png deleted file mode 100644 index 7f304b8e1f2..00000000000 Binary files a/doc/gitlab-basics/img/image_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/merge_requests.png b/doc/gitlab-basics/img/merge_requests.png deleted file mode 100644 index 570164df18b..00000000000 Binary files a/doc/gitlab-basics/img/merge_requests.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select-group.png b/doc/gitlab-basics/img/select-group.png deleted file mode 100644 index 33b978dd899..00000000000 Binary files a/doc/gitlab-basics/img/select-group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/settings.png b/doc/gitlab-basics/img/settings.png deleted file mode 100644 index 78637013d9b..00000000000 Binary files a/doc/gitlab-basics/img/settings.png and /dev/null differ diff --git a/doc/gitlab-basics/img/title_description_mr.png b/doc/gitlab-basics/img/title_description_mr.png deleted file mode 100644 index c31d61ec336..00000000000 Binary files a/doc/gitlab-basics/img/title_description_mr.png and /dev/null differ diff --git a/doc/gitlab-basics/img/white_space.png b/doc/gitlab-basics/img/white_space.png deleted file mode 100644 index eaa969bdcf4..00000000000 Binary files a/doc/gitlab-basics/img/white_space.png and /dev/null differ -- cgit v1.2.1 From c64721f16331f2115d3fe87f3c04134f8cba0163 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 5 Oct 2016 17:40:13 +0100 Subject: Document the new CI_DEBUG_TRACE variable [ci skip] --- doc/ci/pipelines.md | 2 ++ doc/ci/variables/README.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** -- cgit v1.2.1 From f361b1c624443f0e693d8b7c9bd81fb713c943cf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 14:22:18 +0200 Subject: Refactor the SubGit/SVN documentation [ci skip] --- doc/workflow/importing/migrating_from_svn.md | 92 ++++++++++++++++------------ 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index fc27a38f735..423b095e69e 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -10,7 +10,7 @@ There are two approaches to SVN to Git migration: 1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: - Makes the GitLab repository to mirror the SVN project. - - Git and SVN repositories are kept in sync; you can use either one. + - Git and SVN repositories are kept in sync; you can use either one. - Smoothens the migration process and allows to manage migration risks. 1. [Cut over migration](#cut-over-migration-with-svn2git) which: @@ -19,82 +19,94 @@ There are two approaches to SVN to Git migration: ## Smooth migration with a Git/SVN mirror using SubGit -#### Prerequisites +[SubGit](https://subgit.com) is a tool for a smooth, stress-free SVN to Git +migration. It creates a writable Git mirror of a local or remote Subversion +repository and that way you can use both Subversion and Git as long as you like. +It requires access to your GitLab server as it talks with the Git repositories +directly in a filesystem level. -Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions follow this -[instruction](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +### SubGit prerequisites -Download SubGit tool from [https://subgit.com/download/](https://subgit.com/download/) +1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can + follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +1. Download SubGit from https://subgit.com/download/. +1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit` + command will be available at `/opt/subgit-VERSION/bin/subgit`. -Unpack downloaded SubGit zip archive to `/opt` directory, subgit will be available -at `/opt/subgit-VERSION/bin/subgit` +### SubGit configuration -#### Configuration - -In GitLab create new empty repository. In filesystem it will be located at -`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path by default. -For convenice, assign this path to a variable: +The first step to mirror you SVN repository in GitLab is to create a new empty +project which will be used as a mirror. For Omnibus installations the path to +the repository will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPO.git` by default. For +installations from source, the default repository directory will be +`/home/git/repositories/USER/REPO.git`. For convenience, assign this path to a +variable: ``` -GIT_REPOS_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +GIT_REPO_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git ``` -SubGit will keep this repository will be kept in sync with a remote SVN project. -For convenience, assign remote SVN project URL to a variable: +SubGit will keep this repository in sync with a remote SVN project. For +convenience, assign your remote SVN project URL to a variable: ``` SVN_PROJECT_URL=http://svn.company.com/repos/project ``` -Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran -on behalf of the same user that keeps ownership of GitLab Git repositories (`git` by default): +Next you need to run SubGit to set up a Git/SVN mirror. Make sure the following +`subgit` command is ran on behalf of the same user that keeps ownership of +GitLab Git repositories (by default `git`): ``` -subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPOS_PATH +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPO_PATH ``` -Adjust authors and branches mappings, if necessary: +Adjust authors and branches mappings, if necessary. Open with your favorite +text editor: ``` -edit $GIT_REPOS_PATH/subgit/authors.txt -edit $GIT_REPOS_PATH/subgit/config +edit $GIT_REPO_PATH/subgit/authors.txt +edit $GIT_REPO_PATH/subgit/config ``` -For more information regarding SubGit configuration options, refer to -[documentation](https://subgit.com/documentation.html) at SubGit web site. +For more information regarding the SubGit configuration options, refer to +[SubGit's documentation](https://subgit.com/documentation.html) website. -#### Initial translation +### Initial translation -Run `subgit` to perform initial translation of existing SVN revisions into -Git repository: +Now that SubGit has configured the Git/SVN repos, run `subgit` to perform the +initial translation of existing SVN revisions into the Git repository: ``` subgit install $GIT_REPOS_PATH ``` -After initial translation is completed, GitLab Git repository and SVN project -will be kept in sync by `subgit` - new Git commits will be translated to SVN -revisions and new SVN revisions will be translated to Git commits. Mirror works -transparently and does not require any special commands. +After the initial translation is completed, the Git repository and the SVN +project will be kept in sync by `subgit` - new Git commits will be translated to +SVN revisions and new SVN revisions will be translated to Git commits. Mirror +works transparently and does not require any special commands. -Would you prefer to perform one-time cut over migration with `subgit` use -`import` command in place of `install`: +If you would prefer to perform one-time cut over migration with `subgit`, use +the `import` command instead of `install`: ``` -subgit import $GIT_REPOS_PATH +subgit import $GIT_REPO_PATH ``` -#### Licensing +### SubGit licensing -Running SubGit in a mirror mode requires [registration](https://subgit.com/pricing.html). Registration is free for Open Source, -Academic and Startup projects. +Running SubGit in a mirror mode requires a +[registration](https://subgit.com/pricing.html). Registration is free for open +source, academic and startup projects. -We're currently working on deeper GitLab/SubGit intergation. You may track our +We're currently working on deeper GitLab/SubGit integration. You may track our progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). -#### Support +### SubGit support -For any questions related to SVN to GitLab migration with SubGit you can contact SubGit team at [support@subgit.com](mailto:support@subgit.com). +For any questions related to SVN to GitLab migration with SubGit, you can +contact the SubGit team directly at [support@subgit.com](mailto:support@subgit.com). ## Cut over migration with svn2git @@ -168,4 +180,4 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on -how to migrate from SVN and other version control systems. \ No newline at end of file +how to migrate from SVN and other version control systems. -- cgit v1.2.1 From 1022456bb15d18b05c14fe344950fb75c7c69f48 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 7 Oct 2016 16:49:48 +0100 Subject: Allow browsing branches that end with '.atom' We need to do two things to support this: 1. Simplify the regex capture in the routing for the CommitsController to not exclude the '.atom' suffix. That's a perfectly valid git branch name, so we shouldn't blow up if we get it. 2. Because Rails now can't automatically detect the request format, add some code to do so in `ExtractPath` when there is no path. This means that, given branches 'foo' and 'foo.atom', the Atom feed for the former is unroutable. To fix this: don't do that! Give the branches different names! --- CHANGELOG | 1 + config/routes/project.rb | 2 +- lib/extracts_path.rb | 33 +++++++- .../projects/commits_controller_spec.rb | 41 +++++++--- spec/lib/extracts_path_spec.rb | 87 +++++++++++++++++++++- spec/routing/project_routing_spec.rb | 2 +- 6 files changed, 152 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..2f2396ad29f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment + - Allow browsing branches that end with '.atom' - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Cache rendered markdown in the database, rather than Redis diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..4c1da5c4df5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -159,7 +159,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get( '/commits/*id', to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + constraints: { id: /.+/, format: false }, as: :commits ) end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index a4558d157c0..e4d996a3fb6 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -52,8 +52,7 @@ module ExtractsPath # Append a trailing slash if we only get a ref and no file path id += '/' unless id.ends_with?('/') - valid_refs = @project.repository.ref_names - valid_refs.select! { |v| id.start_with?("#{v}/") } + valid_refs = ref_names.select { |v| id.start_with?("#{v}/") } if valid_refs.length == 0 # No exact ref match, so just try our best @@ -74,6 +73,19 @@ module ExtractsPath pair end + # If we have an ID of 'foo.atom', and the controller provides Atom and HTML + # formats, then we have to check if the request was for the Atom version of + # the ID without the '.atom' suffix, or the HTML version of the ID including + # the suffix. We only check this if the version including the suffix doesn't + # match, so it is possible to create a branch which has an unroutable Atom + # feed. + def extract_ref_without_atom(id) + id_without_atom = id.sub(/\.atom$/, '') + valid_refs = ref_names.select { |v| "#{id_without_atom}/".start_with?("#{v}/") } + + valid_refs.max_by(&:length) + end + # Assigns common instance variables for views working with Git tree-ish objects # # Assignments are: @@ -86,6 +98,10 @@ module ExtractsPath # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. # + # If there is no path and the ref doesn't exist in the repo, try to resolve + # the ref without an '.atom' suffix. If _that_ ref is found, set the request's + # format to Atom manually. + # # Automatically renders `not_found!` if a valid tree path could not be # resolved (e.g., when a user inserts an invalid path or ref). def assign_ref_vars @@ -103,6 +119,13 @@ module ExtractsPath @commit = @repo.commit(@options[:extended_sha1]) end + if @path.empty? && !@commit + @id = @ref = extract_ref_without_atom(@id) + @commit = @repo.commit(@ref) + + request.format = :atom if @commit + end + raise InvalidPathError unless @commit @hex_path = Digest::SHA1.hexdigest(@path) @@ -125,4 +148,10 @@ module ExtractsPath id += "/" + params[:path] unless params[:path].blank? id end + + def ref_names + return [] unless @project + + @ref_names ||= @project.repository.ref_names + end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 2518a48e336..1ac7e03a2db 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -10,15 +10,38 @@ describe Projects::CommitsController do end describe "GET show" do - context "as atom feed" do - it "renders as atom" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: "master", - format: "atom") - expect(response).to be_success - expect(response.content_type).to eq('application/atom+xml') + context "when the ref name ends in .atom" do + render_views + + context "when the ref does not exist with the suffix" do + it "renders as atom" do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + + expect(response).to be_success + expect(response.content_type).to eq('application/atom+xml') + end + end + + context "when the ref exists with the suffix" do + before do + commit = project.repository.commit('master') + + allow_any_instance_of(Repository).to receive(:commit).and_call_original + allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit) + + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + end + + it "renders as HTML" do + expect(response).to be_success + expect(response.content_type).to eq('text/html') + end end end end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index e10c1f5c547..0e85e302f29 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -6,6 +6,7 @@ describe ExtractsPath, lib: true do include Gitlab::Routing.url_helpers let(:project) { double('project') } + let(:request) { double('request') } before do @project = project @@ -15,9 +16,10 @@ describe ExtractsPath, lib: true do allow(project).to receive(:repository).and_return(repo) allow(project).to receive(:path_with_namespace). and_return('gitlab/gitlab-ci') + allow(request).to receive(:format=) end - describe '#assign_ref' do + describe '#assign_ref_vars' do let(:ref) { sample_commit[:id] } let(:params) { { path: sample_commit[:line_code_path], ref: ref } } @@ -61,6 +63,75 @@ describe ExtractsPath, lib: true do expect(@id).to eq(get_id) end end + + context 'ref only exists without .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'v1.0.0.atom', path: 'README.md' } } + + it 'renders a 404' do + expect(self).to receive(:render_404) + + assign_ref_vars + end + end + + context 'without a path' do + let(:params) { { ref: 'v1.0.0.atom' } } + before { assign_ref_vars } + + it 'sets the un-suffixed version as @ref' do + expect(@ref).to eq('v1.0.0') + end + + it 'sets the request format to Atom' do + expect(request).to have_received(:format=).with(:atom) + end + end + end + + context 'ref exists with .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'master.atom', path: 'README.md' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + + assign_ref_vars + end + + it 'sets the suffixed version as @ref' do + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to have_received(:format=) + end + end + + context 'without a path' do + let(:params) { { ref: 'master.atom' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + end + + it 'sets the suffixed version as @ref' do + assign_ref_vars + + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to receive(:format=) + + assign_ref_vars + end + end + end end describe '#extract_ref' do @@ -115,4 +186,18 @@ describe ExtractsPath, lib: true do end end end + + describe '#extract_ref_without_atom' do + it 'ignores any matching refs suffixed with atom' do + expect(extract_ref_without_atom('master.atom')).to eq('master') + end + + it 'returns the longest matching ref' do + expect(extract_ref_without_atom('release/app/v1.0.0.atom')).to eq('release/app/v1.0.0') + end + + it 'returns nil if there are no matching refs' do + expect(extract_ref_without_atom('foo.atom')).to eq(nil) + end + end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77842057a10..2322430d212 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -337,7 +337,7 @@ describe Projects::CommitsController, 'routing' do end it 'to #show' do - expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'atom') + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') end end -- cgit v1.2.1 From 75fa98bb93a416aaf16ff832b0bcc98abbbdaa19 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 06:30:50 -0700 Subject: Remove duplicate CHANGELOG entry --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2095e4fc0fd..8c07ce32d08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - - Bump mail_room to v0.8.1 to fix thread cleanup issue - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call -- cgit v1.2.1 From ce8aedf030f0980b3712a3b0090b918fa1451a2d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 15:06:46 +0200 Subject: Add examples of fake tokens to be used in docs [ci skip] --- doc/development/doc_styleguide.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 39b801f761d..0b725cf200c 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -314,6 +314,29 @@ In this case: - different highlighting languages are used for each config in the code block - the [references](#references) guide is used for reconfigure/restart +## Fake tokens + +There may be times where a token is needed to demonstrate an API call using +cURL or a secret variable used in CI. It is strongly advised not to use real +tokens in documentation even if the probability of a token being exploited is +low. + +You can use the following fake tokens as examples. + +| **Token type** | **Token value** | +| --------------------- | --------------------------------- | +| Private user token | `9koXpg98eAheJpvBs5tK` | +| Personal access token | `n671WNGecHugsdEDPsyo` | +| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | +| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | +| Secret CI variable | `Li8j-mLUVA3eZYjPfd_H` | +| Specific Runner token | `yrnZW46BrtBFqM7xDzE7dddd` | +| Shared Runner token | `6Vk7ZsosqQyfreAxXTZr` | +| Trigger token | `be20d8dcc028677c931e04f3871a9b` | +| Webhook secret token | `6XhDroRcYPM5by_h-HLY` | +| Health check token | `Tu7BgjR9qeZTEyRzGG2P` | +| Request profile token | `7VgpS4Ax5utVD2esNstz` | + ## API Here is a list of must-have items. Use them in the exact order that appears -- cgit v1.2.1 From 8caf097a162cc43cea59162600bbb1fbc981f0bc Mon Sep 17 00:00:00 2001 From: henrik Date: Tue, 11 Oct 2016 15:41:10 +0200 Subject: Convert unicode emojis to images. --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 14 +++++- doc/ci/pipelines.md | 2 + doc/ci/variables/README.md | 34 ++++++++++++++ lib/banzai/filter/emoji_filter.rb | 54 ++++++++++++++++++---- lib/gitlab/emoji.rb | 7 ++- spec/lib/banzai/filter/emoji_filter_spec.rb | 71 ++++++++++++++++++++++++++++- spec/models/merge_request_diff_spec.rb | 10 ++++ 8 files changed, 181 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 06dc2993b73..2095e4fc0fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 2492b5213ac..23ae6dfc79a 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -1,6 +1,6 @@ module Banzai module Filter - # HTML filter that replaces :emoji: with images. + # HTML filter that replaces :emoji: and unicode with images. # # Based on HTML::Pipeline::EmojiFilter # @@ -13,14 +13,14 @@ module Banzai def call search_text_nodes(doc).each do |node| content = node.to_html - next unless content.include?(':') next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) + if content.include?(':') || node.text.match(emoji_unicode_pattern) + html = emoji_name_image_filter(content) + html = emoji_unicode_image_filter(html) + next if html == content + node.replace(html) + end - html = emoji_image_filter(content) - - next if html == content - - node.replace(html) end doc @@ -31,18 +31,34 @@ module Banzai # text - String text to replace :emoji: in. # # Returns a String with :emoji: replaced with images. - def emoji_image_filter(text) + def emoji_name_image_filter(text) text.gsub(emoji_pattern) do |match| name = $1 ":#{name}:" end end + + # Replace unicode emojis with corresponding images if they exist. + # + # text - String text to replace unicode emojis in. + # + # Returns a String with unicode emojis replaced with images. + + def emoji_unicode_image_filter(text) + text.gsub(emoji_unicode_pattern) do |moji| + ":#{Gitlab::Emoji.emojis_by_moji[moji][" + end + end # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ end + # Build a regexp that matches all valid unicode emojis names. + def self.emoji_unicode_pattern + @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/ + end private def emoji_url(name) @@ -60,6 +76,21 @@ module Banzai end end + def emoji_unicode_url(moji) + emoji_unicode_path = emoji_unicode_filename(moji) + + if context[:asset_host] + # Asset host is specified. + url_to_image(emoji_unicode_path) + elsif context[:asset_root] + # Gitlab url is specified + File.join(context[:asset_root], url_to_image(emoji_unicode_path)) + else + # All other cases + url_to_image(emoji_unicode_path) + end + end + def url_to_image(image) ActionController::Base.helpers.url_to_image(image) end @@ -71,6 +102,13 @@ module Banzai def emoji_filename(name) "#{Gitlab::Emoji.emoji_filename(name)}.png" end + def emoji_unicode_pattern + self.class.emoji_unicode_pattern + end + + def emoji_unicode_filename(name) + "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png" + end end end end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index b63213ae208..803b0c2d057 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -9,7 +9,9 @@ module Gitlab def emojis_by_moji Gemojione.index.instance_variable_get(:@emoji_by_moji) end - + def emojis_unicodes + emojis_by_moji.keys.sort + end def emojis_names emojis.keys.sort end @@ -17,5 +19,8 @@ module Gitlab def emoji_filename(name) emojis[name]["unicode"] end + def emoji_unicode_filename(moji) + emojis_by_moji[moji]["unicode"] + end end end diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index b5b38cf0c8c..475160bb5ec 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -12,11 +12,14 @@ describe Banzai::Filter::EmojiFilter, lib: true do ActionController::Base.asset_host = @original_asset_host end - it 'replaces supported emoji' do + it 'replaces supported name emoji' do doc = filter('

:heart:

') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end - + it 'replaces supported unicode emoji' do + doc = filter('

❤️

') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' + end it 'ignores unsupported emoji' do exp = act = '

:foo:

' doc = filter(act) @@ -28,46 +31,96 @@ describe Banzai::Filter::EmojiFilter, lib: true do expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' end + it 'correctly encodes unicode to the URL' do + doc = filter('

👍

') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' + end + it 'matches at the start of a string' do doc = filter(':+1:') expect(doc.css('img').size).to eq 1 end + it 'unicode matches at the start of a string' do + doc = filter("'👍'") + expect(doc.css('img').size).to eq 1 + end + it 'matches at the end of a string' do doc = filter('This gets a :-1:') expect(doc.css('img').size).to eq 1 end + it 'unicode matches at the end of a string' do + doc = filter('This gets a 👍') + expect(doc.css('img').size).to eq 1 + end + it 'matches with adjacent text' do doc = filter('+1 (:+1:)') expect(doc.css('img').size).to eq 1 end + it 'unicode matches with adjacent text' do + doc = filter('+1 (👍)') + expect(doc.css('img').size).to eq 1 + end + it 'matches multiple emoji in a row' do doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') expect(doc.css('img').size).to eq 3 end + it 'unicode matches multiple emoji in a row' do + doc = filter("'🙈🙉🙊'") + expect(doc.css('img').size).to eq 3 + end + + it 'mixed matches multiple emoji in a row' do + doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'") + expect(doc.css('img').size).to eq 6 + end + it 'has a title attribute' do doc = filter(':-1:') expect(doc.css('img').first.attr('title')).to eq ':-1:' end + it 'unicode has a title attribute' do + doc = filter("'👎'") + expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:' + end + it 'has an alt attribute' do doc = filter(':-1:') expect(doc.css('img').first.attr('alt')).to eq ':-1:' end + it 'unicode has an alt attribute' do + doc = filter("'👎'") + expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:' + end + it 'has an align attribute' do doc = filter(':8ball:') expect(doc.css('img').first.attr('align')).to eq 'absmiddle' end + it 'unicode has an align attribute' do + doc = filter("'🎱'") + expect(doc.css('img').first.attr('align')).to eq 'absmiddle' + end + it 'has an emoji class' do doc = filter(':cat:') expect(doc.css('img').first.attr('class')).to eq 'emoji' end + it 'unicode has an emoji class' do + doc = filter("'🐱'") + expect(doc.css('img').first.attr('class')).to eq 'emoji' + end + it 'has height and width attributes' do doc = filter(':dog:') img = doc.css('img').first @@ -76,12 +129,26 @@ describe Banzai::Filter::EmojiFilter, lib: true do expect(img.attr('height')).to eq '20' end + it 'unicode has height and width attributes' do + doc = filter("'🐶'") + img = doc.css('img').first + + expect(img.attr('width')).to eq '20' + expect(img.attr('height')).to eq '20' + end + it 'keeps whitespace intact' do doc = filter('This deserves a :+1:, big time.') expect(doc.to_html).to match(/^This deserves a , big time\.\z/) end + it 'unicode keeps whitespace intact' do + doc = filter('This deserves a 🎱, big time.') + + expect(doc.to_html).to match(/^This deserves a , big time\.\z/) + end + it 'uses a custom asset_root context' do root = Gitlab.config.gitlab.url + 'gitlab/root' diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).to be_empty + end + end + context 'when the raw diffs exist' do it 'returns the diffs' do expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) -- cgit v1.2.1 From b4004488f76d7360acd2f38277d617447c76b888 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 4 Oct 2016 15:52:08 +0300 Subject: Make guests unable to view MRs --- CHANGELOG | 1 + app/models/event.rb | 8 ++- app/policies/project_policy.rb | 3 +- app/services/notification_service.rb | 6 ++- app/services/todo_service.rb | 6 +-- doc/user/permissions.md | 1 + .../projects/guest_navigation_menu_spec.rb | 28 +++++++++++ .../security/project/private_access_spec.rb | 2 +- spec/models/event_spec.rb | 11 +++++ spec/policies/project_policy_spec.rb | 5 +- .../services/merge_requests/update_service_spec.rb | 6 +++ spec/services/notification_service_spec.rb | 57 +++++++++++++++++++++- spec/services/todo_service_spec.rb | 22 ++++++++- 13 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 spec/features/projects/guest_navigation_menu_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2095e4fc0fd..9653c4da17f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -93,6 +93,7 @@ v 8.13.0 (unreleased) - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found + - Make guests unable to view MRs on private projects v 8.12.5 (unreleased) - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread diff --git a/app/models/event.rb b/app/models/event.rb index 314d5ba438f..0764cb8cabd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -68,8 +68,10 @@ class Event < ActiveRecord::Base true elsif issue? || issue_note? Ability.allowed?(user, :read_issue, note? ? note_target : target) + elsif merge_request? || merge_request_note? + Ability.allowed?(user, :read_merge_request, note? ? note_target : target) else - ((merge_request? || note?) && target.present?) || milestone? + milestone? end end @@ -280,6 +282,10 @@ class Event < ActiveRecord::Base note? && target && target.for_issue? end + def merge_request_note? + note? && target && target.for_merge_request? + end + def project_snippet_note? target.for_snippet? end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a806cf83782..be4721d7a51 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy can! :read_milestone can! :read_project_snippet can! :read_project_member - can! :read_merge_request can! :read_note can! :create_project can! :create_issue @@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy can! :read_pipeline can! :read_environment can! :read_deployment + can! :read_merge_request end # Permissions given when an user is team member of a project @@ -117,6 +117,7 @@ class ProjectPolicy < BasePolicy can! :read_container_image can! :build_download_code can! :build_read_container_image + can! :read_merge_request end def owner_access! diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index de8049b8e2e..72712afc07e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -475,10 +475,12 @@ class NotificationService end def reject_users_without_access(recipients, target) - return recipients unless target.is_a?(Issue) + return recipients unless target.is_a?(Issuable) + + ability = :"read_#{target.to_ability_name}" recipients.select do |user| - user.can?(:read_issue, target) + user.can?(ability, target) end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 776530ac0a5..f8e6b2ef094 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -273,12 +273,12 @@ class TodoService end def reject_users_without_access(users, project, target) - if target.is_a?(Note) && target.for_issue? + if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?) target = target.noteable end - if target.is_a?(Issue) - select_users(users, :read_issue, target) + if target.is_a?(Issuable) + select_users(users, :"read_#{target.to_ability_name}", target) else select_users(users, :read_project, project) end diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c0dc80325b6..d6216a8dd50 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -32,6 +32,7 @@ The following table depicts the various user permission levels in a project. | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | | See environments | | ✓ | ✓ | ✓ | ✓ | +| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb new file mode 100644 index 00000000000..c22441f8929 --- /dev/null +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "Guest navigation menu" do + let(:project) { create :empty_project, :private } + let(:guest) { create :user } + + before do + project.team << [guest, :guest] + + login_as(guest) + end + + it "shows allowed tabs only" do + visit namespace_project_path(project.namespace, project) + + within(".nav-links") do + expect(page).to have_content 'Project' + expect(page).to have_content 'Activity' + expect(page).to have_content 'Issues' + expect(page).to have_content 'Wiki' + + expect(page).not_to have_content 'Repository' + expect(page).not_to have_content 'Pipelines' + expect(page).not_to have_content 'Graphs' + expect(page).not_to have_content 'Merge Requests' + end + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index ccb5c06dab0..79417c769a8 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -203,7 +203,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for developer } it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index af5002487cc..06cac929bbf 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -135,6 +135,17 @@ describe Event, models: true do it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(guest)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true } + + context 'private project' do + let(:project) { create(:project, :private) } + + it { expect(event.visible_to_user?(non_member)).to eq false } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(guest)).to eq false } + it { expect(event.visible_to_user?(admin)).to eq true } + end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 43c8d884a47..658e3c13a73 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -12,7 +12,7 @@ describe ProjectPolicy, models: true do [ :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, :read_milestone, :read_project_snippet, :read_project_member, - :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :read_note, :create_project, :create_issue, :create_note, :upload_file ] end @@ -21,7 +21,8 @@ describe ProjectPolicy, models: true do [ :download_code, :fork_project, :create_project_snippet, :update_issue, :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, - :read_container_image, :read_pipeline, :read_environment, :read_deployment + :read_container_image, :read_pipeline, :read_environment, :read_deployment, + :read_merge_request ] end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 33db34c0f62..fd5f94047c2 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -17,6 +17,7 @@ describe MergeRequests::UpdateService, services: true do before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -188,6 +189,11 @@ describe MergeRequests::UpdateService, services: true do let!(:non_subscriber) { create(:user) } let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } } + before do + project.team << [non_subscriber, :developer] + project.team << [subscriber, :developer] + end + it 'sends notifications for subscribers of newly added labels' do opts = { label_ids: [label.id] } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d820646ebdf..699b9925b4e 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -331,7 +331,7 @@ describe NotificationService, services: true do describe '#new_note' do it "records sent notifications" do # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note - expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original + expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original notification.new_note(note) @@ -1169,6 +1169,61 @@ describe NotificationService, services: true do end end + context 'guest user in private project' do + let(:private_project) { create(:empty_project, :private) } + let(:guest) { create(:user) } + let(:developer) { create(:user) } + let(:assignee) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: private_project, assignee: assignee) } + let(:merge_request1) { create(:merge_request, source_project: private_project, assignee: assignee, description: "cc @#{guest.username}") } + let(:note) { create(:note, noteable: merge_request, project: private_project) } + + before do + private_project.team << [assignee, :developer] + private_project.team << [developer, :developer] + private_project.team << [guest, :guest] + + ActionMailer::Base.deliveries.clear + end + + it 'filters out guests when new note is created' do + expect(SentNotification).to receive(:record).with(merge_request, any_args).exactly(1).times + + notification.new_note(note) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when new merge request is created' do + notification.new_merge_request(merge_request1, @u_disabled) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is closed' do + notification.close_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is reopened' do + notification.reopen_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is merged' do + notification.merge_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b41f6f14fbd..ed55791d24e 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -345,7 +345,7 @@ describe TodoService, services: true do service.new_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -357,7 +357,7 @@ describe TodoService, services: true do service.update_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -381,6 +381,7 @@ describe TodoService, services: true do should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) end it 'does not raise an error when description not change' do @@ -430,6 +431,11 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) end + + it 'does not create a todo for guests' do + service.reassigned_merge_request(mr_assigned, author) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#merge_merge_request' do @@ -441,6 +447,11 @@ describe TodoService, services: true do expect(first_todo.reload).to be_done expect(second_todo.reload).to be_done end + + it 'does not create todo for guests' do + service.merge_merge_request(mr_assigned, john_doe) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#new_award_emoji' do @@ -495,6 +506,13 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) end + + it 'does not create todo for guests' do + note_on_merge_request = create :note_on_merge_request, project: project, noteable: mr_assigned, note: mentions + service.new_note(note_on_merge_request, author) + + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end end -- cgit v1.2.1 From 1bbe1cea919d174b9bc1bdd39e6235ffadcccb50 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 15:55:32 +0200 Subject: Move health check docs under user/admin_area/monitoring [ci skip] --- doc/README.md | 2 +- doc/administration/monitoring/health_check.md | 66 --------------------- .../monitoring/img/health_check_token.png | Bin 6630 -> 0 bytes doc/monitoring/health_check.md | 2 +- doc/user/admin_area/monitoring/health_check.md | 66 +++++++++++++++++++++ .../monitoring/img/health_check_token.png | Bin 0 -> 6630 bytes 6 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 doc/administration/monitoring/health_check.md delete mode 100644 doc/administration/monitoring/img/health_check_token.png create mode 100644 doc/user/admin_area/monitoring/health_check.md create mode 100644 doc/user/admin_area/monitoring/img/health_check_token.png diff --git a/doc/README.md b/doc/README.md index 04f713a6c5f..effd92b43df 100644 --- a/doc/README.md +++ b/doc/README.md @@ -48,7 +48,7 @@ - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. - [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](administration/monitoring/health_check.md) Check the server status using the health check endpoint. +- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md deleted file mode 100644 index eac57bc3de4..00000000000 --- a/doc/administration/monitoring/health_check.md +++ /dev/null @@ -1,66 +0,0 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/administration/monitoring/img/health_check_token.png b/doc/administration/monitoring/img/health_check_token.png deleted file mode 100644 index 2d7c82a65a8..00000000000 Binary files a/doc/administration/monitoring/img/health_check_token.png and /dev/null differ diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index 23ae1b17258..6cf93c33ec2 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1 +1 @@ -This document was moved to [administration/monitoring/health_check](../administration/monitoring/health_check.md). +This document was moved to [user/admin_area/monitoring/health_check](../user/admin_area/monitoring/health_check.md). diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/user/admin_area/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/user/admin_area/monitoring/img/health_check_token.png b/doc/user/admin_area/monitoring/img/health_check_token.png new file mode 100644 index 00000000000..2d7c82a65a8 Binary files /dev/null and b/doc/user/admin_area/monitoring/img/health_check_token.png differ -- cgit v1.2.1 From dd11c8f5c6984aed434c73197b9b592eee2646e1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 09:10:12 -0500 Subject: Fix overflow to show grouped builds arrow --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 05f59279637..28e850767b7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -421,7 +421,6 @@ right: -197px; top: -9px; max-height: 245px; - overflow-y: scroll; a { color: $gl-text-color; -- cgit v1.2.1 From d8f33c0a51d9106ece6cd4bae469e40734e05f85 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:44:09 +0200 Subject: Move operations/ to new location [ci skip] --- doc/README.md | 2 +- doc/administration/operations.md | 6 + .../operations/cleaning_up_redis_sessions.md | 52 ++++++ .../operations/moving_repositories.md | 180 ++++++++++++++++++++ .../operations/sidekiq_memory_killer.md | 40 +++++ doc/administration/operations/unicorn.md | 86 ++++++++++ doc/operations/README.md | 6 +- doc/operations/cleaning_up_redis_sessions.md | 53 +----- doc/operations/moving_repositories.md | 181 +-------------------- doc/operations/sidekiq_memory_killer.md | 41 +---- doc/operations/unicorn.md | 87 +--------- 11 files changed, 370 insertions(+), 364 deletions(-) create mode 100644 doc/administration/operations.md create mode 100644 doc/administration/operations/cleaning_up_redis_sessions.md create mode 100644 doc/administration/operations/moving_repositories.md create mode 100644 doc/administration/operations/sidekiq_memory_killer.md create mode 100644 doc/administration/operations/unicorn.md diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..ed2d09bedec 100644 --- a/doc/README.md +++ b/doc/README.md @@ -34,7 +34,7 @@ - [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars. - [Log system](administration/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. -- [Operations](operations/README.md) Keeping GitLab up and running. +- [Operations](administration/operations.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. - [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. diff --git a/doc/administration/operations.md b/doc/administration/operations.md new file mode 100644 index 00000000000..4b582d16b64 --- /dev/null +++ b/doc/administration/operations.md @@ -0,0 +1,6 @@ +# GitLab operations + +- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md) +- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) +- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) +- [Moving repositories to a new location](operations/moving_repositories.md) diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md new file mode 100644 index 00000000000..93521e976d5 --- /dev/null +++ b/doc/administration/operations/cleaning_up_redis_sessions.md @@ -0,0 +1,52 @@ +# Cleaning up stale Redis sessions + +Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. +Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. You can also perform a cleanup while +still running GitLab 7.2 or older, but in that case new stale sessions will +start building up again after you clean up. + +In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte +hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with +GitLab 7.3.0, the keys are +prefixed with 'session:gitlab:', so they would look like +'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to +remove the keys in the old format. + +First we define a shell function with the proper Redis connection details. + +``` +rcli() { + # This example works for Omnibus installations of GitLab 7.3 or newer. For an + # installation from source you will have to change the socket path and the + # path to redis-cli. + sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" +} + +# test the new shell function; the response should be PONG +rcli ping +``` + +Now we do a search to see if there are any session keys in the old format for +us to clean up. + +``` +# returns the number of old-format session keys in Redis +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l +``` + +If the number is larger than zero, you can proceed to expire the keys from +Redis. If the number is zero there is nothing to clean up. + +``` +# Tell Redis to expire each matched key after 600 seconds. +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli +# This will print '(integer) 1' for each key that gets expired. +``` + +Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis +background save interval) your Redis database will be compacted. If you are +still using GitLab 7.2, users who are not clicking around in GitLab during the +10 minute expiry window will be signed out of GitLab. diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md new file mode 100644 index 00000000000..54adb99386a --- /dev/null +++ b/doc/administration/operations/moving_repositories.md @@ -0,0 +1,180 @@ +# Moving repositories managed by GitLab + +Sometimes you need to move all repositories managed by GitLab to +another filesystem or another server. In this document we will look +at some of the ways you can copy all your repositories from +`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. + +We will look at three scenarios: the target directory is empty, the +target directory contains an outdated copy of the repositories, and +how to deal with thousands of repositories. + +**Each of the approaches we list can/will overwrite data in the +target directory `/mnt/gitlab/repositories`. Do not mix up the +source and the target.** + +## Target directory is empty: use a tar pipe + +If the target directory `/mnt/gitlab/repositories` is empty the +simplest thing to do is to use a tar pipe. This method has low +overhead and tar is almost always already installed on your system. +However, it is not possible to resume an interrupted tar pipe: if +that happens then all data must be copied again. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to see progress, replace `-xf` with `-xvf`. + +### Tar pipe to another server + +You can also use a tar pipe to copy data to another server. If your +'git' user has SSH access to the newserver as 'git@newserver', you +can pipe the data through SSH. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + ssh git@newserver tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to compress the data before it goes over the network +(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. + +## The target directory contains an outdated copy of the repositories: use rsync + +If the target directory already contains a partial / outdated copy +of the repositories it may be wasteful to copy all the data again +with tar. In this scenario it is better to use rsync. This utility +is either already installed on your system or easily installable +via apt, yum etc. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + /mnt/gitlab/repositories +``` + +The `/.` in the command above is very important, without it you can +easily get the wrong directory structure in the target directory. +If you want to see progress, replace `-a` with `-av`. + +### Single rsync to another server + +If the 'git' user on your source system has SSH access to the target +server you can send the repositories over the network with rsync. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + git@newserver:/mnt/gitlab/repositories +``` + +## Thousands of Git repositories: use one rsync per repository + +Every time you start an rsync job it has to inspect all files in +the source directory, all files in the target directory, and then +decide what files to copy or not. If the source or target directory +has many contents this startup phase of rsync can become a burden +for your GitLab server. In cases like this you can make rsync's +life easier by dividing its work in smaller pieces, and sync one +repository at a time. + +In addition to rsync we will use [GNU +Parallel](http://www.gnu.org/software/parallel/). This utility is +not included in GitLab so you need to install it yourself with apt +or yum. Also note that the GitLab scripts we used below were added +in GitLab 8.1. + +** This process does not clean up repositories at the target location that no +longer exist at the source. ** If you start using your GitLab instance with +`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` +after switching to the new repository storage directory. + +### Parallel rsync for all repositories known to GitLab + +This will sync repositories with 10 rsync processes at a time. We keep +track of progress so that the transfer can be restarted if necessary. + +First we create a new directory, owned by 'git', to hold transfer +logs. We assume the directory is empty before we start the transfer +procedure, and that we are the only ones writing files in it. + +``` +# Omnibus +sudo mkdir /var/opt/gitlab/transfer-logs +sudo chown git:git /var/opt/gitlab/transfer-logs + +# Source +sudo -u git -H mkdir /home/git/transfer-logs +``` + +We seed the process with a list of the directories we want to copy. + +``` +# Omnibus +sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' +``` + +Now we can start the transfer. The command below is idempotent, and +the number of jobs done by GNU Parallel should converge to zero. If it +does not some repositories listed in all-repos-1234.txt may have been +deleted/renamed before they could be copied. + +``` +# Omnibus +sudo -u git sh -c ' +cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories +' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c ' +cat /home/git/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + /home/git/transfer-logs/success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +` +``` + +### Parallel rsync only for repositories with recent activity + +Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. +Then you might only want to sync repositories that were changed via GitLab +_after_ that time. You can use the 'SINCE' variable to tell 'rake +gitlab:list_repos' to only print repositories with recent activity. + +``` +# Omnibus +sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git \ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories + +# Source +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git -H \ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +``` diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md new file mode 100644 index 00000000000..b5e78348989 --- /dev/null +++ b/doc/administration/operations/sidekiq_memory_killer.md @@ -0,0 +1,40 @@ +# Sidekiq MemoryKiller + +The GitLab Rails application code suffers from memory leaks. For web requests +this problem is made manageable using +[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which +restarts Unicorn worker processes in between requests when needed. The Sidekiq +MemoryKiller applies the same approach to the Sidekiq processes used by GitLab +to process background jobs. + +Unlike unicorn-worker-killer, which is enabled by default for all GitLab +installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default +_only_ for Omnibus packages. The reason for this is that the MemoryKiller +relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab +installations from source do not all use Runit or an equivalent. + +With the default settings, the MemoryKiller will cause a Sidekiq restart no +more often than once every 15 minutes, with the restart causing about one +minute of delay for incoming background jobs. + +## Configuring the MemoryKiller + +The MemoryKiller is controlled using environment variables. + +- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is + greater than 0, then after each Sidekiq job, the MemoryKiller will check the + RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq + process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a + delayed shutdown is triggered. The default value for Omnibus packages is set + [in the omnibus-gitlab + repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). +- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When + a shutdown is triggered, the Sidekiq process will keep working normally for + another 15 minutes. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace + time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. + Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells + Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must + restart Sidekiq. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of + the final signal sent to the Sidekiq process when we want it to shut down. diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md new file mode 100644 index 00000000000..bad61151bda --- /dev/null +++ b/doc/administration/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 # 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](https://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 -- : #: 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 # 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](https://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/operations/README.md b/doc/operations/README.md index 6a35dab7b6c..58f16aff7bd 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1,5 +1 @@ -# GitLab operations - -- [Sidekiq MemoryKiller](sidekiq_memory_killer.md) -- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) -- [Understanding Unicorn and unicorn-worker-killer](unicorn.md) +This document was moved to [administration/operations](../administration/operations.md). diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md index 93521e976d5..2a1d0a8c8eb 100644 --- a/doc/operations/cleaning_up_redis_sessions.md +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -1,52 +1 @@ -# Cleaning up stale Redis sessions - -Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. -Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If -you have been running a large GitLab server (thousands of users) since before -GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis -database after you upgrade to GitLab 7.3. You can also perform a cleanup while -still running GitLab 7.2 or older, but in that case new stale sessions will -start building up again after you clean up. - -In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte -hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with -GitLab 7.3.0, the keys are -prefixed with 'session:gitlab:', so they would look like -'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to -remove the keys in the old format. - -First we define a shell function with the proper Redis connection details. - -``` -rcli() { - # This example works for Omnibus installations of GitLab 7.3 or newer. For an - # installation from source you will have to change the socket path and the - # path to redis-cli. - sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" -} - -# test the new shell function; the response should be PONG -rcli ping -``` - -Now we do a search to see if there are any session keys in the old format for -us to clean up. - -``` -# returns the number of old-format session keys in Redis -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l -``` - -If the number is larger than zero, you can proceed to expire the keys from -Redis. If the number is zero there is nothing to clean up. - -``` -# Tell Redis to expire each matched key after 600 seconds. -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli -# This will print '(integer) 1' for each key that gets expired. -``` - -Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis -background save interval) your Redis database will be compacted. If you are -still using GitLab 7.2, users who are not clicking around in GitLab during the -10 minute expiry window will be signed out of GitLab. +This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md). diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md index 54adb99386a..c54bca324a5 100644 --- a/doc/operations/moving_repositories.md +++ b/doc/operations/moving_repositories.md @@ -1,180 +1 @@ -# Moving repositories managed by GitLab - -Sometimes you need to move all repositories managed by GitLab to -another filesystem or another server. In this document we will look -at some of the ways you can copy all your repositories from -`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. - -We will look at three scenarios: the target directory is empty, the -target directory contains an outdated copy of the repositories, and -how to deal with thousands of repositories. - -**Each of the approaches we list can/will overwrite data in the -target directory `/mnt/gitlab/repositories`. Do not mix up the -source and the target.** - -## Target directory is empty: use a tar pipe - -If the target directory `/mnt/gitlab/repositories` is empty the -simplest thing to do is to use a tar pipe. This method has low -overhead and tar is almost always already installed on your system. -However, it is not possible to resume an interrupted tar pipe: if -that happens then all data must be copied again. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to see progress, replace `-xf` with `-xvf`. - -### Tar pipe to another server - -You can also use a tar pipe to copy data to another server. If your -'git' user has SSH access to the newserver as 'git@newserver', you -can pipe the data through SSH. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - ssh git@newserver tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to compress the data before it goes over the network -(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. - -## The target directory contains an outdated copy of the repositories: use rsync - -If the target directory already contains a partial / outdated copy -of the repositories it may be wasteful to copy all the data again -with tar. In this scenario it is better to use rsync. This utility -is either already installed on your system or easily installable -via apt, yum etc. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - /mnt/gitlab/repositories -``` - -The `/.` in the command above is very important, without it you can -easily get the wrong directory structure in the target directory. -If you want to see progress, replace `-a` with `-av`. - -### Single rsync to another server - -If the 'git' user on your source system has SSH access to the target -server you can send the repositories over the network with rsync. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - git@newserver:/mnt/gitlab/repositories -``` - -## Thousands of Git repositories: use one rsync per repository - -Every time you start an rsync job it has to inspect all files in -the source directory, all files in the target directory, and then -decide what files to copy or not. If the source or target directory -has many contents this startup phase of rsync can become a burden -for your GitLab server. In cases like this you can make rsync's -life easier by dividing its work in smaller pieces, and sync one -repository at a time. - -In addition to rsync we will use [GNU -Parallel](http://www.gnu.org/software/parallel/). This utility is -not included in GitLab so you need to install it yourself with apt -or yum. Also note that the GitLab scripts we used below were added -in GitLab 8.1. - -** This process does not clean up repositories at the target location that no -longer exist at the source. ** If you start using your GitLab instance with -`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` -after switching to the new repository storage directory. - -### Parallel rsync for all repositories known to GitLab - -This will sync repositories with 10 rsync processes at a time. We keep -track of progress so that the transfer can be restarted if necessary. - -First we create a new directory, owned by 'git', to hold transfer -logs. We assume the directory is empty before we start the transfer -procedure, and that we are the only ones writing files in it. - -``` -# Omnibus -sudo mkdir /var/opt/gitlab/transfer-logs -sudo chown git:git /var/opt/gitlab/transfer-logs - -# Source -sudo -u git -H mkdir /home/git/transfer-logs -``` - -We seed the process with a list of the directories we want to copy. - -``` -# Omnibus -sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' -``` - -Now we can start the transfer. The command below is idempotent, and -the number of jobs done by GNU Parallel should converge to zero. If it -does not some repositories listed in all-repos-1234.txt may have been -deleted/renamed before they could be copied. - -``` -# Omnibus -sudo -u git sh -c ' -cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories -' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c ' -cat /home/git/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - /home/git/transfer-logs/success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -` -``` - -### Parallel rsync only for repositories with recent activity - -Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. -Then you might only want to sync repositories that were changed via GitLab -_after_ that time. You can use the 'SINCE' variable to tell 'rake -gitlab:list_repos' to only print repositories with recent activity. - -``` -# Omnibus -sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git \ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories - -# Source -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git -H \ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -``` +This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md). diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md index b5e78348989..cf7c3b2e2ed 100644 --- a/doc/operations/sidekiq_memory_killer.md +++ b/doc/operations/sidekiq_memory_killer.md @@ -1,40 +1 @@ -# Sidekiq MemoryKiller - -The GitLab Rails application code suffers from memory leaks. For web requests -this problem is made manageable using -[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which -restarts Unicorn worker processes in between requests when needed. The Sidekiq -MemoryKiller applies the same approach to the Sidekiq processes used by GitLab -to process background jobs. - -Unlike unicorn-worker-killer, which is enabled by default for all GitLab -installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default -_only_ for Omnibus packages. The reason for this is that the MemoryKiller -relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab -installations from source do not all use Runit or an equivalent. - -With the default settings, the MemoryKiller will cause a Sidekiq restart no -more often than once every 15 minutes, with the restart causing about one -minute of delay for incoming background jobs. - -## Configuring the MemoryKiller - -The MemoryKiller is controlled using environment variables. - -- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is - greater than 0, then after each Sidekiq job, the MemoryKiller will check the - RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq - process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a - delayed shutdown is triggered. The default value for Omnibus packages is set - [in the omnibus-gitlab - repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). -- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When - a shutdown is triggered, the Sidekiq process will keep working normally for - another 15 minutes. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace - time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. - Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells - Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must - restart Sidekiq. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of - the final signal sent to the Sidekiq process when we want it to shut down. +This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md). diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index bad61151bda..fbc9697b755 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -1,86 +1 @@ -# 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 # 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](https://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 -- : #: 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 # 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](https://en.wikipedia.org/wiki/Red_herring). +This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md). -- cgit v1.2.1 From fb5a4202062d07d2dbca544f4cfb475a65411716 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 11:42:37 -0300 Subject: Allow projects to have many boards --- app/models/project.rb | 3 +-- spec/models/project_spec.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 74d54e69648..795a456b094 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -65,8 +65,7 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' - - has_one :board, dependent: :destroy + has_many :boards, dependent: :destroy # Project services has_many :services diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dae546a0cdc..3748b1c7f5f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -24,7 +24,7 @@ describe Project, models: true do it { is_expected.to have_one(:slack_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } - it { is_expected.to have_one(:board).dependent(:destroy) } + it { is_expected.to have_many(:boards).dependent(:destroy) } it { is_expected.to have_one(:campfire_service).dependent(:destroy) } it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } -- cgit v1.2.1 From 95a5cc9285a8583988ece697ebdb948730b5db55 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 15:58:28 -0300 Subject: Restrict the number of permitted boards per project to one --- app/models/project.rb | 7 ++++++- spec/models/project_spec.rb | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 795a456b094..30db7ed50b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -16,6 +16,7 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper + NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git' cache_markdown_field :description, pipeline: :description @@ -65,7 +66,7 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' - has_many :boards, dependent: :destroy + has_many :boards, before_add: :validate_board_limit, dependent: :destroy # Project services has_many :services @@ -1338,4 +1339,8 @@ class Project < ActiveRecord::Base shared_projects.any? end + + def validate_board_limit(board) + raise StandardError, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3748b1c7f5f..1b13f1be477 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -94,6 +94,15 @@ describe Project, models: true do end end end + + describe '#boards' do + it 'raises an error when attempting to add more than one board to the project' do + subject.boards.build + + expect { subject.boards.build }.to raise_error(StandardError, 'Number of permitted boards exceeded') + expect(subject.boards.size).to eq 1 + end + end end describe 'modules' do -- cgit v1.2.1 From e46a4aabd925e0182c31976b5d28c38b9a8a0872 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 16:20:21 -0300 Subject: Update Boards::CreateService to handle with the has_many association --- app/services/boards/create_service.rb | 15 ++++++++++----- spec/factories/boards.rb | 5 +++++ spec/factories/projects.rb | 8 -------- spec/services/boards/create_service_spec.rb | 22 ++++++++++------------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index 072a0749285..a9dbd76a44a 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,16 +1,21 @@ module Boards class CreateService < Boards::BaseService def execute - create_board! unless project.board.present? - project.board + if project.boards.empty? + create_board! + else + project.boards.first + end end private def create_board! - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) + board = project.boards.create + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :done) + + board end end end diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index 35c4a0b6f08..ec46146d9b5 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -1,5 +1,10 @@ FactoryGirl.define do factory :board do project factory: :empty_project + + after(:create) do |board| + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :done) + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 331172445e4..719ef17f57e 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -124,12 +124,4 @@ FactoryGirl.define do ) end end - - factory :project_with_board, parent: :empty_project do - after(:create) do |project| - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) - end - end end diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index a1a4dd4c57c..fde807cc410 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -2,33 +2,31 @@ require 'spec_helper' describe Boards::CreateService, services: true do describe '#execute' do + let(:project) { create(:empty_project) } + subject(:service) { described_class.new(project, double) } context 'when project does not have a board' do - let(:project) { create(:empty_project, board: nil) } - it 'creates a new board' do expect { service.execute }.to change(Board, :count).by(1) end it 'creates default lists' do - service.execute + board = service.execute - expect(project.board.lists.size).to eq 2 - expect(project.board.lists.first).to be_backlog - expect(project.board.lists.last).to be_done + expect(board.lists.size).to eq 2 + expect(board.lists.first).to be_backlog + expect(board.lists.last).to be_done end end context 'when project has a board' do - let!(:project) { create(:project_with_board) } - - it 'does not create a new board' do - expect { service.execute }.not_to change(Board, :count) + before do + create(:board, project: project) end - it 'does not create board lists' do - expect { service.execute }.not_to change(project.board.lists, :count) + it 'does not create a new board' do + expect { service.execute }.not_to change(project.boards, :count) end end end -- cgit v1.2.1 From a2c485473a142a5aad2f03abefb4bfafed645995 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 13:54:46 -0300 Subject: Add Boards::Lists::ListService to list lists for a specific board --- app/services/boards/lists/list_service.rb | 9 +++++++++ spec/services/boards/lists/list_service_spec.rb | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 app/services/boards/lists/list_service.rb create mode 100644 spec/services/boards/lists/list_service_spec.rb diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb new file mode 100644 index 00000000000..ff739bc7d9c --- /dev/null +++ b/app/services/boards/lists/list_service.rb @@ -0,0 +1,9 @@ +module Boards + module Lists + class ListService < Boards::BaseService + def execute(board) + board.lists + end + end + end +end diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb new file mode 100644 index 00000000000..4464f80f796 --- /dev/null +++ b/spec/services/boards/lists/list_service_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Boards::Lists::ListService, services: true do + describe '#execute' do + it "returns board's lists" do + project = create(:empty_project) + board = create(:board, project: project) + label = create(:label, project: project) + backlog_list = create(:backlog_list, board: board) + list = create(:list, board: board, label: label) + done_list = create(:done_list, board: board) + + service = described_class.new(project, double) + + expect(service.execute(board)).to eq [backlog_list, list, done_list] + end + end +end -- cgit v1.2.1 From 9110746370c9759402af5087a612cbcfdf667c3c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:00:18 -0300 Subject: Update Boards::Lists::CreateService to create lists for a specific board --- app/services/boards/base_service.rb | 1 - app/services/boards/lists/create_service.rb | 10 +++++----- spec/services/boards/lists/create_service_spec.rb | 14 +++++++------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb index b2069ca825a..7eacacbaf7e 100644 --- a/app/services/boards/base_service.rb +++ b/app/services/boards/base_service.rb @@ -1,5 +1,4 @@ module Boards class BaseService < ::BaseService - delegate :board, to: :project end end diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index b1887820bd4..da6f59c0399 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,23 +1,23 @@ module Boards module Lists class CreateService < Boards::BaseService - def execute + def execute(board) List.transaction do label = project.labels.find(params[:label_id]) - position = next_position + position = next_position(board) - create_list(label, position) + create_list(board, label, position) end end private - def next_position + def next_position(board) max_position = board.lists.movable.maximum(:position) max_position.nil? ? 0 : max_position.succ end - def create_list(label, position) + def create_list(board, label, position) board.lists.create(label: label, list_type: :label, position: position) end end diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index bff9c1fd1fe..e7806add916 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Boards::Lists::CreateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } @@ -11,7 +11,7 @@ describe Boards::Lists::CreateService, services: true do context 'when board lists is empty' do it 'creates a new list at beginning of the list' do - list = service.execute + list = service.execute(board) expect(list.position).to eq 0 end @@ -19,7 +19,7 @@ describe Boards::Lists::CreateService, services: true do context 'when board lists has backlog, and done lists' do it 'creates a new list at beginning of the list' do - list = service.execute + list = service.execute(board) expect(list.position).to eq 0 end @@ -30,7 +30,7 @@ describe Boards::Lists::CreateService, services: true do create(:list, board: board, position: 0) create(:list, board: board, position: 1) - list = service.execute + list = service.execute(board) expect(list.position).to eq 2 end @@ -40,7 +40,7 @@ describe Boards::Lists::CreateService, services: true do it 'creates a new list at end of the label lists' do list1 = create(:list, board: board, position: 0) - list2 = service.execute + list2 = service.execute(board) expect(list1.reload.position).to eq 0 expect(list2.reload.position).to eq 1 @@ -52,7 +52,7 @@ describe Boards::Lists::CreateService, services: true do label = create(:label, name: 'in-development') service = described_class.new(project, user, label_id: label.id) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound) end end end -- cgit v1.2.1 From af87cf7c6ee1778b283ed285cdd7edbaaffc5fa5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:06:34 -0300 Subject: Update Boards::Lists::GenerateService to generate for a specific board --- app/services/boards/lists/generate_service.rb | 8 ++++---- spec/services/boards/lists/generate_service_spec.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 830e386c98b..686e4e4b336 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -1,11 +1,11 @@ module Boards module Lists class GenerateService < Boards::BaseService - def execute + def execute(board) return false unless board.lists.movable.empty? List.transaction do - label_params.each { |params| create_list(params) } + label_params.each { |params| create_list(board, params) } end true @@ -13,9 +13,9 @@ module Boards private - def create_list(params) + def create_list(board, params) label = find_or_create_label(params) - Lists::CreateService.new(project, current_user, label_id: label.id).execute + Lists::CreateService.new(project, current_user, label_id: label.id).execute(board) end def find_or_create_label(params) diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 4171e4d816c..8b2f5e81338 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -2,15 +2,15 @@ require 'spec_helper' describe Boards::Lists::GenerateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } subject(:service) { described_class.new(project, user) } context 'when board lists is empty' do it 'creates the default lists' do - expect { service.execute }.to change(board.lists, :count).by(2) + expect { service.execute(board) }.to change(board.lists, :count).by(2) end end @@ -18,13 +18,13 @@ describe Boards::Lists::GenerateService, services: true do it 'does not creates the default lists' do create(:list, board: board) - expect { service.execute }.not_to change(board.lists, :count) + expect { service.execute(board) }.not_to change(board.lists, :count) end end context 'when project labels does not contains any list label' do it 'creates labels' do - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute(board) }.to change(project.labels, :count).by(2) end end @@ -32,7 +32,7 @@ describe Boards::Lists::GenerateService, services: true do it 'creates the missing labels' do create(:label, project: project, name: 'Doing') - expect { service.execute }.to change(project.labels, :count).by(1) + expect { service.execute(board) }.to change(project.labels, :count).by(1) end end end -- cgit v1.2.1 From 1a4b1e9735c8b3502d54f1e9ecbce0c3f235f5c7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:13:43 -0300 Subject: Update Boards::Lists::MoveService to move lists inside a specific board --- app/services/boards/lists/move_service.rb | 3 ++- spec/services/boards/lists/move_service_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb index 020ff69f4a7..7d0730e8332 100644 --- a/app/services/boards/lists/move_service.rb +++ b/app/services/boards/lists/move_service.rb @@ -2,6 +2,7 @@ module Boards module Lists class MoveService < Boards::BaseService def execute(list) + @board = list.board @old_position = list.position @new_position = params[:position] @@ -16,7 +17,7 @@ module Boards private - attr_reader :old_position, :new_position + attr_reader :board, :old_position, :new_position def valid_move? new_position.present? && new_position != old_position && diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 102ed67449d..63fa0bb8c5f 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' describe Boards::Lists::MoveService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:planning) { create(:list, board: board, position: 0) } let!(:development) { create(:list, board: board, position: 1) } let!(:review) { create(:list, board: board, position: 2) } let!(:staging) { create(:list, board: board, position: 3) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } context 'when list type is set to label' do it 'keeps position of lists when new position is nil' do -- cgit v1.2.1 From 1fa3f30811c3599a2f060803b201441046926b87 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:26:28 -0300 Subject: Update Boards::Issues::ListService to list issues for a board list --- app/services/boards/issues/list_service.rb | 4 +++ spec/services/boards/issues/list_service_spec.rb | 31 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 435a8c6e681..782dbf0db1a 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -10,6 +10,10 @@ module Boards private + def board + @board ||= project.boards.find(params[:board_id]) + end + def list @list ||= board.lists.find(params[:id]) end diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 5b9f454fd2d..cc96fd05189 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -4,7 +4,7 @@ describe Boards::Issues::ListService, services: true do describe '#execute' do let(:user) { create(:user) } let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } @@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) } let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) } @@ -37,7 +37,7 @@ describe Boards::Issues::ListService, services: true do end it 'delegates search to IssuesFinder' do - params = { id: list1.id } + params = { board_id: board.id, id: list1.id } expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original @@ -46,7 +46,7 @@ describe Boards::Issues::ListService, services: true do context 'sets default order to priority' do it 'returns opened issues when listing issues from Backlog' do - params = { id: backlog.id } + params = { board_id: board.id, id: backlog.id } issues = described_class.new(project, user, params).execute @@ -54,7 +54,7 @@ describe Boards::Issues::ListService, services: true do end it 'returns closed issues when listing issues from Done' do - params = { id: done.id } + params = { board_id: board.id, id: done.id } issues = described_class.new(project, user, params).execute @@ -62,12 +62,29 @@ describe Boards::Issues::ListService, services: true do end it 'returns opened issues that have label list applied when listing issues from a label list' do - params = { id: list1.id } + params = { board_id: board.id, id: list1.id } issues = described_class.new(project, user, params).execute expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] end end + + context 'with list that does not belongs to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(project, user, board_id: board.id, id: list.id) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(project, user, board_id: board.id, id: nil) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end end end -- cgit v1.2.1 From 104c4f88cdc5aaa40334c83112f7c994ff7128c6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:32:53 -0300 Subject: Update Boards::Issues::MoveService to move issues on a specific board --- app/services/boards/issues/move_service.rb | 4 ++++ spec/services/boards/issues/move_service_spec.rb | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 84dc3f70e76..24f6f2c7025 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -10,6 +10,10 @@ module Boards private + def board + @board ||= project.boards.find(params[:board_id]) + end + def valid_move? moving_from_list.present? && moving_to_list.present? && moving_from_list != moving_to_list diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 180f1b08631..9cf5a17e128 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe Boards::Issues::MoveService, services: true do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } before do project.team << [user, :developer] @@ -22,7 +22,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog' do it 'adds the label of the list it goes to' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { from_list_id: backlog.id, to_list_id: list1.id } + params = { board_id: board.id, from_list_id: backlog.id, to_list_id: list1.id } described_class.new(project, user, params).execute(issue) @@ -33,7 +33,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to backlog' do it 'removes all list-labels' do issue = create(:labeled_issue, project: project, labels: [bug, development, testing]) - params = { from_list_id: list1.id, to_list_id: backlog.id } + params = { board_id: board.id, from_list_id: list1.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) @@ -44,7 +44,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog to done' do it 'closes the issue' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { from_list_id: backlog.id, to_list_id: done.id } + params = { board_id: board.id, from_list_id: backlog.id, to_list_id: done.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -56,7 +56,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { from_list_id: list1.id, to_list_id: list2.id } } + let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list2.id } } it 'delegates the label changes to Issues::UpdateService' do expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once @@ -73,7 +73,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to done' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing]) } - let(:params) { { from_list_id: list2.id, to_list_id: done.id } } + let(:params) { { board_id: board.id, from_list_id: list2.id, to_list_id: done.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -92,7 +92,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board.id, from_list_id: done.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -112,7 +112,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done to backlog' do it 'reopens the issue' do issue = create(:labeled_issue, :closed, project: project, labels: [bug]) - params = { from_list_id: done.id, to_list_id: backlog.id } + params = { board_id: board.id, from_list_id: done.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -124,7 +124,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to same list' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { from_list_id: list1.id, to_list_id: list1.id } } + let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list1.id } } it 'returns false' do expect(described_class.new(project, user, params).execute(issue)).to eq false -- cgit v1.2.1 From 8b15e328a60f050d4ff24c0c7203461ae2724a30 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:50:44 -0300 Subject: Removes all labels from project boards when moving and issue to done --- app/services/boards/issues/move_service.rb | 2 +- spec/services/boards/issues/move_service_spec.rb | 34 +++++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 24f6f2c7025..9a16609a8b5 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -53,7 +53,7 @@ module Boards if moving_to_list.movable? moving_from_list.label_id else - board.lists.movable.pluck(:label_id) + project.boards.joins(:lists).merge(List.movable).pluck(:label_id) end Array(label_ids).compact diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 9cf5a17e128..c43b2aec490 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -4,16 +4,16 @@ describe Boards::Issues::MoveService, services: true do describe '#execute' do let(:user) { create(:user) } let(:project) { create(:empty_project) } - let(:board) { create(:board, project: project) } + let(:board1) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } - let!(:backlog) { create(:backlog_list, board: board) } - let!(:list1) { create(:list, board: board, label: development, position: 0) } - let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { create(:done_list, board: board) } + let!(:backlog) { create(:backlog_list, board: board1) } + let!(:list1) { create(:list, board: board1, label: development, position: 0) } + let!(:list2) { create(:list, board: board1, label: testing, position: 1) } + let!(:done) { create(:done_list, board: board1) } before do project.team << [user, :developer] @@ -22,7 +22,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog' do it 'adds the label of the list it goes to' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: backlog.id, to_list_id: list1.id } + params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: list1.id } described_class.new(project, user, params).execute(issue) @@ -33,7 +33,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to backlog' do it 'removes all list-labels' do issue = create(:labeled_issue, project: project, labels: [bug, development, testing]) - params = { board_id: board.id, from_list_id: list1.id, to_list_id: backlog.id } + params = { board_id: board1.id, from_list_id: list1.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) @@ -44,7 +44,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog to done' do it 'closes the issue' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: backlog.id, to_list_id: done.id } + params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: done.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -56,7 +56,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } it 'delegates the label changes to Issues::UpdateService' do expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once @@ -72,8 +72,12 @@ describe Boards::Issues::MoveService, services: true do end context 'when moving to done' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing]) } - let(:params) { { board_id: board.id, from_list_id: list2.id, to_list_id: done.id } } + let(:board2) { create(:board, project: project) } + let(:regression) { create(:label, project: project, name: 'Regression') } + let!(:list3) { create(:list, board: board2, label: regression, position: 1) } + + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: done.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -81,7 +85,7 @@ describe Boards::Issues::MoveService, services: true do described_class.new(project, user, params).execute(issue) end - it 'removes all list-labels and close the issue' do + it 'removes all list-labels from project boards and close the issue' do described_class.new(project, user, params).execute(issue) issue.reload @@ -92,7 +96,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { board_id: board.id, from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: done.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -112,7 +116,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done to backlog' do it 'reopens the issue' do issue = create(:labeled_issue, :closed, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: done.id, to_list_id: backlog.id } + params = { board_id: board1.id, from_list_id: done.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -124,7 +128,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to same list' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } it 'returns false' do expect(described_class.new(project, user, params).execute(issue)).to eq false -- cgit v1.2.1 From b4b8e0ec9405c4b5d17b53552612397e847e734d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 16:01:06 -0300 Subject: Add Boards::ListService service to list project boards --- app/services/boards/list_service.rb | 14 ++++++++++++ spec/services/boards/list_service_spec.rb | 37 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 app/services/boards/list_service.rb create mode 100644 spec/services/boards/list_service_spec.rb diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb new file mode 100644 index 00000000000..cca4bdd82a2 --- /dev/null +++ b/app/services/boards/list_service.rb @@ -0,0 +1,14 @@ +module Boards + class ListService < Boards::BaseService + def execute + create_board! if project.boards.empty? + project.boards + end + + private + + def create_board! + Boards::CreateService.new(project, current_user).execute + end + end +end diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb new file mode 100644 index 00000000000..dff33e4bcbb --- /dev/null +++ b/spec/services/boards/list_service_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Boards::ListService, services: true do + describe '#execute' do + let(:project) { create(:empty_project) } + + subject(:service) { described_class.new(project, double) } + + context 'when project does not have a board' do + it 'creates a new project board' do + expect { service.execute }.to change(project.boards, :count).by(1) + end + + it 'delegates the project board creation to Boards::CreateService' do + expect_any_instance_of(Boards::CreateService).to receive(:execute).once + + service.execute + end + end + + context 'when project has a board' do + before do + create(:board, project: project) + end + + it 'does not create a new board' do + expect { service.execute }.not_to change(project.boards, :count) + end + end + + it 'returns project boards' do + board = create(:board, project: project) + + expect(service.execute).to match_array [board] + end + end +end -- cgit v1.2.1 From ecf4c10e9c395604583820ad01167db34d09d4aa Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 16:24:29 -0300 Subject: Add index action to Projects::BoardsController to return project boards --- app/controllers/projects/boards_controller.rb | 15 +++++-- app/views/projects/boards/index.html.haml | 0 config/routes/project.rb | 2 +- .../controllers/projects/boards_controller_spec.rb | 47 ++++++++++++++++++++++ spec/fixtures/api/schemas/board.json | 12 ++++++ spec/fixtures/api/schemas/boards.json | 4 ++ 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 app/views/projects/boards/index.html.haml create mode 100644 spec/fixtures/api/schemas/board.json create mode 100644 spec/fixtures/api/schemas/boards.json diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 0035633b774..56bc54fbd1c 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -1,9 +1,18 @@ class Projects::BoardsController < Projects::ApplicationController include IssuableCollections - - respond_to :html - before_action :authorize_read_board!, only: [:show] + before_action :authorize_read_board!, only: [:index, :show] + + def index + @boards = ::Boards::ListService.new(project, current_user).execute + + respond_to do |format| + format.html + format.json do + render json: @boards.as_json(only: [:id, :name]) + end + end + end def show ::Boards::CreateService.new(project, current_user).execute diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..3a7c4f86301 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -416,7 +416,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resource :board, only: [:show] do + resources :boards, only: [:index, :show] do scope module: :boards do resources :issues, only: [:update] diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 6f6e608e1f3..d7698afd141 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -9,6 +9,53 @@ describe Projects::BoardsController do sign_in(user) end + describe 'GET index' do + it 'creates a new project board when project does not have one' do + expect { list_boards }.to change(project.boards, :count).by(1) + end + + context 'when format is HTML' do + it 'renders template' do + list_boards + + expect(response).to render_template :index + expect(response.content_type).to eq 'text/html' + end + end + + context 'when format is JSON' do + it 'returns a list of project boards' do + create_list(:board, 2, project: project) + + list_boards format: :json + + parsed_response = JSON.parse(response.body) + + expect(response).to match_response_schema('boards') + expect(parsed_response.length).to eq 2 + end + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end + + it 'returns a not found 404 response' do + list_boards + + expect(response).to have_http_status(404) + end + end + + def list_boards(format: :html) + get :index, namespace_id: project.namespace.to_param, + project_id: project.to_param, + format: format + end + end + describe 'GET show' do it 'creates a new board when project does not have one' do expect { read_board }.to change(Board, :count).by(1) diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json new file mode 100644 index 00000000000..6c6e2bee8cb --- /dev/null +++ b/spec/fixtures/api/schemas/board.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required" : [ + "id", + "name" + ], + "properties" : { + "id": { "type": "integer" }, + "name": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/boards.json b/spec/fixtures/api/schemas/boards.json new file mode 100644 index 00000000000..117564ef77a --- /dev/null +++ b/spec/fixtures/api/schemas/boards.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "board.json" } +} -- cgit v1.2.1 From 723ed9cc3a76f7ce0e2d1b358a33d05fb05865c9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 17:08:55 -0300 Subject: Update Projects::BoardsController#show to look up for a specific board --- app/controllers/projects/boards_controller.rb | 15 +++++++-- .../controllers/projects/boards_controller_spec.rb | 37 ++++++++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 56bc54fbd1c..bbb1b1bf1e1 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -9,13 +9,20 @@ class Projects::BoardsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @boards.as_json(only: [:id, :name]) + render json: serialize_as_json(@boards) end end end def show - ::Boards::CreateService.new(project, current_user).execute + @board = project.boards.find(params[:id]) + + respond_to do |format| + format.html + format.json do + render json: serialize_as_json(@board) + end + end end private @@ -23,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController def authorize_read_board! return access_denied! unless can?(current_user, :read_board, project) end + + def serialize_as_json(resource) + resource.as_json(only: [:id, :name]) + end end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index d7698afd141..cc19035740e 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -57,15 +57,23 @@ describe Projects::BoardsController do end describe 'GET show' do - it 'creates a new board when project does not have one' do - expect { read_board }.to change(Board, :count).by(1) + let!(:board) { create(:board, project: project) } + + context 'when format is HTML' do + it 'renders template' do + read_board board: board + + expect(response).to render_template :show + expect(response.content_type).to eq 'text/html' + end end - it 'renders HTML template' do - read_board + context 'when format is JSON' do + it 'returns project board' do + read_board board: board, format: :json - expect(response).to render_template :show - expect(response.content_type).to eq 'text/html' + expect(response).to match_response_schema('board') + end end context 'with unauthorized user' do @@ -74,16 +82,27 @@ describe Projects::BoardsController do allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) end - it 'returns a successful 404 response' do - read_board + it 'returns a not found 404 response' do + read_board board: board + + expect(response).to have_http_status(404) + end + end + + context 'when board does not belong to project' do + it 'returns a not found 404 response' do + another_board = create(:board) + + read_board board: another_board expect(response).to have_http_status(404) end end - def read_board(format: :html) + def read_board(board:, format: :html) get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, + id: board.to_param, format: format end end -- cgit v1.2.1 From 5de4fba6e34ebf31dc6e737bf96dbd65d3daf958 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 19:24:42 -0300 Subject: Update Boards::Lists::DestroyService to remove list on a specic board --- app/services/boards/lists/destroy_service.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb index 25da3bfb56d..d75c5fd3dc6 100644 --- a/app/services/boards/lists/destroy_service.rb +++ b/app/services/boards/lists/destroy_service.rb @@ -4,6 +4,8 @@ module Boards def execute(list) return false unless list.destroyable? + @board = list.board + list.with_lock do decrement_higher_lists(list) remove_list(list) @@ -12,6 +14,8 @@ module Boards private + attr_reader :board + def decrement_higher_lists(list) board.lists.movable.where('position > ?', list.position) .update_all('position = position - 1') -- cgit v1.2.1 From e1f889df6463203e5bd899fca8e98de6b705cd43 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 19:25:29 -0300 Subject: Update endpoints to handle with board list changes --- .../projects/boards/lists_controller.rb | 18 ++++--- .../projects/boards/lists_controller_spec.rb | 57 ++++++++++++---------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index b995f586737..76ae41319c4 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -5,11 +5,11 @@ module Projects before_action :authorize_read_list!, only: [:index] def index - render json: serialize_as_json(project.board.lists) + render json: serialize_as_json(board.lists) end def create - list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute + list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board) if list.valid? render json: serialize_as_json(list) @@ -19,7 +19,7 @@ module Projects end def update - list = project.board.lists.movable.find(params[:id]) + list = board.lists.movable.find(params[:id]) service = ::Boards::Lists::MoveService.new(project, current_user, move_params) if service.execute(list) @@ -30,8 +30,8 @@ module Projects end def destroy - list = project.board.lists.destroyable.find(params[:id]) - service = ::Boards::Lists::DestroyService.new(project, current_user, params) + list = board.lists.destroyable.find(params[:id]) + service = ::Boards::Lists::DestroyService.new(project, current_user) if service.execute(list) head :ok @@ -43,8 +43,8 @@ module Projects def generate service = ::Boards::Lists::GenerateService.new(project, current_user) - if service.execute - render json: serialize_as_json(project.board.lists.movable) + if service.execute(board) + render json: serialize_as_json(board.lists.movable) else head :unprocessable_entity end @@ -60,6 +60,10 @@ module Projects return render_403 unless can?(current_user, :read_list, project) end + def board + @board ||= project.boards.find(params[:board_id]) + end + def list_params params.require(:list).permit(:label_id) end diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index 709006a3601..a53a5feef44 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Boards::ListsController do describe 'GET index' do it 'returns a successful 200 response' do - read_board_list user: user + read_board_list user: user, board: board expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/json' @@ -22,7 +22,7 @@ describe Projects::Boards::ListsController do it 'returns a list of board lists' do create(:list, board: board) - read_board_list user: user + read_board_list user: user, board: board parsed_response = JSON.parse(response.body) @@ -37,17 +37,18 @@ describe Projects::Boards::ListsController do end it 'returns a forbidden 403 response' do - read_board_list user: user + read_board_list user: user, board: board expect(response).to have_http_status(403) end end - def read_board_list(user:) + def read_board_list(user:, board:) sign_in(user) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, format: :json end end @@ -57,13 +58,13 @@ describe Projects::Boards::ListsController do let(:label) { create(:label, project: project, name: 'Development') } it 'returns a successful 200 response' do - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to have_http_status(200) end it 'returns the created list' do - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to match_response_schema('list') end @@ -72,7 +73,7 @@ describe Projects::Boards::ListsController do context 'with invalid params' do context 'when label is nil' do it 'returns a not found 404 response' do - create_board_list user: user, label_id: nil + create_board_list user: user, board: board, label_id: nil expect(response).to have_http_status(404) end @@ -82,7 +83,7 @@ describe Projects::Boards::ListsController do it 'returns a not found 404 response' do label = create(:label, name: 'Development') - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to have_http_status(404) end @@ -93,17 +94,18 @@ describe Projects::Boards::ListsController do it 'returns a forbidden 403 response' do label = create(:label, project: project, name: 'Development') - create_board_list user: guest, label_id: label.id + create_board_list user: guest, board: board, label_id: label.id expect(response).to have_http_status(403) end end - def create_board_list(user:, label_id:) + def create_board_list(user:, board:, label_id:) sign_in(user) post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, list: { label_id: label_id }, format: :json end @@ -115,13 +117,13 @@ describe Projects::Boards::ListsController do context 'with valid position' do it 'returns a successful 200 response' do - move user: user, list: planning, position: 1 + move user: user, board: board, list: planning, position: 1 expect(response).to have_http_status(200) end it 'moves the list to the desired position' do - move user: user, list: planning, position: 1 + move user: user, board: board, list: planning, position: 1 expect(planning.reload.position).to eq 1 end @@ -129,7 +131,7 @@ describe Projects::Boards::ListsController do context 'with invalid position' do it 'returns an unprocessable entity 422 response' do - move user: user, list: planning, position: 6 + move user: user, board: board, list: planning, position: 6 expect(response).to have_http_status(422) end @@ -137,7 +139,7 @@ describe Projects::Boards::ListsController do context 'with invalid list id' do it 'returns a not found 404 response' do - move user: user, list: 999, position: 1 + move user: user, board: board, list: 999, position: 1 expect(response).to have_http_status(404) end @@ -145,17 +147,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - move user: guest, list: planning, position: 6 + move user: guest, board: board, list: planning, position: 6 expect(response).to have_http_status(403) end end - def move(user:, list:, position:) + def move(user:, board:, list:, position:) sign_in(user) patch :update, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: list.to_param, list: { position: position }, format: :json @@ -167,19 +170,19 @@ describe Projects::Boards::ListsController do context 'with valid list id' do it 'returns a successful 200 response' do - remove_board_list user: user, list: planning + remove_board_list user: user, board: board, list: planning expect(response).to have_http_status(200) end it 'removes list from board' do - expect { remove_board_list user: user, list: planning }.to change(board.lists, :size).by(-1) + expect { remove_board_list user: user, board: board, list: planning }.to change(board.lists, :size).by(-1) end end context 'with invalid list id' do it 'returns a not found 404 response' do - remove_board_list user: user, list: 999 + remove_board_list user: user, board: board, list: 999 expect(response).to have_http_status(404) end @@ -187,17 +190,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - remove_board_list user: guest, list: planning + remove_board_list user: guest, board: board, list: planning expect(response).to have_http_status(403) end end - def remove_board_list(user:, list:) + def remove_board_list(user:, board:, list:) sign_in(user) delete :destroy, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: list.to_param, format: :json end @@ -206,13 +210,13 @@ describe Projects::Boards::ListsController do describe 'POST generate' do context 'when board lists is empty' do it 'returns a successful 200 response' do - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to have_http_status(200) end it 'returns the defaults lists' do - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to match_response_schema('lists') end @@ -222,7 +226,7 @@ describe Projects::Boards::ListsController do it 'returns an unprocessable entity 422 response' do create(:list, board: board) - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to have_http_status(422) end @@ -230,17 +234,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - generate_default_board_lists user: guest + generate_default_lists user: guest, board: board expect(response).to have_http_status(403) end end - def generate_default_board_lists(user:) + def generate_default_lists(user:, board: board) sign_in(user) post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, format: :json end end -- cgit v1.2.1 From 67515098657704505935119503ef23e45f0fda04 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:44:44 -0300 Subject: Update endpoints to handle with board issues --- .../projects/boards/issues_controller.rb | 4 +- .../projects/boards/issues_controller_spec.rb | 53 ++++++++++++++++------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 095af6c35eb..82c99e4e8d9 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -60,11 +60,11 @@ module Projects end def filter_params - params.merge(id: params[:list_id]) + params.merge(board_id: params[:board_id], id: params[:list_id]) end def move_params - params.permit(:id, :from_list_id, :to_list_id) + params.permit(:board_id, :id, :from_list_id, :to_list_id) end def issue_params diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 566658b508d..92e77291da9 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -1,15 +1,16 @@ require 'spec_helper' describe Projects::Boards::IssuesController do - let(:project) { create(:project_with_board) } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } let(:planning) { create(:label, project: project, name: 'Planning') } let(:development) { create(:label, project: project, name: 'Development') } - let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } - let!(:list2) { create(:list, board: project.board, label: development, position: 1) } + let!(:list1) { create(:list, board: board, label: planning, position: 0) } + let!(:list2) { create(:list, board: board, label: development, position: 1) } before do project.team << [user, :master] @@ -24,7 +25,7 @@ describe Projects::Boards::IssuesController do create(:labeled_issue, project: project, labels: [development]) create(:labeled_issue, project: project, labels: [development], assignee: johndoe) - list_issues user: user, list_id: list2 + list_issues user: user, board: board, list: list2 parsed_response = JSON.parse(response.body) @@ -33,9 +34,17 @@ describe Projects::Boards::IssuesController do end end + context 'with invalid board id' do + it 'returns a not found 404 response' do + list_issues user: user, board: 999, list: list2 + + expect(response).to have_http_status(404) + end + end + context 'with invalid list id' do it 'returns a not found 404 response' do - list_issues user: user, list_id: 999 + list_issues user: user, board: board, list: 999 expect(response).to have_http_status(404) end @@ -47,19 +56,20 @@ describe Projects::Boards::IssuesController do allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false) end - it 'returns a successful 403 response' do - list_issues user: user, list_id: list2 + it 'returns a forbidden 403 response' do + list_issues user: user, board: board, list: list2 expect(response).to have_http_status(403) end end - def list_issues(user:, list_id:) + def list_issues(user:, board:, list:) sign_in(user) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, - list_id: list_id.to_param + board_id: board.to_param, + list_id: list.to_param end end @@ -122,13 +132,13 @@ describe Projects::Boards::IssuesController do context 'with valid params' do it 'returns a successful 200 response' do - move user: user, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(200) end it 'moves issue to the desired list' do - move user: user, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(issue.reload.labels).to contain_exactly(development) end @@ -136,31 +146,44 @@ describe Projects::Boards::IssuesController do context 'with invalid params' do it 'returns a unprocessable entity 422 response for invalid lists' do - move user: user, issue: issue, from_list_id: nil, to_list_id: nil + move user: user, board: board, issue: issue, from_list_id: nil, to_list_id: nil expect(response).to have_http_status(422) end + it 'returns a not found 404 response for invalid board id' do + move user: user, board: 999, issue: issue, from_list_id: list1.id, to_list_id: list2.id + + expect(response).to have_http_status(404) + end + it 'returns a not found 404 response for invalid issue id' do - move user: user, issue: 999, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: 999, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(404) end end context 'with unauthorized user' do + let(:guest) { create(:user) } + + before do + project.team << [guest, :guest] + end + it 'returns a forbidden 403 response' do - move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: guest, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(403) end end - def move(user:, issue:, from_list_id:, to_list_id:) + def move(user:, board:, issue:, from_list_id:, to_list_id:) sign_in(user) patch :update, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: issue.to_param, from_list_id: from_list_id, to_list_id: to_list_id, -- cgit v1.2.1 From a88ed81fb97cdd04b3ca222a534f8319319d7091 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:45:54 -0300 Subject: Remove unused Projects::BoardListsController controller --- app/controllers/projects/board_lists_controller.rb | 65 ---------------------- 1 file changed, 65 deletions(-) delete mode 100644 app/controllers/projects/board_lists_controller.rb diff --git a/app/controllers/projects/board_lists_controller.rb b/app/controllers/projects/board_lists_controller.rb deleted file mode 100644 index 3cfb08d5822..00000000000 --- a/app/controllers/projects/board_lists_controller.rb +++ /dev/null @@ -1,65 +0,0 @@ -class Projects::BoardListsController < Projects::ApplicationController - respond_to :json - - before_action :authorize_admin_list! - - rescue_from ActiveRecord::RecordNotFound, with: :record_not_found - - def create - list = Boards::Lists::CreateService.new(project, current_user, list_params).execute - - if list.valid? - render json: list.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } }) - else - render json: list.errors, status: :unprocessable_entity - end - end - - def update - service = Boards::Lists::MoveService.new(project, current_user, move_params) - - if service.execute - head :ok - else - head :unprocessable_entity - end - end - - def destroy - service = Boards::Lists::DestroyService.new(project, current_user, params) - - if service.execute - head :ok - else - head :unprocessable_entity - end - end - - def generate - service = Boards::Lists::GenerateService.new(project, current_user) - - if service.execute - render json: project.board.lists.label.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } }) - else - head :unprocessable_entity - end - end - - private - - def authorize_admin_list! - return render_403 unless can?(current_user, :admin_list, project) - end - - def list_params - params.require(:list).permit(:label_id) - end - - def move_params - params.require(:list).permit(:position).merge(id: params[:id]) - end - - def record_not_found(exception) - render json: { error: exception.message }, status: :not_found - end -end -- cgit v1.2.1 From 38cece495715df191fcb0c4ec0c5c2e44da2debf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:50:35 -0300 Subject: Fix rubocop offenses --- .../projects/boards/lists_controller_spec.rb | 2 +- spec/services/boards/issues/list_service_spec.rb | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index a53a5feef44..afede0191dc 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -240,7 +240,7 @@ describe Projects::Boards::ListsController do end end - def generate_default_lists(user:, board: board) + def generate_default_lists(user:, board:) sign_in(user) post :generate, namespace_id: project.namespace.to_param, diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index cc96fd05189..218a6d2bc2f 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -70,21 +70,21 @@ describe Boards::Issues::ListService, services: true do end end - context 'with list that does not belongs to the board' do - it 'raises an error' do - list = create(:list) - service = described_class.new(project, user, board_id: board.id, id: list.id) + context 'with list that does not belongs to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(project, user, board_id: board.id, id: list.id) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) end + end - context 'with invalid list id' do - it 'raises an error' do - service = described_class.new(project, user, board_id: board.id, id: nil) + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(project, user, board_id: board.id, id: nil) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) end + end end end -- cgit v1.2.1 From 9dc4ebc8ea5cd223b17cbc1d772348bebc50cdeb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 14:35:58 -0300 Subject: Fix links to issue boards --- app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/boards/show.html.haml | 2 +- app/views/projects/issues/_head.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e44a2bfed9d..99a58bbb676 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -116,4 +116,4 @@ -# Shortcut to issue boards %li.hidden - = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' + = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index edbbd3f3d2a..5b441b48caf 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 509b01c548a..4825820c4d9 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -10,7 +10,7 @@ Issues = nav_link(controller: :boards) do - = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do + = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do %span Board -- cgit v1.2.1 From 81c253ded12c96bfa3f5cf27f0e23c61619ad914 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 14:37:41 -0300 Subject: Add Project#boards to import/export configuration file --- spec/lib/gitlab/import_export/all_models.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 006569254a6..5d5836e9bee 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -118,7 +118,7 @@ project: - creator - group - namespace -- board +- boards - last_event - services - campfire_service -- cgit v1.2.1 From 2c2a1dea67ef41a6e283c4816865645fba318a16 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:13:33 -0300 Subject: Refactoring service to create a new issue in a board list --- app/controllers/projects/boards/issues_controller.rb | 5 ++--- app/services/boards/issues/create_service.rb | 15 +++++++++++---- .../controllers/projects/boards/issues_controller_spec.rb | 13 +++++++------ spec/services/boards/issues/create_service_spec.rb | 12 ++++++------ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 82c99e4e8d9..71eb56aed0b 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -16,9 +16,8 @@ module Projects end def create - list = project.board.lists.find(params[:list_id]) service = ::Boards::Issues::CreateService.new(project, current_user, issue_params) - issue = service.execute(list) + issue = service.execute if issue.valid? render json: serialize_as_json(issue) @@ -68,7 +67,7 @@ module Projects end def issue_params - params.require(:issue).permit(:title).merge(request: request) + params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request) end def serialize_as_json(resource) diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index 3701afd441f..a181e74043b 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -1,14 +1,21 @@ module Boards module Issues class CreateService < Boards::BaseService - def execute(list) - params.merge!(label_ids: [list.label_id]) - create_issue + def execute + create_issue(params.merge(label_ids: [list.label_id])) end private - def create_issue + def board + @board ||= project.boards.find(params.delete(:board_id)) + end + + def list + @list ||= board.lists.find(params.delete(:list_id)) + end + + def create_issue(params) ::Issues::CreateService.new(project, current_user, params).execute end end diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 92e77291da9..da59642f24d 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -76,13 +76,13 @@ describe Projects::Boards::IssuesController do describe 'POST create' do context 'with valid params' do it 'returns a successful 200 response' do - create_issue user: user, list: list1, title: 'New issue' + create_issue user: user, board: board, list: list1, title: 'New issue' expect(response).to have_http_status(200) end it 'returns the created issue' do - create_issue user: user, list: list1, title: 'New issue' + create_issue user: user, board: board, list: list1, title: 'New issue' expect(response).to match_response_schema('issue') end @@ -91,7 +91,7 @@ describe Projects::Boards::IssuesController do context 'with invalid params' do context 'when title is nil' do it 'returns an unprocessable entity 422 response' do - create_issue user: user, list: list1, title: nil + create_issue user: user, board: board, list: list1, title: nil expect(response).to have_http_status(422) end @@ -101,7 +101,7 @@ describe Projects::Boards::IssuesController do it 'returns a not found 404 response' do list = create(:list) - create_issue user: user, list: list, title: 'New issue' + create_issue user: user, board: board, list: list, title: 'New issue' expect(response).to have_http_status(404) end @@ -110,17 +110,18 @@ describe Projects::Boards::IssuesController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - create_issue user: guest, list: list1, title: 'New issue' + create_issue user: guest, board: board, list: list1, title: 'New issue' expect(response).to have_http_status(403) end end - def create_issue(user:, list:, title:) + def create_issue(user:, board:, list:, title:) sign_in(user) post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, list_id: list.to_param, issue: { title: title }, format: :json diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index 33e10e79f6d..360ee398f77 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe Boards::Issues::CreateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } let!(:list) { create(:list, board: board, label: label, position: 0) } - subject(:service) { described_class.new(project, user, title: 'New issue') } + subject(:service) { described_class.new(project, user, board_id: board.id, list_id: list.id, title: 'New issue') } before do project.team << [user, :developer] @@ -17,15 +17,15 @@ describe Boards::Issues::CreateService, services: true do it 'delegates the create proceedings to Issues::CreateService' do expect_any_instance_of(Issues::CreateService).to receive(:execute).once - service.execute(list) + service.execute end it 'creates a new issue' do - expect { service.execute(list) }.to change(project.issues, :count).by(1) + expect { service.execute }.to change(project.issues, :count).by(1) end it 'adds the label of the list to the issue' do - issue = service.execute(list) + issue = service.execute expect(issue.labels).to eq [label] end -- cgit v1.2.1 From 1a2002d9084db71d5a0a2631985012b4544c4037 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:14:39 -0300 Subject: Update board specs to use board factory instead of project_with_board --- spec/controllers/projects/boards/lists_controller_spec.rb | 4 ++-- spec/features/boards/boards_spec.rb | 5 +++-- spec/features/boards/new_issue_spec.rb | 3 ++- spec/services/boards/issues/list_service_spec.rb | 2 +- spec/services/boards/lists/destroy_service_spec.rb | 12 ++++++------ spec/services/boards/lists/list_service_spec.rb | 4 +--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index afede0191dc..34d6119429d 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Projects::Boards::ListsController do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 470e2bdbb9b..f79b6a77696 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:user2) { create(:user) } @@ -468,7 +469,7 @@ describe 'Issue Boards', feature: true, js: true do it 'removes filtered labels' do wait_for_vue_resource - + page.within '.labels-filter' do click_button('Label') wait_for_ajax diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index c046e6b8d79..1ceb60d5297 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards new issue', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } context 'authorized user' do diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 218a6d2bc2f..d9eb757f986 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Boards::Issues::ListService, services: true do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:project_with_board) } + let(:project) { create(:empty_project) } let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index 474c4512471..628caf03476 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Boards::Lists::DestroyService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } context 'when list type is label' do @@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do end it 'decrements position of higher lists' do - backlog = project.board.backlog_list + backlog = board.backlog_list development = create(:list, board: board, position: 0) review = create(:list, board: board, position: 1) staging = create(:list, board: board, position: 2) - done = project.board.done_list + done = board.done_list described_class.new(project, user).execute(development) @@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do end it 'does not remove list from board when list type is backlog' do - list = project.board.backlog_list + list = board.backlog_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) end it 'does not remove list from board when list type is done' do - list = project.board.done_list + list = board.done_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 4464f80f796..334cee3f06d 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -6,13 +6,11 @@ describe Boards::Lists::ListService, services: true do project = create(:empty_project) board = create(:board, project: project) label = create(:label, project: project) - backlog_list = create(:backlog_list, board: board) list = create(:list, board: board, label: label) - done_list = create(:done_list, board: board) service = described_class.new(project, double) - expect(service.execute(board)).to eq [backlog_list, list, done_list] + expect(service.execute(board)).to eq [board.backlog_list, list, board.done_list] end end end -- cgit v1.2.1 From e171c1d80debd88d1aa3a3c74d1937b68a7f0f18 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:36:46 -0300 Subject: Update Issue Board API to handle with has_many association --- lib/api/boards.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 4d5d144a02e..9b71d335128 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -7,13 +7,14 @@ module API # Get the project board get ':id/boards' do authorize!(:read_board, user_project) - present [user_project.board], with: Entities::Board + present user_project.boards, with: Entities::Board end segment ':id/boards/:board_id' do helpers do def project_board - board = user_project.board + board = user_project.boards.first + if params[:board_id].to_i == board.id board else @@ -55,8 +56,10 @@ module API authorize!(:admin_list, user_project) - list = ::Boards::Lists::CreateService.new(user_project, current_user, - { label_id: params[:label_id] }).execute + service = ::Boards::Lists::CreateService.new(user_project, current_user, + { label_id: params[:label_id] }) + + list = service.execute(project_board) if list.valid? present list, with: Entities::List @@ -78,10 +81,10 @@ module API authorize!(:admin_list, user_project) - moved = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position].to_i }).execute(list) + service = ::Boards::Lists::MoveService.new(user_project, current_user, + { position: params[:position].to_i }) - if moved + if service.execute(list) present list, with: Entities::List else render_api_error!({ error: "List could not be moved!" }, 400) @@ -97,16 +100,16 @@ module API # Example Request: # DELETE /projects/:id/boards/:board_id/lists/:list_id delete "/lists/:list_id" do - list = board_lists.find_by(id: params[:list_id]) - authorize!(:admin_list, user_project) - if list - destroyed_list = ::Boards::Lists::DestroyService.new( - user_project, current_user).execute(list) - present destroyed_list, with: Entities::List + list = board_lists.find(params[:list_id]) + + service = ::Boards::Lists::DestroyService.new(user_project, current_user) + + if service.execute(list) + present list, with: Entities::List else - not_found!('List') + render_api_error!({ error: 'List could not be deleted!' }, 400) end end end -- cgit v1.2.1 From 478b3d64f8cc7a47c5d1fc0507036590417b8aa8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 7 Oct 2016 16:46:32 -0300 Subject: Remove Boards::BaseService --- app/services/boards/base_service.rb | 4 ---- app/services/boards/create_service.rb | 2 +- app/services/boards/issues/create_service.rb | 2 +- app/services/boards/issues/list_service.rb | 2 +- app/services/boards/issues/move_service.rb | 2 +- app/services/boards/list_service.rb | 2 +- app/services/boards/lists/create_service.rb | 2 +- app/services/boards/lists/destroy_service.rb | 2 +- app/services/boards/lists/generate_service.rb | 2 +- app/services/boards/lists/list_service.rb | 2 +- app/services/boards/lists/move_service.rb | 2 +- 11 files changed, 10 insertions(+), 14 deletions(-) delete mode 100644 app/services/boards/base_service.rb diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb deleted file mode 100644 index 7eacacbaf7e..00000000000 --- a/app/services/boards/base_service.rb +++ /dev/null @@ -1,4 +0,0 @@ -module Boards - class BaseService < ::BaseService - end -end diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index a9dbd76a44a..9bdd7b6f0cf 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,5 +1,5 @@ module Boards - class CreateService < Boards::BaseService + class CreateService < BaseService def execute if project.boards.empty? create_board! diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index a181e74043b..c0d7ff5b585 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class CreateService < Boards::BaseService + class CreateService < BaseService def execute create_issue(params.merge(label_ids: [list.label_id])) end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 782dbf0db1a..fd4a462c7b2 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class ListService < Boards::BaseService + class ListService < BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute issues = without_board_labels(issues) unless list.movable? diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 9a16609a8b5..96554a92a02 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class MoveService < Boards::BaseService + class MoveService < BaseService def execute(issue) return false unless can?(current_user, :update_issue, issue) return false unless valid_move? diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb index cca4bdd82a2..84f1fc3a4e2 100644 --- a/app/services/boards/list_service.rb +++ b/app/services/boards/list_service.rb @@ -1,5 +1,5 @@ module Boards - class ListService < Boards::BaseService + class ListService < BaseService def execute create_board! if project.boards.empty? project.boards diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index da6f59c0399..abc7aeece39 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class CreateService < Boards::BaseService + class CreateService < BaseService def execute(board) List.transaction do label = project.labels.find(params[:label_id]) diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb index d75c5fd3dc6..f986e05944c 100644 --- a/app/services/boards/lists/destroy_service.rb +++ b/app/services/boards/lists/destroy_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class DestroyService < Boards::BaseService + class DestroyService < BaseService def execute(list) return false unless list.destroyable? diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 686e4e4b336..d8048f1c67e 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class GenerateService < Boards::BaseService + class GenerateService < BaseService def execute(board) return false unless board.lists.movable.empty? diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb index ff739bc7d9c..c579ed4c869 100644 --- a/app/services/boards/lists/list_service.rb +++ b/app/services/boards/lists/list_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class ListService < Boards::BaseService + class ListService < BaseService def execute(board) board.lists end diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb index 7d0730e8332..f2a68865f7b 100644 --- a/app/services/boards/lists/move_service.rb +++ b/app/services/boards/lists/move_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class MoveService < Boards::BaseService + class MoveService < BaseService def execute(list) @board = list.board @old_position = list.position -- cgit v1.2.1 From 7be133aa36c4160c31010dc74af002320e9070b8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 7 Oct 2016 17:06:27 -0300 Subject: Fix typo on Boards::Issues::ListService spec --- spec/services/boards/issues/list_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index d9eb757f986..7c206cf3ce7 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -70,7 +70,7 @@ describe Boards::Issues::ListService, services: true do end end - context 'with list that does not belongs to the board' do + context 'with list that does not belong to the board' do it 'raises an error' do list = create(:list) service = described_class.new(project, user, board_id: board.id, id: list.id) -- cgit v1.2.1 From c5ea58b63007b404a725f28eaae33f5f11d8d925 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 14:36:09 +0100 Subject: Update endpoint path for the frontend --- app/views/projects/boards/index.html.haml | 19 +++++++++++++++++++ app/views/projects/boards/show.html.haml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index e69de29bb2d..7a1f0ee4428 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -0,0 +1,19 @@ +- @no_container = true +- @content_class = "issue-boards-content" +- page_title "Boards" + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('boards/boards_bundle.js') + = page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test? + += render "projects/issues/head" + += render 'shared/issuable/filter', type: :boards + +.boards-list#board-app{ "v-cloak" => true, + "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @boards.first)}", + "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", + "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } + .boards-app-loading.text-center{ "v-if" => "loading" } + = icon("spinner spin") + = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 5b441b48caf..7bc476264b1 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @board)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } -- cgit v1.2.1 From 2ad531f5e279bcd278600d1f95ff9d4e4988b034 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 11:22:30 -0300 Subject: Add Project::BoardLimitExcedeed error class --- app/models/project.rb | 4 +++- spec/models/project_spec.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 30db7ed50b3..758927edd5c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -16,6 +16,8 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper + class BoardLimitExceeded < StandardError; end + NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git' @@ -1341,6 +1343,6 @@ class Project < ActiveRecord::Base end def validate_board_limit(board) - raise StandardError, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS + raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1b13f1be477..308a00db9cd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -99,7 +99,7 @@ describe Project, models: true do it 'raises an error when attempting to add more than one board to the project' do subject.boards.build - expect { subject.boards.build }.to raise_error(StandardError, 'Number of permitted boards exceeded') + expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded') expect(subject.boards.size).to eq 1 end end -- cgit v1.2.1 From 4e9783e4ddffdb550c362e7bc93f31c904d638e3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 09:42:34 -0500 Subject: Allow scrolling within grouped pipelines --- app/assets/stylesheets/pages/pipelines.scss | 6 +++++- app/views/projects/commit/_pipeline_status_group.html.haml | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 28e850767b7..7843355f0ab 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -420,7 +420,11 @@ left: auto; right: -197px; top: -9px; - max-height: 245px; + + ul { + max-height: 245px; + overflow: auto; + } a { color: $gl-text-color; diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 6ada719e006..5d0d5ba0262 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -5,8 +5,9 @@ %span.ci-status-text = name %span.badge= subject.size -%ul.dropdown-menu.grouped-pipeline-dropdown - %li.arrow - - subject.each do |status| - %li - = render "projects/#{status.to_partial_path}_pipeline", subject: status +.dropdown-menu.grouped-pipeline-dropdown + .arrow + %ul + - subject.each do |status| + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From e2c97567c1a80d1d4857efabead9021897406913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 16:47:02 +0200 Subject: Move some CHANGELOG entries to the 8.13.0 part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spotted while reviewing https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/789/diffs#diff-1. Signed-off-by: Rémy Coutable --- CHANGELOG | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ffbaefdb5f1..693f5179297 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ v 8.13.0 (unreleased) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Updating verbiage on git basics to be more intuitive + - Clarify documentation for Runners API (Gennady Trafimenkov) + - Change user & group landing page routing from /u/:username to /:username + - Prevent running GfmAutocomplete setup for each diff note !6569 - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) @@ -110,7 +114,6 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only - - Change user & group landing page routing from /u/:username to /:username v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves @@ -130,7 +133,6 @@ v 8.12.2 - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 - - Prevent running GfmAutocomplete setup for each diff note !6569 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST @@ -334,7 +336,6 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. - - Updating verbiage on git basics to be more intuitive v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 @@ -495,7 +496,6 @@ v 8.11.0 - Add pipeline events hook - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) - - Clarify documentation for Runners API (Gennady Trafimenkov) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Convert image diff background image to CSS (ClemMakesApps) -- cgit v1.2.1 From 53f50edf4dac5133ff4d67a67ba5071e3eb7e78a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 23:29:20 -0300 Subject: Fix board relates specs --- app/controllers/projects/boards_controller.rb | 2 +- spec/features/boards/boards_spec.rb | 16 ++++++++-------- spec/features/boards/keyboard_shortcut_spec.rb | 4 +--- spec/features/boards/new_issue_spec.rb | 4 ++-- spec/fixtures/api/schemas/board.json | 3 +-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index bbb1b1bf1e1..808affa4f98 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -32,6 +32,6 @@ class Projects::BoardsController < Projects::ApplicationController end def serialize_as_json(resource) - resource.as_json(only: [:id, :name]) + resource.as_json(only: [:id]) end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index f79b6a77696..0fb1608a0a3 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -18,7 +18,7 @@ describe 'Issue Boards', feature: true, js: true do context 'no lists' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource expect(page).to have_selector('.board', count: 3) end @@ -61,8 +61,8 @@ describe 'Issue Boards', feature: true, js: true do let!(:done) { create(:label, project: project, name: 'Done') } let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') } - let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } - let!(:list2) { create(:list, board: project.board, label: development, position: 1) } + let!(:list1) { create(:list, board: board, label: planning, position: 0) } + let!(:list2) { create(:list, board: board, label: development, position: 1) } let!(:confidential_issue) { create(:issue, :confidential, project: project, author: user) } let!(:issue1) { create(:issue, project: project, assignee: user) } @@ -76,7 +76,7 @@ describe 'Issue Boards', feature: true, js: true do let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug, accepting]) } before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource @@ -170,7 +170,7 @@ describe 'Issue Boards', feature: true, js: true do create(:issue, project: project) end - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource page.within(find('.board', match: :first)) do @@ -604,7 +604,7 @@ describe 'Issue Boards', feature: true, js: true do context 'keyboard shortcuts' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end @@ -617,7 +617,7 @@ describe 'Issue Boards', feature: true, js: true do context 'signed out user' do before do logout - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end @@ -633,7 +633,7 @@ describe 'Issue Boards', feature: true, js: true do project.team << [user_guest, :guest] logout login_as(user_guest) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 7ef68e9eb8d..a5fc766401f 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -6,9 +6,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do let(:project) { create(:empty_project) } before do - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) + create(:board, project: project) login_as :admin diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 1ceb60d5297..67d6da5f39a 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -14,7 +14,7 @@ describe 'Issue Boards new issue', feature: true, js: true do login_as(user) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource expect(page).to have_selector('.board', count: 3) @@ -70,7 +70,7 @@ describe 'Issue Boards new issue', feature: true, js: true do context 'unauthorized user' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json index 6c6e2bee8cb..03aca4a3cc0 100644 --- a/spec/fixtures/api/schemas/board.json +++ b/spec/fixtures/api/schemas/board.json @@ -1,8 +1,7 @@ { "type": "object", "required" : [ - "id", - "name" + "id" ], "properties" : { "id": { "type": "integer" }, -- cgit v1.2.1 From dc49ee6247e041020e96c5d5be292e3f92926c4d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 09:29:47 +0100 Subject: Updated to pass the board ID with the boards root to save conflicts with EE --- app/assets/javascripts/boards/boards_bundle.js.es6 | 3 ++- app/assets/javascripts/boards/services/board_service.js.es6 | 10 +++++----- app/views/projects/boards/index.html.haml | 3 ++- app/views/projects/boards/show.html.haml | 3 ++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 91c12570e09..d4f8f4b9420 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -28,12 +28,13 @@ $(() => { state: Store.state, loading: true, endpoint: $boardApp.dataset.endpoint, + boardId: $boardApp.dataset.boardId, disabled: $boardApp.dataset.disabled === 'true', issueLinkBase: $boardApp.dataset.issueLinkBase }, init: Store.create.bind(Store), created () { - gl.boardService = new BoardService(this.endpoint); + gl.boardService = new BoardService(this.endpoint, this.boardId); }, ready () { Store.disabled = this.disabled; diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 2b825c3949f..b9c91cbf31e 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -1,15 +1,15 @@ class BoardService { - constructor (root) { + constructor (root, boardId) { Vue.http.options.root = root; - this.lists = Vue.resource(`${root}/lists{/id}`, {}, { + this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { generate: { method: 'POST', - url: `${root}/lists/generate.json` + url: `${root}/${boardId}/lists/generate.json` } }); - this.issue = Vue.resource(`${root}/issues{/id}`, {}); - this.issues = Vue.resource(`${root}/lists{/id}/issues`, {}); + this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {}); + this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}); Vue.http.interceptors.push((request, next) => { request.headers['X-CSRF-Token'] = $.rails.csrfToken(); diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 7a1f0ee4428..9252bbc08c1 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -11,7 +11,8 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @boards.first)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-board-id" => "#{@boards.first.id}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 7bc476264b1..d6e1ac5f3cf 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,8 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @board)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-board-id" => "#{@board.id}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } -- cgit v1.2.1 From 53b3c62e0f889d077ee5b19703c3e04384c85c03 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 16:07:53 +0100 Subject: Fixed JS tests --- spec/javascripts/boards/boards_store_spec.js.es6 | 2 +- spec/javascripts/boards/issue_spec.js.es6 | 2 +- spec/javascripts/boards/list_spec.js.es6 | 2 +- spec/javascripts/boards/mock_data.js.es6 | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index 078e4b00023..15c305ce321 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -14,7 +14,7 @@ (() => { beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); $.cookie('issue_board_welcome_hidden', 'false'); diff --git a/spec/javascripts/boards/issue_spec.js.es6 b/spec/javascripts/boards/issue_spec.js.es6 index 3569d1b98bd..328c6f82ab5 100644 --- a/spec/javascripts/boards/issue_spec.js.es6 +++ b/spec/javascripts/boards/issue_spec.js.es6 @@ -16,7 +16,7 @@ describe('Issue model', () => { let issue; beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); issue = new ListIssue({ diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index 1688b996162..ec78d82e919 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -16,7 +16,7 @@ describe('List model', () => { let list; beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); list = new List(listObj); diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index f3797ed44d4..052455f2ca6 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -26,7 +26,7 @@ const listObjDuplicate = { const BoardsMockData = { 'GET': { - '/test/issue-boards/board/lists{/id}/issues': { + '/test/issue-boards/board/1/lists{/id}/issues': { issues: [{ title: 'Testing', iid: 1, @@ -37,13 +37,13 @@ const BoardsMockData = { } }, 'POST': { - '/test/issue-boards/board/lists{/id}': listObj + '/test/issue-boards/board/1/lists{/id}': listObj }, 'PUT': { - '/test/issue-boards/board/lists{/id}': {} + '/test/issue-boards/board/1/lists{/id}': {} }, 'DELETE': { - '/test/issue-boards/board/lists{/id}': {} + '/test/issue-boards/board/1/lists{/id}': {} } }; -- cgit v1.2.1 From 66af08df281214f59bb21e911949625024b41829 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 16:10:12 +0100 Subject: Fixed default branch dropdown not converting to select2 Closes #23200 --- app/assets/javascripts/project_new.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index a787b11f2a9..3cf41505814 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -4,7 +4,9 @@ this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = bind(this.toggleSettings, this); - this.$selects = $('.features select'); + this.$selects = $('.features select').filter(function () { + return $(this).data('field'); + }); $('.project-edit-container').on('ajax:before', (function(_this) { return function() { -- cgit v1.2.1 From 8e70cf25641fc023e146ac1632f89203a55ce6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 17:25:57 +0200 Subject: Addresses Robert's feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/api/projects.md | 2 +- doc/api/users.md | 2 +- spec/requests/api/users_spec.rb | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 27436a052da..f96bf7f6d63 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -436,7 +436,7 @@ Parameters: ### Get project events Get the events for the specified project. -Sorted from newest to latest +Sorted from newest to oldest ``` GET /projects/:id/events diff --git a/doc/api/users.md b/doc/api/users.md index 15202010b7b..a52b2d51d78 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -630,7 +630,7 @@ Will return `200 OK` on success, `404 User Not Found` is user cannot be found or ### Get user contribution events -Get the contribution events for the specified user, sorted from newest to latest. +Get the contribution events for the specified user, sorted from newest to oldest. ``` GET /users/:id/events diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 56b2ec6271e..91e54af1209 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -898,7 +898,6 @@ describe API::API, api: true do describe 'GET /user/:id/events' do let(:user) { create(:user) } - let(:lambda_user) { create(:user) } let(:project) { create(:empty_project) } let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } @@ -909,7 +908,9 @@ describe API::API, api: true do context "as a user than cannot see the event's project" do it 'returns no events' do - get api("/users/#{user.id}/events", lambda_user) + other_user = create(:user) + + get api("/users/#{user.id}/events", other_user) expect(response).to have_http_status(200) expect(json_response).to be_empty -- cgit v1.2.1 From c90483406ecc2717076391737ff57167b5a03393 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 3 Oct 2016 13:11:16 +0100 Subject: refactors tests because of gitlab-test repository changes --- CHANGELOG | 1 - app/models/repository.rb | 9 +++------ spec/controllers/projects/commit_controller_spec.rb | 16 ++++++++++------ spec/helpers/search_helper_spec.rb | 2 +- spec/lib/gitlab/data_builder/push_spec.rb | 8 ++++---- spec/mailers/notify_spec.rb | 2 +- spec/models/commit_spec.rb | 8 ++++---- spec/models/merge_request_diff_spec.rb | 6 +++--- spec/models/merge_request_spec.rb | 4 ++-- spec/models/repository_spec.rb | 15 ++++++++++++++- spec/requests/api/commits_spec.rb | 12 +++++++++--- spec/requests/api/repositories_spec.rb | 8 ++++---- spec/requests/git_http_spec.rb | 4 ++-- spec/services/merge_requests/refresh_service_spec.rb | 4 ++-- spec/services/system_note_service_spec.rb | 8 ++++---- spec/workers/emails_on_push_worker_spec.rb | 4 ++-- 16 files changed, 65 insertions(+), 46 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 841861293c8..0116d3ac0a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -119,7 +119,6 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 -v 8.12.0 (unreleased) - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable diff --git a/app/models/repository.rb b/app/models/repository.rb index 1bf6e58b9db..cf269061ebe 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1013,17 +1013,14 @@ class Repository branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) - if branch_commit && !same_head?(branch_commit.id, root_ref_commit.id) - is_ancestor?(branch_commit.id, root_ref_commit.id) + if branch_commit + same_head = branch_commit.id == root_ref_commit.id + !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end end - def same_head?(first_commit_id, second_commit_id) - first_commit_id == second_commit_id - end - def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id second_commit_id = commit(second_commit_id).try(:id) || second_commit_id diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 7e440193d7b..646b097d74e 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -102,15 +102,16 @@ describe Projects::CommitController do describe "as patch" do include_examples "export as", :patch let(:format) { :patch } + let(:commit2) { project.commit('498214de67004b1da3d820901307bed2a68a8ef6') } it "is a git email patch" do - go(id: commit.id, format: format) + go(id: commit2.id, format: format) - expect(response.body).to start_with("From #{commit.id}") + expect(response.body).to start_with("From #{commit2.id}") end it "contains a git diff" do - go(id: commit.id, format: format) + go(id: commit2.id, format: format) expect(response.body).to match(/^diff --git/) end @@ -135,6 +136,8 @@ describe Projects::CommitController do describe "GET branches" do it "contains branch and tags information" do + commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + get(:branches, namespace_id: project.namespace.to_param, project_id: project.to_param, @@ -254,16 +257,17 @@ describe Projects::CommitController do end let(:existing_path) { '.gitmodules' } + let(:commit2) { project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } context 'when the commit exists' do context 'when the user has access to the project' do context 'when the path exists in the diff' do it 'enables diff notes' do - diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + diff_for_path(id: commit2.id, old_path: existing_path, new_path: existing_path) expect(assigns(:diff_notes_disabled)).to be_falsey expect(assigns(:comments_target)).to eq(noteable_type: 'Commit', - commit_id: commit.id) + commit_id: commit2.id) end it 'only renders the diffs for the path given' do @@ -272,7 +276,7 @@ describe Projects::CommitController do meth.call(diffs) end - diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + diff_for_path(id: commit2.id, old_path: existing_path, new_path: existing_path) end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index c5b5aa8c445..64aa41020c9 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -19,7 +19,7 @@ describe SearchHelper do expect(subject.filename).to eq('CHANGELOG') expect(subject.basename).to eq('CHANGELOG') expect(subject.ref).to eq('master') - expect(subject.startline).to eq(186) + expect(subject.startline).to eq(188) expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") end diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index b73434e8dd7..a379f798a16 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -8,13 +8,13 @@ describe Gitlab::DataBuilder::Push, lib: true do let(:data) { described_class.build_sample(project, user) } it { expect(data).to be_a(Hash) } - it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } - it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(data[:before]).to eq('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') } + it { expect(data[:after]).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') } it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:commits].size).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) } - it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) } - it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) } + it { expect(data[:commits].first[:added]).to eq(['bar/branch-test.txt']) } + it { expect(data[:commits].first[:modified]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) } include_examples 'project hook data with deprecateds' diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0e4130e8a3a..c8207e58e90 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -628,7 +628,7 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it 'has the correct subject' do - is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ + is_expected.to have_subject /Re: #{project.name} | #{commit.title} \(#{commit.short_id}\)/ end it 'contains a link to the commit' do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index d3e6a6648cc..51be3f36135 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -164,10 +164,10 @@ eos let(:data) { commit.hook_attrs(with_changed_files: true) } it { expect(data).to be_a(Hash) } - it { expect(data[:message]).to include('Add submodule from gitlab.com') } - it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') } - it { expect(data[:added]).to eq(["gitlab-grack"]) } - it { expect(data[:modified]).to eq([".gitmodules"]) } + it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') } + it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46+00:00') } + it { expect(data[:added]).to eq(["bar/branch-test.txt"]) } + it { expect(data[:modified]).to eq([]) } it { expect(data[:removed]).to eq([]) } end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..a27c2b28326 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -6,9 +6,9 @@ describe MergeRequestDiff, models: true do it { expect(subject).to be_valid } it { expect(subject).to be_persisted } - it { expect(subject.commits.count).to eq(5) } - it { expect(subject.diffs.count).to eq(8) } - it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(subject.commits.count).to eq(29) } + it { expect(subject.diffs.count).to eq(20) } + it { expect(subject.head_commit_sha).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') } it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 38b6da50168..5884b4cff8c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -489,7 +489,7 @@ describe MergeRequest, models: true do subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do - expect(subject.diverged_commits_count).to eq(5) + expect(subject.diverged_commits_count).to eq(29) end end @@ -497,7 +497,7 @@ describe MergeRequest, models: true do subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do - expect(subject.diverged_commits_count).to eq(5) + expect(subject.diverged_commits_count).to eq(29) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6dd3f91be17..4df78713a82 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -114,17 +114,30 @@ describe Repository, models: true do end describe '#merged_to_root_ref?' do - context 'merged branch' do + context 'merged branch without ff' do subject { repository.merged_to_root_ref?('branch-merged') } it { is_expected.to be_truthy } end + # If the HEAD was ff then it will be false + context 'merged with ff' do + subject { repository.merged_to_root_ref?('improve/awesome') } + + it { is_expected.to be_truthy } + end + context 'not merged branch' do subject { repository.merged_to_root_ref?('not-merged-branch') } it { is_expected.to be_falsey } end + + context 'default branch' do + subject { repository.merged_to_root_ref?('master') } + + it { is_expected.to be_falsey } + end end describe '#can_be_merged?' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index aa610557056..66fa0c0c01f 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -53,7 +53,12 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) - expect(json_response.size).to eq(commits.size - 1) + if commits.size >= 20 + expect(json_response.size).to eq(20) + else + expect(json_response.size).to eq(commits.size - 1) + end + expect(json_response.first["id"]).to eq(commits.second.id) expect(json_response.second["id"]).to eq(commits.third.id) end @@ -447,11 +452,12 @@ describe API::API, api: true do end it 'returns the inline comment' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 7, line_type: 'new' + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) - expect(json_response['line']).to eq(7) + expect(json_response['line']).to eq(1) expect(json_response['line_type']).to eq('new') end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 80a856a6e90..c4dc2d9006a 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -21,7 +21,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.first['name']).to eq('encoding') + expect(json_response.first['name']).to eq('bar') expect(json_response.first['type']).to eq('tree') expect(json_response.first['mode']).to eq('040000') end @@ -166,9 +166,9 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array contributor = json_response.first - expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com') - expect(contributor['name']).to eq('Dmitriy Zaporozhets') - expect(contributor['commits']).to eq(13) + expect(contributor['email']).to eq('tiagonbotelho@hotmail.com') + expect(contributor['name']).to eq('tiagonbotelho') + expect(contributor['commits']).to eq(1) expect(contributor['additions']).to eq(0) expect(contributor['deletions']).to eq(0) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..27f0fd22ae6 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -440,8 +440,8 @@ describe 'Git HTTP requests', lib: true do before do # Provide a dummy file in its place allow_any_instance_of(Repository).to receive(:blob_at).and_call_original - allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do - Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore') + allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do + Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt') end get "/#{project.path_with_namespace}/blob/master/info/refs" diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 59d3912018a..5b4e4908add 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -118,7 +118,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') } + it { expect(@fork_merge_request.notes.last.note).to include('Added 28 commits') } it { expect(@fork_merge_request).to be_open } it { expect(@build_failed_todo).to be_pending } it { expect(@fork_build_failed_todo).to be_pending } @@ -169,7 +169,7 @@ describe MergeRequests::RefreshService, services: true do notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) expect(notes[0]).to include('Restored source branch `master`') - expect(notes[1]).to include('Added 4 commits') + expect(notes[1]).to include('Added 28 commits') expect(@fork_merge_request).to be_open end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 304d4e62396..b4ba28dfe8e 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -54,7 +54,7 @@ describe SystemNoteService, services: true do it 'adds a message line for each commit' do new_commits.each_with_index do |commit, i| # Skip the header - expect(note_lines[i + 1]).to eq "* #{commit.short_id} - #{commit.title}" + expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}" end end end @@ -81,7 +81,7 @@ describe SystemNoteService, services: true do end it 'includes a commit count' do - expect(summary_line).to end_with " - 2 commits from branch `feature`" + expect(summary_line).to end_with " - 26 commits from branch `feature`" end end @@ -91,7 +91,7 @@ describe SystemNoteService, services: true do end it 'includes a commit count' do - expect(summary_line).to end_with " - 2 commits from branch `feature`" + expect(summary_line).to end_with " - 26 commits from branch `feature`" end end @@ -537,7 +537,7 @@ describe SystemNoteService, services: true do let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:jira_issue) { ExternalIssue.new("JIRA-1", project)} let(:jira_tracker) { project.jira_service } - let(:commit) { project.commit } + let(:commit) { project.repository.commits('master').find { |commit| commit.id == '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } } context 'in JIRA issue tracker' do before do diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 7ca2c29da1c..036d037f3f9 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -57,7 +57,7 @@ describe EmailsOnPushWorker do end it "sends a mail with the correct subject" do - expect(email.subject).to include('Change some files') + expect(email.subject).to include('adds bar folder and branch-test text file') end it "mentions force pushing in the body" do @@ -73,7 +73,7 @@ describe EmailsOnPushWorker do before { perform } it "sends a mail with the correct subject" do - expect(email.subject).to include('Change some files') + expect(email.subject).to include('adds bar folder and branch-test text file') end it "does not mention force pushing in the body" do -- cgit v1.2.1 From 8213fc6a744cb5c9fa4c7461dbf1a96f48898662 Mon Sep 17 00:00:00 2001 From: barthc Date: Mon, 10 Oct 2016 16:04:52 +0100 Subject: allow multiple labels commands --- CHANGELOG | 1 + app/services/slash_commands/interpret_service.rb | 21 +++++++++-- .../slash_commands/interpret_service_spec.rb | 43 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba6c3f7c558..850cb16dff1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -91,6 +91,7 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list + - Add multiple command support for all label related slash commands !6780 (barthc) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 1725a30fae5..e4ae3dec8aa 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -122,7 +122,12 @@ module SlashCommands command :label do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:add_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end end desc 'Remove all or specific label(s)' @@ -136,7 +141,12 @@ module SlashCommands if labels_param.present? label_ids = find_label_ids(labels_param) - @updates[:remove_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end else @updates[:label_ids] = [] end @@ -152,7 +162,12 @@ module SlashCommands command :relabel do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end end desc 'Add a todo' diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index ae4d286d250..b57e338b782 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -86,6 +86,25 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple label command' do + it 'fetches label ids and populates add_label_ids if content contains multiple /label' do + bug # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) + end + end + + shared_examples 'multiple label with same argument' do + it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id]) + end + end + shared_examples 'unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -95,6 +114,15 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do + issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) + end + end + shared_examples 'unlabel command with no argument' do it 'populates label_ids: [] if content contains /unlabel with no arguments' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -285,6 +313,16 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple label command' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'multiple label with same argument' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{inprogress.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command' do let(:content) { %(/unlabel ~"#{inprogress.title}") } let(:issuable) { issue } @@ -295,6 +333,11 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}" \n/unlabel ~#{bug.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command with no argument' do let(:content) { %(/unlabel) } let(:issuable) { issue } -- cgit v1.2.1 From 9db841127bef1509d32ae5f3ff2458923c84a19d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 11 Oct 2016 18:22:41 +0200 Subject: Remove pointless `.vagrant_enabled` file --- .vagrant_enabled | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .vagrant_enabled diff --git a/.vagrant_enabled b/.vagrant_enabled deleted file mode 100644 index e69de29bb2d..00000000000 -- cgit v1.2.1 From ea3bbbdef86ae30fcf76baaba11a3fceb6d2aa03 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 11 Oct 2016 13:01:57 -0500 Subject: FIx JS bug with select2 because of missing `data-field` attribute in select box. --- app/views/projects/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index d19422c8657..c8f84b96cb7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -91,7 +91,7 @@ Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control' + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } - if Gitlab.config.registry.enabled .form-group -- cgit v1.2.1 From b0acc0a308848529727e8bcf2a7c6e2bc0f76303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 20:29:42 +0200 Subject: Add 8.12.5, 8.11.9, and 8.10.12 CHANGELOG entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 48eaa328f23..37cbfeff01a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,6 @@ v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) @@ -84,7 +83,6 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list @@ -102,8 +100,13 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects -v 8.12.5 (unreleased) - - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread +v 8.12.5 + - Switch from request to env in ::API::Helpers. !6615 + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 + - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) @@ -330,6 +333,10 @@ v 8.12.0 - Fix non-master branch readme display in tree view - Add UX improvements for merge request version diffs +v 8.11.9 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.11.8 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests @@ -555,6 +562,10 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.12 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.10.11 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests -- cgit v1.2.1 From 670b2eb5c05a721f810a5b248612cadde0eaf2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 10:20:35 +0000 Subject: Merge branch 'api-fix-project-group-sharing' into 'security' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API: Share projects only with groups current_user can access Aims to address the issues here: https://gitlab.com/gitlab-org/gitlab-ce/issues/23004 * Projects can be shared with non-existent groups * Projects can be shared with groups that the current user does not have access to read Concerns: The new implementation of the API endpoint allows projects to be shared with a larger range of groups than can be done via the web UI. The form for sharing a project with a group uses the following API endpoint to index the available groups: https://gitlab.com/gitlab-org/gitlab-ce/blob/494269fc92f61098ee6bd635a0426129ce2c5456/lib/api/groups.rb#L17. The groups indexed in the web form will only be those groups that the user is currently a member of. The new implementation allows projects to be shared with any group that the authenticated user has access to view. This widens the range of groups to those that are public and internal. See merge request !2005 Signed-off-by: Rémy Coutable --- app/models/project_group_link.rb | 2 +- lib/api/projects.rb | 6 ++++++ spec/models/project_group_link_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index 7613cbdea93..db46def11eb 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base belongs_to :group validates :project_id, presence: true - validates :group_id, presence: true + validates :group, presence: true validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } validates :group_access, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c24e8e8bd9b..da16e24d7ea 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -416,6 +416,12 @@ module API required_attributes! [:group_id, :group_access] attrs = attributes_for_keys [:group_id, :group_access, :expires_at] + group = Group.find_by_id(attrs[:group_id]) + + unless group && can?(current_user, :read_group, group) + not_found!('Group') + end + unless user_project.allowed_to_share_with_group? return render_api_error!("The project sharing with group is disabled", 400) end diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 2fa6715fcaf..c5ff1941378 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -11,7 +11,7 @@ describe ProjectGroupLink do it { should validate_presence_of(:project_id) } it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } - it { should validate_presence_of(:group_id) } + it { should validate_presence_of(:group) } it { should validate_presence_of(:group_access) } end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..19a2c7a2700 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -819,6 +819,20 @@ describe API::API, api: true do expect(response.status).to eq 400 end + it 'returns a 404 error when user cannot read group' do + private_group = create(:group, :private) + + post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + + it 'returns a 404 error when group does not exist' do + post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + it "returns a 409 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 expect(response.status).to eq 409 -- cgit v1.2.1 From c7865786572a3a2e12ed67a3f8121c0be8c4ba38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 21:26:49 +0200 Subject: Make spec deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/requests/api/users_spec.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 91e54af1209..684c27f4353 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -926,20 +926,18 @@ describe API::API, api: true do it 'returns the "joined" event' do get api("/users/#{user.id}/events", user) - first_event = json_response.first + comment_event = json_response.find { |e| e['action_name'] == 'commented on' } - expect(first_event['action_name']).to eq('commented on') - expect(first_event['project_id'].to_i).to eq(project.id) - expect(first_event['author_username']).to eq(user.username) - expect(first_event['note']['id']).to eq(note.id) - expect(first_event['note']['body']).to eq('What an awesome day!') + expect(comment_event['project_id'].to_i).to eq(project.id) + expect(comment_event['author_username']).to eq(user.username) + expect(comment_event['note']['id']).to eq(note.id) + expect(comment_event['note']['body']).to eq('What an awesome day!') - last_event = json_response.last + joined_event = json_response.find { |e| e['action_name'] == 'joined' } - expect(last_event['action_name']).to eq('joined') - expect(last_event['project_id'].to_i).to eq(project.id) - expect(last_event['author_username']).to eq(user.username) - expect(last_event['author']['name']).to eq(user.name) + expect(joined_event['project_id'].to_i).to eq(project.id) + expect(joined_event['author_username']).to eq(user.username) + expect(joined_event['author']['name']).to eq(user.name) end end end -- cgit v1.2.1 From 32186b4ab89e751d42590d2cbfbcf0c6a131bdca Mon Sep 17 00:00:00 2001 From: Justin DiPierro Date: Thu, 6 Oct 2016 12:42:17 -0400 Subject: Added 'Download' button to snippet view --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 14 +++++++++++--- app/views/snippets/show.html.haml | 1 + config/routes/snippets.rb | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e2480b66ae4..ad34e7476ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ v 8.13.0 (unreleased) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Remove redundant mixins (ClemMakesApps) + - Added 'Download' button to the Snippets page (Justin DiPierro) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index d198782138a..dee57e4a388 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,10 +1,10 @@ class SnippetsController < ApplicationController include ToggleAwardEmoji - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] # Allow read snippet - before_action :authorize_read_snippet!, only: [:show, :raw] + before_action :authorize_read_snippet!, only: [:show, :raw, :download] # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -12,7 +12,7 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:index, :show, :raw] + skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download] layout 'snippets' respond_to :html @@ -75,6 +75,14 @@ class SnippetsController < ApplicationController ) end + def download + send_data( + @snippet.content, + type: 'text/plain; charset=utf-8', + filename: @snippet.sanitized_file_name + ) + end + protected def snippet diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index cd89155c616..27d7a6c5bb6 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -9,6 +9,7 @@ .file-actions = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" + = link_to 'Download', download_snippet_path(@snippet), class: "btn btn-sm" = render 'shared/snippets/blob' = render 'award_emoji/awards_block', awardable: @snippet, inline: true \ No newline at end of file diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 1949f215c66..3ca096f31ba 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -1,6 +1,7 @@ resources :snippets, concerns: :awardable do member do get 'raw' + get 'download' end end -- cgit v1.2.1 From ba99f0b45157e841550f87c1d4e6879e108d2d6c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 15:17:56 -0700 Subject: Add CHANGELOG entry for 8.12.6 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 37cbfeff01a..c8271f55cfb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -100,6 +100,9 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects +v 8.12.6 + - Update mailroom to 0.8.1 in Gemfile.lock !6814 + v 8.12.5 - Switch from request to env in ::API::Helpers. !6615 - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 -- cgit v1.2.1 From f0150ef4d9ac869be21079d21c856a1ede72a3bf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 20 Sep 2016 15:44:07 -0500 Subject: Add disabled delete button to protected branches --- CHANGELOG | 1 + app/views/projects/branches/_branch.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..8b5d95649bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.13.0 (unreleased) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created + - Add disabled delete button to protected branches (ClemMakesApps) - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 5217b8bf028..4480b2f22c3 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -30,8 +30,8 @@ = render 'projects/buttons/download', project: @project, ref: branch.name - - if can_remove_branch?(@project, branch.name) - = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do + - if can?(current_user, :push_code, @project) + = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: "btn btn-remove remove-row has-tooltip #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do = icon("trash-o") - if branch.name != @repository.root_ref -- cgit v1.2.1 From 9c92443ffe90ec58490fb66ebce98e8e19a07c9b Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 10:46:30 +0500 Subject: Remove unused lets from deploy_key spec --- spec/models/deploy_key_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 6a90598a629..93623e8e99b 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe DeployKey, models: true do - let(:project) { create(:project) } - let(:deploy_key) { create(:deploy_key, projects: [project]) } - describe "Associations" do it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:projects) } -- cgit v1.2.1 From 31c41a8fe1ef67347e5bcfdb97ff15a079976945 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 10:50:01 +0500 Subject: Use build instead create record in appearance_spec --- spec/models/appearance_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index c5658bd26e1..0b72a2f979b 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe Appearance, type: :model do - subject { create(:appearance) } + subject { build(:appearance) } it { is_expected.to be_valid } -- cgit v1.2.1 From b9b13ea8016077e186374e4ee45dfc8a59ea5a78 Mon Sep 17 00:00:00 2001 From: Thomas Balthazar Date: Mon, 8 Aug 2016 13:42:24 +0200 Subject: Create a new /templates API namespace The /licenses, /gitignores and /gitlab_ci_ymls endpoints are now also available under a new /templates namespace. Old endpoints will be deprecated when GitLab 9.0.0 is released. --- CHANGELOG | 1 + app/assets/javascripts/api.js | 7 +- doc/api/README.md | 4 +- doc/api/licenses.md | 147 ------- doc/api/templates/gitignores.md | 579 ++++++++++++++++++++++++++++ doc/api/templates/gitlab_ci_ymls.md | 120 ++++++ doc/api/templates/licenses.md | 147 +++++++ lib/api/api.rb | 1 - lib/api/license_templates.rb | 58 --- lib/api/templates.rb | 124 ++++-- spec/requests/api/license_templates_spec.rb | 136 ------- spec/requests/api/templates_spec.rb | 204 ++++++++-- 12 files changed, 1129 insertions(+), 399 deletions(-) delete mode 100644 doc/api/licenses.md create mode 100644 doc/api/templates/gitignores.md create mode 100644 doc/api/templates/gitlab_ci_ymls.md create mode 100644 doc/api/templates/licenses.md delete mode 100644 lib/api/license_templates.rb delete mode 100644 spec/requests/api/license_templates_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 201bab610ba..65d890185fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.13.0 (unreleased) - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Fix Error 500 when viewing old merge requests with bad diff data + - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 599331df3f5..56ec1489f89 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -6,11 +6,10 @@ groupProjectsPath: "/api/:version/groups/:id/projects.json", projectsPath: "/api/:version/projects.json?simple=true", labelsPath: "/:namespace_path/:project_path/labels", - licensePath: "/api/:version/licenses/:key", - gitignorePath: "/api/:version/gitignores/:key", - gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key", + licensePath: "/api/:version/templates/licenses/:key", + gitignorePath: "/api/:version/templates/gitignores/:key", + gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key", issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", - group: function(group_id, callback) { var url = Api.buildUrl(Api.groupPath) .replace(':id', group_id); diff --git a/doc/api/README.md b/doc/api/README.md index 9e907689c80..00cb003ed8a 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -17,6 +17,8 @@ following locations: - [Commits](commits.md) - [Deployments](deployments.md) - [Deploy Keys](deploy_keys.md) +- [Gitignores templates](templates/gitignores.md) +- [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [Groups](groups.md) - [Group Access Requests](access_requests.md) - [Group Members](members.md) @@ -25,7 +27,7 @@ following locations: - [Labels](labels.md) - [Merge Requests](merge_requests.md) - [Milestones](milestones.md) -- [Open source license templates](licenses.md) +- [Open source license templates](templates/licenses.md) - [Namespaces](namespaces.md) - [Notes](notes.md) (comments) - [Notification settings](notification_settings.md) diff --git a/doc/api/licenses.md b/doc/api/licenses.md deleted file mode 100644 index ed26d1fb7fb..00000000000 --- a/doc/api/licenses.md +++ /dev/null @@ -1,147 +0,0 @@ -# Licenses - -## List license templates - -Get all license templates. - -``` -GET /licenses -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | -------- | --------------------- | -| `popular` | boolean | no | If passed, returns only popular licenses | - -```bash -curl https://gitlab.example.com/api/v3/licenses?popular=1 -``` - -Example response: - -```json -[ - { - "key": "apache-2.0", - "name": "Apache License 2.0", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/apache-2.0/", - "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", - "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", - "conditions": [ - "include-copyright", - "document-changes" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "patent-use", - "private-use" - ], - "limitations": [ - "trademark-use", - "no-liability" - ], - "content": " Apache License\n Version 2.0, January 2004\n [...]" - }, - { - "key": "gpl-3.0", - "name": "GNU General Public License v3.0", - "nickname": "GNU GPLv3", - "featured": true, - "html_url": "http://choosealicense.com/licenses/gpl-3.0/", - "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", - "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", - "conditions": [ - "include-copyright", - "document-changes", - "disclose-source", - "same-license" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "patent-use", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" - }, - { - "key": "mit", - "name": "MIT License", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/mit/", - "source_url": "http://opensource.org/licenses/MIT", - "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", - "conditions": [ - "include-copyright" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" - } -] -``` - -## Single license template - -Get a single license template. You can pass parameters to replace the license -placeholder. - -``` -GET /licenses/:key -``` - -| Attribute | Type | Required | Description | -| ---------- | ------ | -------- | ----------- | -| `key` | string | yes | The key of the license template | -| `project` | string | no | The copyrighted project name | -| `fullname` | string | no | The full-name of the copyright holder | - ->**Note:** -If you omit the `fullname` parameter but authenticate your request, the name of -the authenticated user will be used to replace the copyright holder placeholder. - -```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project -``` - -Example response: - -```json -{ - "key": "mit", - "name": "MIT License", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/mit/", - "source_url": "http://opensource.org/licenses/MIT", - "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", - "conditions": [ - "include-copyright" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" -} -``` diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md new file mode 100644 index 00000000000..8235be92b12 --- /dev/null +++ b/doc/api/templates/gitignores.md @@ -0,0 +1,579 @@ +# Gitignores + +## List gitignore templates + +Get all gitignore templates. + +``` +GET /templates/gitignores +``` + +```bash +curl https://gitlab.example.com/api/v3/templates/gitignores +``` + +Example response: + +```json +[ + { + "name": "AppEngine" + }, + { + "name": "Laravel" + }, + { + "name": "Elisp" + }, + { + "name": "SketchUp" + }, + { + "name": "Ada" + }, + { + "name": "Ruby" + }, + { + "name": "Kohana" + }, + { + "name": "Nanoc" + }, + { + "name": "Erlang" + }, + { + "name": "OCaml" + }, + { + "name": "Lithium" + }, + { + "name": "Fortran" + }, + { + "name": "Scala" + }, + { + "name": "Node" + }, + { + "name": "Fancy" + }, + { + "name": "Perl" + }, + { + "name": "Zephir" + }, + { + "name": "WordPress" + }, + { + "name": "Symfony" + }, + { + "name": "FuelPHP" + }, + { + "name": "DM" + }, + { + "name": "Sdcc" + }, + { + "name": "Rust" + }, + { + "name": "C" + }, + { + "name": "Umbraco" + }, + { + "name": "Actionscript" + }, + { + "name": "Android" + }, + { + "name": "Grails" + }, + { + "name": "Composer" + }, + { + "name": "ExpressionEngine" + }, + { + "name": "Gcov" + }, + { + "name": "Qt" + }, + { + "name": "Phalcon" + }, + { + "name": "ArchLinuxPackages" + }, + { + "name": "TeX" + }, + { + "name": "SCons" + }, + { + "name": "Lilypond" + }, + { + "name": "CommonLisp" + }, + { + "name": "Rails" + }, + { + "name": "Mercury" + }, + { + "name": "Magento" + }, + { + "name": "ChefCookbook" + }, + { + "name": "GitBook" + }, + { + "name": "C++" + }, + { + "name": "Eagle" + }, + { + "name": "Go" + }, + { + "name": "OpenCart" + }, + { + "name": "Scheme" + }, + { + "name": "Typo3" + }, + { + "name": "SeamGen" + }, + { + "name": "Swift" + }, + { + "name": "Elm" + }, + { + "name": "Unity" + }, + { + "name": "Agda" + }, + { + "name": "CUDA" + }, + { + "name": "VVVV" + }, + { + "name": "Finale" + }, + { + "name": "LemonStand" + }, + { + "name": "Textpattern" + }, + { + "name": "Julia" + }, + { + "name": "Packer" + }, + { + "name": "Scrivener" + }, + { + "name": "Dart" + }, + { + "name": "Plone" + }, + { + "name": "Jekyll" + }, + { + "name": "Xojo" + }, + { + "name": "LabVIEW" + }, + { + "name": "Autotools" + }, + { + "name": "KiCad" + }, + { + "name": "Prestashop" + }, + { + "name": "ROS" + }, + { + "name": "Smalltalk" + }, + { + "name": "GWT" + }, + { + "name": "OracleForms" + }, + { + "name": "SugarCRM" + }, + { + "name": "Nim" + }, + { + "name": "SymphonyCMS" + }, + { + "name": "Maven" + }, + { + "name": "CFWheels" + }, + { + "name": "Python" + }, + { + "name": "ZendFramework" + }, + { + "name": "CakePHP" + }, + { + "name": "Concrete5" + }, + { + "name": "PlayFramework" + }, + { + "name": "Terraform" + }, + { + "name": "Elixir" + }, + { + "name": "CMake" + }, + { + "name": "Joomla" + }, + { + "name": "Coq" + }, + { + "name": "Delphi" + }, + { + "name": "Haskell" + }, + { + "name": "Yii" + }, + { + "name": "Java" + }, + { + "name": "UnrealEngine" + }, + { + "name": "AppceleratorTitanium" + }, + { + "name": "CraftCMS" + }, + { + "name": "ForceDotCom" + }, + { + "name": "ExtJs" + }, + { + "name": "MetaProgrammingSystem" + }, + { + "name": "D" + }, + { + "name": "Objective-C" + }, + { + "name": "RhodesRhomobile" + }, + { + "name": "R" + }, + { + "name": "EPiServer" + }, + { + "name": "Yeoman" + }, + { + "name": "VisualStudio" + }, + { + "name": "Processing" + }, + { + "name": "Leiningen" + }, + { + "name": "Stella" + }, + { + "name": "Opa" + }, + { + "name": "Drupal" + }, + { + "name": "TurboGears2" + }, + { + "name": "Idris" + }, + { + "name": "Jboss" + }, + { + "name": "CodeIgniter" + }, + { + "name": "Qooxdoo" + }, + { + "name": "Waf" + }, + { + "name": "Sass" + }, + { + "name": "Lua" + }, + { + "name": "Clojure" + }, + { + "name": "IGORPro" + }, + { + "name": "Gradle" + }, + { + "name": "Archives" + }, + { + "name": "SynopsysVCS" + }, + { + "name": "Ninja" + }, + { + "name": "Tags" + }, + { + "name": "OSX" + }, + { + "name": "Dreamweaver" + }, + { + "name": "CodeKit" + }, + { + "name": "NotepadPP" + }, + { + "name": "VisualStudioCode" + }, + { + "name": "Mercurial" + }, + { + "name": "BricxCC" + }, + { + "name": "DartEditor" + }, + { + "name": "Eclipse" + }, + { + "name": "Cloud9" + }, + { + "name": "TortoiseGit" + }, + { + "name": "NetBeans" + }, + { + "name": "GPG" + }, + { + "name": "Espresso" + }, + { + "name": "Redcar" + }, + { + "name": "Xcode" + }, + { + "name": "Matlab" + }, + { + "name": "LyX" + }, + { + "name": "SlickEdit" + }, + { + "name": "Dropbox" + }, + { + "name": "CVS" + }, + { + "name": "Calabash" + }, + { + "name": "JDeveloper" + }, + { + "name": "Vagrant" + }, + { + "name": "IPythonNotebook" + }, + { + "name": "TextMate" + }, + { + "name": "Ensime" + }, + { + "name": "WebMethods" + }, + { + "name": "VirtualEnv" + }, + { + "name": "Emacs" + }, + { + "name": "Momentics" + }, + { + "name": "JetBrains" + }, + { + "name": "SublimeText" + }, + { + "name": "Kate" + }, + { + "name": "ModelSim" + }, + { + "name": "Redis" + }, + { + "name": "KDevelop4" + }, + { + "name": "Bazaar" + }, + { + "name": "Linux" + }, + { + "name": "Windows" + }, + { + "name": "XilinxISE" + }, + { + "name": "Lazarus" + }, + { + "name": "EiffelStudio" + }, + { + "name": "Anjuta" + }, + { + "name": "Vim" + }, + { + "name": "Otto" + }, + { + "name": "MicrosoftOffice" + }, + { + "name": "LibreOffice" + }, + { + "name": "SBT" + }, + { + "name": "MonoDevelop" + }, + { + "name": "SVN" + }, + { + "name": "FlexBuilder" + } +] +``` + +## Single gitignore template + +Get a single gitignore template. + +``` +GET /templates/gitignores/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the gitignore template | + +```bash +curl https://gitlab.example.com/api/v3/templates/gitignores/Ruby +``` + +Example response: + +```json +{ + "name": "Ruby", + "content": "*.gem\n*.rbc\n/.config\n/coverage/\n/InstalledFiles\n/pkg/\n/spec/reports/\n/spec/examples.txt\n/test/tmp/\n/test/version_tmp/\n/tmp/\n\n# Used by dotenv library to load environment variables.\n# .env\n\n## Specific to RubyMotion:\n.dat*\n.repl_history\nbuild/\n*.bridgesupport\nbuild-iPhoneOS/\nbuild-iPhoneSimulator/\n\n## Specific to RubyMotion (use of CocoaPods):\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# vendor/Pods/\n\n## Documentation cache and generated files:\n/.yardoc/\n/_yardoc/\n/doc/\n/rdoc/\n\n## Environment normalization:\n/.bundle/\n/vendor/bundle\n/lib/bundler/man/\n\n# for a library or gem, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# Gemfile.lock\n# .ruby-version\n# .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n" +} +``` diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md new file mode 100644 index 00000000000..e120016fbe6 --- /dev/null +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -0,0 +1,120 @@ +# GitLab CI YMLs + +## List GitLab CI YML templates + +Get all GitLab CI YML templates. + +``` +GET /templates/gitlab_ci_ymls +``` + +```bash +curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls +``` + +Example response: + +```json +[ + { + "name": "C++" + }, + { + "name": "Docker" + }, + { + "name": "Elixir" + }, + { + "name": "LaTeX" + }, + { + "name": "Grails" + }, + { + "name": "Rust" + }, + { + "name": "Nodejs" + }, + { + "name": "Ruby" + }, + { + "name": "Scala" + }, + { + "name": "Maven" + }, + { + "name": "Harp" + }, + { + "name": "Pelican" + }, + { + "name": "Hyde" + }, + { + "name": "Nanoc" + }, + { + "name": "Octopress" + }, + { + "name": "JBake" + }, + { + "name": "HTML" + }, + { + "name": "Hugo" + }, + { + "name": "Metalsmith" + }, + { + "name": "Hexo" + }, + { + "name": "Lektor" + }, + { + "name": "Doxygen" + }, + { + "name": "Brunch" + }, + { + "name": "Jekyll" + }, + { + "name": "Middleman" + } +] +``` + +## Single GitLab CI YML template + +Get a single GitLab CI YML template. + +``` +GET /templates/gitlab_ci_ymls/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the GitLab CI YML template | + +```bash +curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls/Ruby +``` + +Example response: + +```json +{ + "name": "Ruby", + "content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.3\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - bundle exec rake db:migrate\n - bundle exec rake db:seed\n - bundle exec rake test\n" +} +``` diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md new file mode 100644 index 00000000000..ae7218cf1bd --- /dev/null +++ b/doc/api/templates/licenses.md @@ -0,0 +1,147 @@ +# Licenses + +## List license templates + +Get all license templates. + +``` +GET /templates/licenses +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `popular` | boolean | no | If passed, returns only popular licenses | + +```bash +curl https://gitlab.example.com/api/v3/templates/licenses?popular=1 +``` + +Example response: + +```json +[ + { + "key": "apache-2.0", + "name": "Apache License 2.0", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/apache-2.0/", + "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", + "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", + "conditions": [ + "include-copyright", + "document-changes" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "trademark-use", + "no-liability" + ], + "content": " Apache License\n Version 2.0, January 2004\n [...]" + }, + { + "key": "gpl-3.0", + "name": "GNU General Public License v3.0", + "nickname": "GNU GPLv3", + "featured": true, + "html_url": "http://choosealicense.com/licenses/gpl-3.0/", + "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", + "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", + "conditions": [ + "include-copyright", + "document-changes", + "disclose-source", + "same-license" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" + }, + { + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" + } +] +``` + +## Single license template + +Get a single license template. You can pass parameters to replace the license +placeholder. + +``` +GET /templates/licenses/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the license template | +| `project` | string | no | The copyrighted project name | +| `fullname` | string | no | The full-name of the copyright holder | + +>**Note:** +If you omit the `fullname` parameter but authenticate your request, the name of +the authenticated user will be used to replace the copyright holder placeholder. + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/templates/licenses/mit?project=My+Cool+Project +``` + +Example response: + +```json +{ + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..c64d444842d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -46,7 +46,6 @@ module API mount ::API::Boards mount ::API::Keys mount ::API::Labels - mount ::API::LicenseTemplates mount ::API::Lint mount ::API::Members mount ::API::MergeRequests diff --git a/lib/api/license_templates.rb b/lib/api/license_templates.rb deleted file mode 100644 index d0552299ed0..00000000000 --- a/lib/api/license_templates.rb +++ /dev/null @@ -1,58 +0,0 @@ -module API - # License Templates API - class LicenseTemplates < Grape::API - PROJECT_TEMPLATE_REGEX = - /[\<\{\[] - (project|description| - one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here - [\>\}\]]/xi.freeze - YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze - FULLNAME_TEMPLATE_REGEX = - /[\<\{\[] - (fullname|name\sof\s(author|copyright\sowner)) - [\>\}\]]/xi.freeze - - # Get the list of the available license templates - # - # Parameters: - # popular - Filter licenses to only the popular ones - # - # Example Request: - # GET /licenses - # GET /licenses?popular=1 - get 'licenses' do - options = { - featured: params[:popular].present? ? true : nil - } - present Licensee::License.all(options), with: Entities::RepoLicense - end - - # Get text for specific license - # - # Parameters: - # key (required) - The key of a license - # project - Copyrighted project name - # fullname - Full name of copyright holder - # - # Example Request: - # GET /licenses/mit - # - get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do - required_attributes! [:key] - - not_found!('License') unless Licensee::License.find(params[:key]) - - # We create a fresh Licensee::License object since we'll modify its - # content in place below. - license = Licensee::License.new(params[:key]) - - license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) - license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? - - fullname = params[:fullname].presence || current_user.try(:name) - license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname - - present license, with: Entities::RepoLicense - end - end -end diff --git a/lib/api/templates.rb b/lib/api/templates.rb index b9e718147e1..8a53d9c0095 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -1,39 +1,115 @@ module API class Templates < Grape::API GLOBAL_TEMPLATE_TYPES = { - gitignores: Gitlab::Template::GitignoreTemplate, - gitlab_ci_ymls: Gitlab::Template::GitlabCiYmlTemplate + gitignores: { + klass: Gitlab::Template::GitignoreTemplate, + gitlab_version: 8.8 + }, + gitlab_ci_ymls: { + klass: Gitlab::Template::GitlabCiYmlTemplate, + gitlab_version: 8.9 + } }.freeze + PROJECT_TEMPLATE_REGEX = + /[\<\{\[] + (project|description| + one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here + [\>\}\]]/xi.freeze + YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze + FULLNAME_TEMPLATE_REGEX = + /[\<\{\[] + (fullname|name\sof\s(author|copyright\sowner)) + [\>\}\]]/xi.freeze + DEPRECATION_MESSAGE = ' This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze helpers do + def parsed_license_template + # We create a fresh Licensee::License object since we'll modify its + # content in place below. + template = Licensee::License.new(params[:name]) + + template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) + template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? + + fullname = params[:fullname].presence || current_user.try(:name) + template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname + template + end + def render_response(template_type, template) not_found!(template_type.to_s.singularize) unless template present template, with: Entities::Template end end - GLOBAL_TEMPLATE_TYPES.each do |template_type, klass| - # Get the list of the available template - # - # Example Request: - # GET /gitignores - # GET /gitlab_ci_ymls - get template_type.to_s do - present klass.all, with: Entities::TemplatesList - end - - # Get the text for a specific template present in local filesystem - # - # Parameters: - # name (required) - The name of a template - # - # Example Request: - # GET /gitignores/Elixir - # GET /gitlab_ci_ymls/Ruby - get "#{template_type}/:name" do - required_attributes! [:name] - new_template = klass.find(params[:name]) - render_response(template_type, new_template) + { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status| + desc 'Get the list of the available license template' do + detailed_desc = 'This feature was introduced in GitLab 8.7.' + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::RepoLicense + end + params do + optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses' + end + get route do + options = { + featured: declared(params).popular.present? ? true : nil + } + present Licensee::License.all(options), with: Entities::RepoLicense + end + end + + { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status| + desc 'Get the text for a specific license' do + detailed_desc = 'This feature was introduced in GitLab 8.7.' + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::RepoLicense + end + params do + requires :name, type: String, desc: 'The name of the template' + end + get route, requirements: { name: /[\w\.-]+/ } do + not_found!('License') unless Licensee::License.find(declared(params).name) + + template = parsed_license_template + + present template, with: Entities::RepoLicense + end + end + + GLOBAL_TEMPLATE_TYPES.each do |template_type, properties| + klass = properties[:klass] + gitlab_version = properties[:gitlab_version] + + { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status| + desc 'Get the list of the available template' do + detailed_desc = "This feature was introduced in GitLab #{gitlab_version}." + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::TemplatesList + end + get route do + present klass.all, with: Entities::TemplatesList + end + end + + { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status| + desc 'Get the text for a specific template present in local filesystem' do + detailed_desc = "This feature was introduced in GitLab #{gitlab_version}." + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::Template + end + params do + requires :name, type: String, desc: 'The name of the template' + end + get route do + new_template = klass.find(declared(params).name) + + render_response(template_type, new_template) + end end end end diff --git a/spec/requests/api/license_templates_spec.rb b/spec/requests/api/license_templates_spec.rb deleted file mode 100644 index 9a1894d63a2..00000000000 --- a/spec/requests/api/license_templates_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'spec_helper' - -describe API::API, api: true do - include ApiHelpers - - describe 'Entity' do - before { get api('/licenses/mit') } - - it { expect(json_response['key']).to eq('mit') } - it { expect(json_response['name']).to eq('MIT License') } - it { expect(json_response['nickname']).to be_nil } - it { expect(json_response['popular']).to be true } - it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') } - it { expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') } - it { expect(json_response['description']).to include('A permissive license that is short and to the point.') } - it { expect(json_response['conditions']).to eq(%w[include-copyright]) } - it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) } - it { expect(json_response['limitations']).to eq(%w[no-liability]) } - it { expect(json_response['content']).to include('The MIT License (MIT)') } - end - - describe 'GET /licenses' do - it 'returns a list of available license templates' do - get api('/licenses') - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(15) - expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') - end - - describe 'the popular parameter' do - context 'with popular=1' do - it 'returns a list of available popular license templates' do - get api('/licenses?popular=1') - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(3) - expect(json_response.map { |l| l['key'] }).to include('apache-2.0') - end - end - end - end - - describe 'GET /licenses/:key' do - context 'with :project and :fullname given' do - before do - get api("/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") - end - - context 'for the mit license' do - let(:license_type) { 'mit' } - - it 'returns the license text' do - expect(json_response['content']).to include('The MIT License (MIT)') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") - end - end - - context 'for the agpl-3.0 license' do - let(:license_type) { 'agpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-3.0 license' do - let(:license_type) { 'gpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-2.0 license' do - let(:license_type) { 'gpl-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the apache-2.0 license' do - let(:license_type) { 'apache-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('Apache License') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") - end - end - - context 'for an uknown license' do - let(:license_type) { 'muth-over9000' } - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'with no :fullname given' do - context 'with an authenticated user' do - let(:user) { create(:user) } - - it 'replaces the copyright owner placeholder with the name of the current user' do - get api('/licenses/mit', user) - - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") - end - end - end - end -end diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index 5bd5b861792..d32ba60fc4c 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -3,53 +3,201 @@ require 'spec_helper' describe API::Templates, api: true do include ApiHelpers - context 'global templates' do - describe 'the Template Entity' do - before { get api('/gitignores/Ruby') } + shared_examples_for 'the Template Entity' do |path| + before { get api(path) } - it { expect(json_response['name']).to eq('Ruby') } - it { expect(json_response['content']).to include('*.gem') } + it { expect(json_response['name']).to eq('Ruby') } + it { expect(json_response['content']).to include('*.gem') } + end + + shared_examples_for 'the TemplateList Entity' do |path| + before { get api(path) } + + it { expect(json_response.first['name']).not_to be_nil } + it { expect(json_response.first['content']).to be_nil } + end + + shared_examples_for 'requesting gitignores' do |path| + it 'returns a list of available gitignore templates' do + get api(path) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to be > 15 end + end - describe 'the TemplateList Entity' do - before { get api('/gitignores') } + shared_examples_for 'requesting gitlab-ci-ymls' do |path| + it 'returns a list of available gitlab_ci_ymls' do + get api(path) - it { expect(json_response.first['name']).not_to be_nil } - it { expect(json_response.first['content']).to be_nil } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).not_to be_nil end + end + + shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path| + it 'adds a disclaimer on the top' do + get api(path) + + expect(response).to have_http_status(200) + expect(json_response['content']).to start_with("# This file is a template,") + end + end + + shared_examples_for 'the License Template Entity' do |path| + before { get api(path) } + + it 'returns a license template' do + expect(json_response['key']).to eq('mit') + expect(json_response['name']).to eq('MIT License') + expect(json_response['nickname']).to be_nil + expect(json_response['popular']).to be true + expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') + expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') + expect(json_response['description']).to include('A permissive license that is short and to the point.') + expect(json_response['conditions']).to eq(%w[include-copyright]) + expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) + expect(json_response['limitations']).to eq(%w[no-liability]) + expect(json_response['content']).to include('The MIT License (MIT)') + end + end - context 'requesting gitignores' do - describe 'GET /gitignores' do - it 'returns a list of available gitignore templates' do - get api('/gitignores') + shared_examples_for 'GET licenses' do |path| + it 'returns a list of available license templates' do + get api(path) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(15) + expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') + end + + describe 'the popular parameter' do + context 'with popular=1' do + it 'returns a list of available popular license templates' do + get api("#{path}?popular=1") + + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.size).to be > 15 + expect(json_response.size).to eq(3) + expect(json_response.map { |l| l['key'] }).to include('apache-2.0') end end end + end - context 'requesting gitlab-ci-ymls' do - describe 'GET /gitlab_ci_ymls' do - it 'returns a list of available gitlab_ci_ymls' do - get api('/gitlab_ci_ymls') + shared_examples_for 'GET licenses/:name' do |path| + context 'with :project and :fullname given' do + before do + get api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") + end - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).not_to be_nil + context 'for the mit license' do + let(:license_type) { 'mit' } + + it 'returns the license text' do + expect(json_response['content']).to include('The MIT License (MIT)') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") + end + end + + context 'for the agpl-3.0 license' do + let(:license_type) { 'agpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the gpl-3.0 license' do + let(:license_type) { 'gpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the gpl-2.0 license' do + let(:license_type) { 'gpl-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the apache-2.0 license' do + let(:license_type) { 'apache-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('Apache License') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") + end + end + + context 'for an uknown license' do + let(:license_type) { 'muth-over9000' } + + it 'returns a 404' do + expect(response).to have_http_status(404) end end end - describe 'GET /gitlab_ci_ymls/Ruby' do - it 'adds a disclaimer on the top' do - get api('/gitlab_ci_ymls/Ruby') + context 'with no :fullname given' do + context 'with an authenticated user' do + let(:user) { create(:user) } + + it 'replaces the copyright owner placeholder with the name of the current user' do + get api('/templates/licenses/mit', user) - expect(response).to have_http_status(200) - expect(json_response['name']).not_to be_nil - expect(json_response['content']).to start_with("# This file is a template,") + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") + end end end end + + describe 'with /templates namespace' do + it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby' + it_behaves_like 'the TemplateList Entity', '/templates/gitignores' + it_behaves_like 'requesting gitignores', '/templates/gitignores' + it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls' + it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby' + it_behaves_like 'the License Template Entity', '/templates/licenses/mit' + it_behaves_like 'GET licenses', '/templates/licenses' + it_behaves_like 'GET licenses/:name', '/templates/licenses' + end + + describe 'without /templates namespace' do + it_behaves_like 'the Template Entity', '/gitignores/Ruby' + it_behaves_like 'the TemplateList Entity', '/gitignores' + it_behaves_like 'requesting gitignores', '/gitignores' + it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls' + it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby' + it_behaves_like 'the License Template Entity', '/licenses/mit' + it_behaves_like 'GET licenses', '/licenses' + it_behaves_like 'GET licenses/:name', '/licenses' + end end -- cgit v1.2.1 From df404f551a29258fa5b97c0e7abe144fd7a0fb31 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 12:07:58 +0200 Subject: Schedule async pipeline success worker after commit --- app/models/ci/pipeline.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d3a6e0a11f0..957f6755b2e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,6 +3,7 @@ module Ci extend Ci::Model include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_commits' @@ -71,7 +72,7 @@ module Ci end after_transition [:created, :pending, :running] => :success do |pipeline| - PipelineSuccessWorker.perform_async(pipeline.id) + pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) } end after_transition do |pipeline, transition| -- cgit v1.2.1 From 9eedd6f9c7baa5693a24c33c29909bfc7dac1b21 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 12:23:52 +0200 Subject: Improve desc for pipeline integration spec in MWBS --- .../merge_when_build_succeeds_service_spec.rb | 36 ++++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 01cdc172136..b80cfd8f450 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -98,7 +98,26 @@ describe MergeRequests::MergeWhenBuildSucceedsService do service.trigger(unrelated_pipeline) end end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the merge_when_build_succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'Canceled the automatic merge' + end + end + describe 'pipeline integration' do context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } let(:sha) { project.commit(ref).id } @@ -139,21 +158,4 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end end end - - describe "#cancel" do - before do - service.cancel(mr_merge_if_green_enabled) - end - - it "resets all the merge_when_build_succeeds params" do - expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey - expect(mr_merge_if_green_enabled.merge_params).to eq({}) - expect(mr_merge_if_green_enabled.merge_user).to be nil - end - - it 'Posts a system note' do - note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Canceled the automatic merge' - end - end end -- cgit v1.2.1 From 2b37f040b612303b714ec8cee0b482427e7c2b3c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 11 Oct 2016 12:58:56 +0200 Subject: Ignore deployment for statistics in Cycle Analytics, except in staging and production stages Also, updated specs and docs. --- CHANGELOG | 1 + app/models/cycle_analytics.rb | 14 +++-- doc/user/project/cycle_analytics.md | 22 ++++---- spec/models/cycle_analytics/code_spec.rb | 88 +++++++++++++++++++++--------- spec/models/cycle_analytics/issue_spec.rb | 2 - spec/models/cycle_analytics/plan_spec.rb | 2 - spec/models/cycle_analytics/review_spec.rb | 4 +- spec/models/cycle_analytics/test_spec.rb | 5 -- 8 files changed, 83 insertions(+), 55 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba6c3f7c558..576b755d06b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Make searching for commits case insensitive diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index be295487fd2..0f3fd995681 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -2,6 +2,8 @@ class CycleAnalytics include Gitlab::Database::Median include Gitlab::Database::DateTime + DEPLOYED_CHECK_METRICS = %i[production staging] + def initialize(project, from:) @project = project @from = from @@ -66,7 +68,7 @@ class CycleAnalytics # cycle analytics stage. interval_query = Arel::Nodes::As.new( cte_table, - subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s)) + subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s)) median_datetime(cte_table, interval_query, name) end @@ -75,7 +77,7 @@ class CycleAnalytics # closes the given issue) with issue and merge request metrics included. The metrics # are loaded with an inner join, so issues / merge requests without metrics are # automatically excluded. - def base_query + def base_query_for(name) arel_table = MergeRequestsClosingIssues.arel_table # Load issues @@ -91,7 +93,11 @@ class CycleAnalytics join(MergeRequest::Metrics.arel_table). on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) - # Limit to merge requests that have been deployed to production after `@from` - query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) + if DEPLOYED_CHECK_METRICS.include?(name) + # Limit to merge requests that have been deployed to production after `@from` + query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) + end + + query end end diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index c16058165d7..1892ccabb70 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -3,8 +3,8 @@ > [Introduced][ce-5986] in GitLab 8.12. > > **Note:** -This the first iteration of Cycle Analytics, you can follow the following issue -to track the changes that are coming to this feature: [#20975][ce-20975]. +There are more changes coming to Cycle Analytics, you can follow the following +issue to track the changes to this feature: [#20975][ce-20975]. Cycle Analytics measures the time it takes to go from an [idea to production] for each project you have. This is achieved by not only indicating the total time it @@ -48,13 +48,12 @@ You can see that there are seven stages in total: ## How the data is measured -Cycle Analytics records cycle time so only data on the issues that have been -deployed to production are measured. In case you just started a new project and -you have not pushed anything to production, then you will not be able to -properly see the Cycle Analytics of your project. +Cycle Analytics records cycle time and data based on the project issues with the +exception of the staging and production stages, where only data deployed to +production are measured. Specifically, if your CI is not set up and you have not defined a `production` -[environment], then you will not have any data. +[environment], then you will not have any data for those stages. Below you can see in more detail what the various stages of Cycle Analytics mean. @@ -76,9 +75,8 @@ Here's a little explanation of how this works behind the scenes: `` pair, the merge request has the [issue closing pattern] for the corresponding issue. All other issues and merge requests are **not** considered. -1. Then the pairs are filtered out. Any merge request - that has **not** been deployed to production in the last XX days (specified - by the UI - default is 90 days) prohibits these pairs from being considered. +1. Then the pairs are filtered out by last XX days (specified + by the UI - default is 90 days). So it prohibits these pairs from being considered. 1. For the remaining `` pairs, we check the information that we need for the stages, like issue creation date, merge request merge time, etc. @@ -86,8 +84,8 @@ Here's a little explanation of how this works behind the scenes: To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all. So, if a merge request doesn't close an issue or an issue is not labeled with a label present in the Issue Board or assigned a milestone or a project has no -`production` environment, the Cycle Analytics dashboard won't present any data -at all. +`production` environment (for staging and production stages), the Cycle Analytics +dashboard won't present any data at all. ## Example workflow diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index b9381e33914..7691d690db0 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -8,35 +8,69 @@ describe 'CycleAnalytics#code', feature: true do let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } - generate_cycle_analytics_spec( - phase: :code, - data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, - start_time_conditions: [["issue mentioned in a commit", - -> (context, data) do - context.create_commit_referencing_issue(data[:issue]) - end]], - end_time_conditions: [["merge request that closes issue is created", - -> (context, data) do - context.create_merge_request_closing_issue(data[:issue]) - end]], - post_fn: -> (context, data) do - context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master - end) - - context "when a regular merge request (that doesn't close the issue) is created" do - it "returns nil" do - 5.times do - issue = create(:issue, project: project) - - create_commit_referencing_issue(issue) - create_merge_request_closing_issue(issue, message: "Closes nothing") - - merge_merge_requests_closing_issue(issue) - deploy_master + context 'with deployment' do + generate_cycle_analytics_spec( + phase: :code, + data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, + start_time_conditions: [["issue mentioned in a commit", + -> (context, data) do + context.create_commit_referencing_issue(data[:issue]) + end]], + end_time_conditions: [["merge request that closes issue is created", + -> (context, data) do + context.create_merge_request_closing_issue(data[:issue]) + end]], + post_fn: -> (context, data) do + context.merge_merge_requests_closing_issue(data[:issue]) + context.deploy_master + end) + + context "when a regular merge request (that doesn't close the issue) is created" do + it "returns nil" do + 5.times do + issue = create(:issue, project: project) + + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") + + merge_merge_requests_closing_issue(issue) + deploy_master + end + + expect(subject.code).to be_nil end + end + end - expect(subject.code).to be_nil + context 'without deployment' do + generate_cycle_analytics_spec( + phase: :code, + data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, + start_time_conditions: [["issue mentioned in a commit", + -> (context, data) do + context.create_commit_referencing_issue(data[:issue]) + end]], + end_time_conditions: [["merge request that closes issue is created", + -> (context, data) do + context.create_merge_request_closing_issue(data[:issue]) + end]], + post_fn: -> (context, data) do + context.merge_merge_requests_closing_issue(data[:issue]) + end) + + context "when a regular merge request (that doesn't close the issue) is created" do + it "returns nil" do + 5.times do + issue = create(:issue, project: project) + + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") + + merge_merge_requests_closing_issue(issue) + end + + expect(subject.code).to be_nil + end end end end diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index e9cc71254ab..f649b44d367 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -28,7 +28,6 @@ describe 'CycleAnalytics#issue', models: true do if data[:issue].persisted? context.create_merge_request_closing_issue(data[:issue].reload) context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end end) @@ -41,7 +40,6 @@ describe 'CycleAnalytics#issue', models: true do create_merge_request_closing_issue(issue) merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.issue).to be_nil diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index 5b8c96dc992..2cdefbeef21 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -31,7 +31,6 @@ describe 'CycleAnalytics#plan', feature: true do post_fn: -> (context, data) do context.create_merge_request_closing_issue(data[:issue], source_branch: data[:branch_name]) context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end) context "when a regular label (instead of a list label) is added to the issue" do @@ -44,7 +43,6 @@ describe 'CycleAnalytics#plan', feature: true do create_merge_request_closing_issue(issue, source_branch: branch_name) merge_merge_requests_closing_issue(issue) - deploy_master expect(subject.issue).to be_nil end diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index b6e26d8f261..0ed080a42b1 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -19,14 +19,12 @@ describe 'CycleAnalytics#review', feature: true do -> (context, data) do context.merge_merge_requests_closing_issue(data[:issue]) end]], - post_fn: -> (context, data) { context.deploy_master }) + post_fn: nil) context "when a regular merge request (that doesn't close the issue) is created and merged" do it "returns nil" do 5.times do MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) - - deploy_master end expect(subject.review).to be_nil diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 89ace0b2742..c454e3e0701 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -20,7 +20,6 @@ describe 'CycleAnalytics#test', feature: true do end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]], post_fn: -> (context, data) do context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end) context "when the pipeline is for a regular merge request (that doesn't close an issue)" do @@ -34,7 +33,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.succeed! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil @@ -49,7 +47,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.run! pipeline.succeed! - deploy_master end expect(subject.test).to be_nil @@ -67,7 +64,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.drop! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil @@ -85,7 +81,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.cancel! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil -- cgit v1.2.1 From f76f569bb87803b14c763427fc2875cca7dda892 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Oct 2016 12:50:47 +0100 Subject: Moved data attribute values into helper method --- app/helpers/boards_helper.rb | 12 ++++++++++++ app/views/projects/boards/index.html.haml | 6 +----- app/views/projects/boards/show.html.haml | 6 +----- 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 app/helpers/boards_helper.rb diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb new file mode 100644 index 00000000000..b7247ffa8b2 --- /dev/null +++ b/app/helpers/boards_helper.rb @@ -0,0 +1,12 @@ +module BoardsHelper + def board_data + board = @board || @boards.first + + { + endpoint: namespace_project_boards_path(@project.namespace, @project), + board_id: board.id, + disabled: !can?(current_user, :admin_list, @project), + issue_link_base: namespace_project_issues_path(@project.namespace, @project) + } + end +end diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 9252bbc08c1..885f8e34b55 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -10,11 +10,7 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", - "data-board-id" => "#{@boards.first.id}", - "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", - "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } +.boards-list#board-app{ "v-cloak" => true, data: board_data } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index d6e1ac5f3cf..885f8e34b55 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -10,11 +10,7 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", - "data-board-id" => "#{@board.id}", - "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", - "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } +.boards-list#board-app{ "v-cloak" => true, data: board_data } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" -- cgit v1.2.1 From 42ba19f339a63a1e49a40fd4df9c75470ec71f25 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 12 Oct 2016 14:30:49 +0200 Subject: fixed newline --- spec/models/cycle_analytics/test_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index c454e3e0701..02ddfeed9c1 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -46,7 +46,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.run! pipeline.succeed! - end expect(subject.test).to be_nil -- cgit v1.2.1 From 1102659262749ca184af38aa9ad6fba8b562d51f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 14:39:18 -0700 Subject: Add a bundle check step to ensure dependencies are correct This should help prevent merge issues in the future, which caused !6814 to be needed. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb6f691058e..05687d22b68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -263,6 +263,7 @@ bundler:audit: only: - master script: + - bundle check - "bundle exec bundle-audit check --update --ignore OSVDB-115941" migration paths: -- cgit v1.2.1 From 916210a0d4fb658a190a6da1dad82e8482741148 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 12 Oct 2016 05:49:10 -0700 Subject: Add a separate stage for bundle check --- .gitlab-ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05687d22b68..4130aa4b6e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -257,13 +257,18 @@ lint-doc: script: - scripts/lint-doc.sh +bundler:check: + stage: test + <<: *ruby-static-analysis + script: + - bundle check + bundler:audit: stage: test <<: *ruby-static-analysis only: - master script: - - bundle check - "bundle exec bundle-audit check --update --ignore OSVDB-115941" migration paths: -- cgit v1.2.1 From 254c8200ef185ad1c45db50c0141785bbb8238ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 12 Oct 2016 14:51:29 +0200 Subject: Use activerecord_sane_schema_dumper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- Gemfile | 2 + Gemfile.lock | 3 + db/schema.rb | 1174 +++++++++++++++++++++++++++++----------------------------- 3 files changed, 592 insertions(+), 587 deletions(-) diff --git a/Gemfile b/Gemfile index c5f1ce26daf..9d98a34a0d5 100644 --- a/Gemfile +++ b/Gemfile @@ -262,6 +262,8 @@ group :development do # thin instead webrick gem 'thin', '~> 1.7.0' + + gem 'activerecord_sane_schema_dumper', '0.2' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 2feec4c4eb5..69804c8c533 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,6 +38,8 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 1.5.2, < 3) railties (>= 4.0, < 5.1) + activerecord_sane_schema_dumper (0.2) + rails (>= 4, < 5) activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) @@ -805,6 +807,7 @@ DEPENDENCIES RedCloth (~> 4.3.2) ace-rails-ap (~> 4.1.0) activerecord-session_store (~> 1.0.0) + activerecord_sane_schema_dumper (= 0.2) acts-as-taggable-on (~> 4.0) addressable (~> 2.3.8) after_commit_queue (~> 1.3.0) diff --git a/db/schema.rb b/db/schema.rb index c5ddf9eee32..a362fd8f228 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -18,94 +18,94 @@ ActiveRecord::Schema.define(version: 20161007133303) do enable_extension "pg_trgm" create_table "abuse_reports", force: :cascade do |t| - t.integer "reporter_id" - t.integer "user_id" - t.text "message" + t.integer "reporter_id" + t.integer "user_id" + t.text "message" t.datetime "created_at" t.datetime "updated_at" - t.text "message_html" + t.text "message_html" end create_table "appearances", force: :cascade do |t| - t.string "title" - t.text "description" - t.string "header_logo" - t.string "logo" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "description_html" + t.string "title" + t.text "description" + t.string "header_logo" + t.string "logo" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "description_html" end create_table "application_settings", force: :cascade do |t| - t.integer "default_projects_limit" - t.boolean "signup_enabled" - t.boolean "signin_enabled" - t.boolean "gravatar_enabled" - t.text "sign_in_text" + t.integer "default_projects_limit" + t.boolean "signup_enabled" + t.boolean "signin_enabled" + t.boolean "gravatar_enabled" + t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url" - t.integer "default_branch_protection", default: 2 - t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false - t.integer "default_project_visibility" - t.integer "default_snippet_visibility" - t.text "domain_whitelist" - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false - t.text "import_sources" - t.text "help_page_text" - t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false - t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false - t.string "recaptcha_site_key" - t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false - t.string "sentry_dsn" - t.boolean "email_author_in_body", default: false - t.integer "default_group_visibility" - t.boolean "repository_checks_enabled", default: false - t.text "shared_runners_text" - t.integer "metrics_packet_size", default: 1 - t.text "disabled_oauth_sign_in_sources" - t.string "health_check_access_token" - t.boolean "send_user_confirmation_email", default: false - t.integer "container_registry_token_expire_delay", default: 5 - t.text "after_sign_up_text" - t.boolean "user_default_external", default: false, null: false - t.string "repository_storage", default: "default" - t.string "enabled_git_access_protocol" - t.boolean "domain_blacklist_enabled", default: false - t.text "domain_blacklist" - t.boolean "koding_enabled" - t.string "koding_url" - t.text "sign_in_text_html" - t.text "help_page_text_html" - t.text "shared_runners_text_html" - t.text "after_sign_up_text_html" + t.string "home_page_url" + t.integer "default_branch_protection", default: 2 + t.text "restricted_visibility_levels" + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false + t.integer "default_project_visibility" + t.integer "default_snippet_visibility" + t.text "domain_whitelist" + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path" + t.integer "session_expire_delay", default: 10080, null: false + t.text "import_sources" + t.text "help_page_text" + t.string "admin_notification_email" + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false + t.string "runners_registration_token" + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false + t.string "recaptcha_site_key" + t.string "recaptcha_private_key" + t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false + t.string "sentry_dsn" + t.boolean "email_author_in_body", default: false + t.integer "default_group_visibility" + t.boolean "repository_checks_enabled", default: false + t.text "shared_runners_text" + t.integer "metrics_packet_size", default: 1 + t.text "disabled_oauth_sign_in_sources" + t.string "health_check_access_token" + t.boolean "send_user_confirmation_email", default: false + t.integer "container_registry_token_expire_delay", default: 5 + t.text "after_sign_up_text" + t.boolean "user_default_external", default: false, null: false + t.string "repository_storage", default: "default" + t.string "enabled_git_access_protocol" + t.boolean "domain_blacklist_enabled", default: false + t.text "domain_blacklist" + t.boolean "koding_enabled" + t.string "koding_url" + t.text "sign_in_text_html" + t.text "help_page_text_html" + t.text "shared_runners_text_html" + t.text "after_sign_up_text_html" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", null: false - t.integer "entity_id", null: false - t.string "entity_type", null: false - t.text "details" + t.integer "author_id", null: false + t.string "type", null: false + t.integer "entity_id", null: false + t.string "entity_type", null: false + t.text "details" t.datetime "created_at" t.datetime "updated_at" end @@ -113,10 +113,10 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree create_table "award_emoji", force: :cascade do |t| - t.string "name" - t.integer "user_id" - t.integer "awardable_id" - t.string "awardable_type" + t.string "name" + t.integer "user_id" + t.integer "awardable_id" + t.string "awardable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -126,7 +126,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree create_table "boards", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -134,61 +134,61 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" t.datetime "created_at" t.datetime "updated_at" - t.string "color" - t.string "font" - t.text "message_html" + t.string "color" + t.string "font" + t.text "message_html" end create_table "ci_application_settings", force: :cascade do |t| - t.boolean "all_broken_builds" - t.boolean "add_pusher" + t.boolean "all_broken_builds" + t.boolean "add_pusher" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_builds", force: :cascade do |t| - t.integer "project_id" - t.string "status" + t.integer "project_id" + t.string "status" t.datetime "finished_at" - t.text "trace" + t.text "trace" t.datetime "created_at" t.datetime "updated_at" t.datetime "started_at" - t.integer "runner_id" - t.float "coverage" - t.integer "commit_id" - t.text "commands" - t.integer "job_id" - t.string "name" - t.boolean "deploy", default: false - t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage" - t.integer "trigger_request_id" - t.integer "stage_idx" - t.boolean "tag" - t.string "ref" - t.integer "user_id" - t.string "type" - t.string "target_url" - t.string "description" - t.text "artifacts_file" - t.integer "gl_project_id" - t.text "artifacts_metadata" - t.integer "erased_by_id" + t.integer "runner_id" + t.float "coverage" + t.integer "commit_id" + t.text "commands" + t.integer "job_id" + t.string "name" + t.boolean "deploy", default: false + t.text "options" + t.boolean "allow_failure", default: false, null: false + t.string "stage" + t.integer "trigger_request_id" + t.integer "stage_idx" + t.boolean "tag" + t.string "ref" + t.integer "user_id" + t.string "type" + t.string "target_url" + t.string "description" + t.text "artifacts_file" + t.integer "gl_project_id" + t.text "artifacts_metadata" + t.integer "erased_by_id" t.datetime "erased_at" t.datetime "artifacts_expire_at" - t.string "environment" - t.integer "artifacts_size", limit: 8 - t.string "when" - t.text "yaml_variables" + t.string "environment" + t.integer "artifacts_size", limit: 8 + t.string "when" + t.text "yaml_variables" t.datetime "queued_at" - t.string "token" + t.string "token" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -203,22 +203,22 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree create_table "ci_commits", force: :cascade do |t| - t.integer "project_id" - t.string "ref" - t.string "sha" - t.string "before_sha" - t.text "push_data" + t.integer "project_id" + t.string "ref" + t.string "sha" + t.string "before_sha" + t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false - t.text "yaml_errors" + t.boolean "tag", default: false + t.text "yaml_errors" t.datetime "committed_at" - t.integer "gl_project_id" - t.string "status" + t.integer "gl_project_id" + t.string "status" t.datetime "started_at" t.datetime "finished_at" - t.integer "duration" - t.integer "user_id" + t.integer "duration" + t.integer "user_id" end add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree @@ -228,140 +228,140 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree create_table "ci_events", force: :cascade do |t| - t.integer "project_id" - t.integer "user_id" - t.integer "is_admin" - t.text "description" + t.integer "project_id" + t.integer "user_id" + t.integer "is_admin" + t.text "description" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false - t.text "commands" - t.boolean "active", default: true, null: false + t.integer "project_id", null: false + t.text "commands" + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", default: "parallel" - t.string "refs" + t.string "name" + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", default: "parallel" + t.string "refs" t.datetime "deleted_at" end create_table "ci_projects", force: :cascade do |t| - t.string "name" - t.integer "timeout", default: 3600, null: false + t.string "name" + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token" - t.string "default_ref" - t.string "path" - t.boolean "always_build", default: false, null: false - t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo" - t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs" - t.string "coverage_regex" - t.boolean "shared_runners_enabled", default: false - t.text "generated_yaml_config" + t.string "token" + t.string "default_ref" + t.string "path" + t.boolean "always_build", default: false, null: false + t.integer "polling_interval" + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo" + t.integer "gitlab_id" + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs" + t.string "coverage_regex" + t.boolean "shared_runners_enabled", default: false + t.text "generated_yaml_config" end create_table "ci_runner_projects", force: :cascade do |t| - t.integer "runner_id", null: false - t.integer "project_id" + t.integer "runner_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "gl_project_id" + t.integer "gl_project_id" end add_index "ci_runner_projects", ["gl_project_id"], name: "index_ci_runner_projects_on_gl_project_id", using: :btree add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token" + t.string "token" t.datetime "created_at" t.datetime "updated_at" - t.string "description" + t.string "description" t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name" - t.string "version" - t.string "revision" - t.string "platform" - t.string "architecture" - t.boolean "run_untagged", default: true, null: false - t.boolean "locked", default: false, null: false + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name" + t.string "version" + t.string "revision" + t.string "platform" + t.string "architecture" + t.boolean "run_untagged", default: true, null: false + t.boolean "locked", default: false, null: false end add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", null: false - t.text "data" + t.string "session_id", null: false + t.text "data" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context", limit: 128 + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", limit: 128 t.datetime "created_at" end add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name" + t.string "name" t.integer "taggings_count", default: 0 end create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id", null: false - t.text "variables" + t.integer "trigger_id", null: false + t.text "variables" t.datetime "created_at" t.datetime "updated_at" - t.integer "commit_id" + t.integer "commit_id" end create_table "ci_triggers", force: :cascade do |t| - t.string "token" - t.integer "project_id" + t.string "token" + t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" - t.integer "gl_project_id" + t.integer "gl_project_id" end add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key" - t.text "value" - t.text "encrypted_value" - t.string "encrypted_value_salt" - t.string "encrypted_value_iv" + t.string "key" + t.text "value" + t.text "encrypted_value" + t.string "encrypted_value_salt" + t.string "encrypted_value_iv" t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "deploy_keys_projects", force: :cascade do |t| - t.integer "deploy_key_id", null: false - t.integer "project_id", null: false + t.integer "deploy_key_id", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -369,15 +369,15 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "deployments", force: :cascade do |t| - t.integer "iid", null: false - t.integer "project_id", null: false - t.integer "environment_id", null: false - t.string "ref", null: false - t.boolean "tag", null: false - t.string "sha", null: false - t.integer "user_id" - t.integer "deployable_id" - t.string "deployable_type" + t.integer "iid", null: false + t.integer "project_id", null: false + t.integer "environment_id", null: false + t.string "ref", null: false + t.boolean "tag", null: false + t.string "sha", null: false + t.integer "user_id" + t.integer "deployable_id" + t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -388,8 +388,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", null: false + t.integer "user_id", null: false + t.string "email", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -398,26 +398,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "environments", force: :cascade do |t| - t.integer "project_id" - t.string "name", null: false + t.integer "project_id" + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "external_url" - t.string "environment_type" + t.string "external_url" + t.string "environment_type" end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type" - t.integer "target_id" - t.string "title" - t.text "data" - t.integer "project_id" + t.string "target_type" + t.integer "target_id" + t.string "title" + t.text "data" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "action" - t.integer "author_id" + t.integer "action" + t.integer "author_id" end add_index "events", ["action"], name: "index_events_on_action", using: :btree @@ -428,8 +428,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree create_table "forked_project_links", force: :cascade do |t| - t.integer "forked_to_project_id", null: false - t.integer "forked_from_project_id", null: false + t.integer "forked_to_project_id", null: false + t.integer "forked_from_project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -437,9 +437,9 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid" - t.string "provider" - t.integer "user_id" + t.string "extern_uid" + t.string "provider" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end @@ -447,37 +447,37 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issue_metrics", force: :cascade do |t| - t.integer "issue_id", null: false + t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" t.datetime "first_associated_with_milestone_at" t.datetime "first_added_to_board_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "issue_metrics", ["issue_id"], name: "index_issue_metrics", using: :btree create_table "issues", force: :cascade do |t| - t.string "title" - t.integer "assignee_id" - t.integer "author_id" - t.integer "project_id" + t.string "title" + t.integer "assignee_id" + t.integer "author_id" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name" - t.text "description" - t.integer "milestone_id" - t.string "state" - t.integer "iid" - t.integer "updated_by_id" - t.boolean "confidential", default: false + t.integer "position", default: 0 + t.string "branch_name" + t.text "description" + t.integer "milestone_id" + t.string "state" + t.integer "iid" + t.integer "updated_by_id" + t.boolean "confidential", default: false t.datetime "deleted_at" - t.date "due_date" - t.integer "moved_to_id" - t.integer "lock_version" - t.text "title_html" - t.text "description_html" + t.date "due_date" + t.integer "moved_to_id" + t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -493,23 +493,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "keys", force: :cascade do |t| - t.integer "user_id" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" - t.text "key" - t.string "title" - t.string "type" - t.string "fingerprint" - t.boolean "public", default: false, null: false + t.text "key" + t.string "title" + t.string "type" + t.string "fingerprint" + t.boolean "public", default: false, null: false end add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree create_table "label_links", force: :cascade do |t| - t.integer "label_id" - t.integer "target_id" - t.string "target_type" + t.integer "label_id" + t.integer "target_id" + t.string "target_type" t.datetime "created_at" t.datetime "updated_at" end @@ -518,15 +518,15 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title" - t.string "color" - t.integer "project_id" + t.string "title" + t.string "color" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false - t.string "description" - t.integer "priority" - t.text "description_html" + t.boolean "template", default: false + t.string "description" + t.integer "priority" + t.text "description_html" end add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree @@ -534,18 +534,18 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", null: false - t.integer "size", limit: 8, null: false + t.string "oid", null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file" + t.string "file" end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree create_table "lfs_objects_projects", force: :cascade do |t| - t.integer "lfs_object_id", null: false - t.integer "project_id", null: false + t.integer "lfs_object_id", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -553,12 +553,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "lists", force: :cascade do |t| - t.integer "board_id", null: false - t.integer "label_id" - t.integer "list_type", default: 1, null: false - t.integer "position" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "board_id", null: false + t.integer "label_id" + t.integer "list_type", default: 1, null: false + t.integer "position" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "lists", ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree @@ -566,20 +566,20 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "lists", ["label_id"], name: "index_lists_on_label_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", null: false - t.integer "user_id" - t.integer "notification_level", null: false - t.string "type" + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", null: false + t.integer "user_id" + t.integer "notification_level", null: false + t.string "type" t.datetime "created_at" t.datetime "updated_at" - t.integer "created_by_id" - t.string "invite_email" - t.string "invite_token" + t.integer "created_by_id" + t.string "invite_email" + t.string "invite_token" t.datetime "invite_accepted_at" t.datetime "requested_at" - t.date "expires_at" + t.date "expires_at" end add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree @@ -589,61 +589,61 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state" - t.text "st_commits" - t.text "st_diffs" - t.integer "merge_request_id", null: false + t.string "state" + t.text "st_commits" + t.text "st_diffs" + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "base_commit_sha" - t.string "real_size" - t.string "head_commit_sha" - t.string "start_commit_sha" + t.string "base_commit_sha" + t.string "real_size" + t.string "head_commit_sha" + t.string "start_commit_sha" end add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree create_table "merge_request_metrics", force: :cascade do |t| - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "latest_build_started_at" t.datetime "latest_build_finished_at" t.datetime "first_deployed_to_production_at" t.datetime "merged_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false - t.integer "author_id" - t.integer "assignee_id" - t.string "title" + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false + t.integer "author_id" + t.integer "assignee_id" + t.string "title" t.datetime "created_at" t.datetime "updated_at" - t.integer "milestone_id" - t.string "state" - t.string "merge_status" - t.integer "target_project_id", null: false - t.integer "iid" - t.text "description" - t.integer "position", default: 0 + t.integer "milestone_id" + t.string "state" + t.string "merge_status" + t.integer "target_project_id", null: false + t.integer "iid" + t.text "description" + t.integer "position", default: 0 t.datetime "locked_at" - t.integer "updated_by_id" - t.text "merge_error" - t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false - t.integer "merge_user_id" - t.string "merge_commit_sha" + t.integer "updated_by_id" + t.text "merge_error" + t.text "merge_params" + t.boolean "merge_when_build_succeeds", default: false, null: false + t.integer "merge_user_id" + t.string "merge_commit_sha" t.datetime "deleted_at" - t.string "in_progress_merge_commit_sha" - t.integer "lock_version" - t.text "title_html" - t.text "description_html" + t.string "in_progress_merge_commit_sha" + t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -660,26 +660,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "merge_requests_closing_issues", force: :cascade do |t| - t.integer "merge_request_id", null: false - t.integer "issue_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "merge_request_id", null: false + t.integer "issue_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "merge_requests_closing_issues", ["issue_id"], name: "index_merge_requests_closing_issues_on_issue_id", using: :btree add_index "merge_requests_closing_issues", ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false - t.text "description" - t.date "due_date" + t.string "title", null: false + t.integer "project_id", null: false + t.text "description" + t.date "due_date" t.datetime "created_at" t.datetime "updated_at" - t.string "state" - t.integer "iid" - t.text "title_html" - t.text "description_html" + t.string "state" + t.integer "iid" + t.text "title_html" + t.text "description_html" end add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -690,20 +690,20 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false - t.integer "owner_id" + t.string "name", null: false + t.string "path", null: false + t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false - t.boolean "request_access_enabled", default: true, null: false + t.string "type" + t.string "description", default: "", null: false + t.string "avatar" + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false + t.boolean "request_access_enabled", default: true, null: false t.datetime "deleted_at" - t.boolean "lfs_enabled" - t.text "description_html" + t.boolean "lfs_enabled" + t.text "description_html" end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree @@ -716,27 +716,27 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: :cascade do |t| - t.text "note" - t.string "noteable_type" - t.integer "author_id" + t.text "note" + t.string "noteable_type" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" - t.integer "noteable_id" - t.boolean "system", default: false, null: false - t.text "st_diff" - t.integer "updated_by_id" - t.string "type" - t.text "position" - t.text "original_position" + t.integer "project_id" + t.string "attachment" + t.string "line_code" + t.string "commit_id" + t.integer "noteable_id" + t.boolean "system", default: false, null: false + t.text "st_diff" + t.integer "updated_by_id" + t.string "type" + t.text "position" + t.text "original_position" t.datetime "resolved_at" - t.integer "resolved_by_id" - t.string "discussion_id" - t.string "original_discussion_id" - t.text "note_html" + t.integer "resolved_by_id" + t.string "discussion_id" + t.string "original_discussion_id" + t.text "note_html" end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -752,13 +752,13 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "notification_settings", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "source_id" - t.string "source_type" - t.integer "level", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "events" + t.integer "user_id", null: false + t.integer "source_id" + t.string "source_type" + t.integer "level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "events" end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree @@ -766,27 +766,27 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes" + t.string "scopes" end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree create_table "oauth_access_tokens", force: :cascade do |t| - t.integer "resource_owner_id" - t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" + t.integer "resource_owner_id" + t.integer "application_id" + t.string "token", null: false + t.string "refresh_token" + t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" + t.datetime "created_at", null: false + t.string "scopes" end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -794,40 +794,40 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "owner_id" - t.string "owner_type" + t.integer "owner_id" + t.string "owner_type" end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree create_table "personal_access_tokens", force: :cascade do |t| - t.integer "user_id", null: false - t.string "token", null: false - t.string "name", null: false - t.boolean "revoked", default: false + t.integer "user_id", null: false + t.string "token", null: false + t.string "name", null: false + t.boolean "revoked", default: false t.datetime "expires_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree create_table "project_features", force: :cascade do |t| - t.integer "project_id" - t.integer "merge_requests_access_level" - t.integer "issues_access_level" - t.integer "wiki_access_level" - t.integer "snippets_access_level" - t.integer "builds_access_level" + t.integer "project_id" + t.integer "merge_requests_access_level" + t.integer "issues_access_level" + t.integer "wiki_access_level" + t.integer "snippets_access_level" + t.integer "builds_access_level" t.datetime "created_at" t.datetime "updated_at" end @@ -835,60 +835,60 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree create_table "project_group_links", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "group_id", null: false + t.integer "project_id", null: false + t.integer "group_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "group_access", default: 30, null: false - t.date "expires_at" + t.integer "group_access", default: 30, null: false + t.date "expires_at" end create_table "project_import_data", force: :cascade do |t| t.integer "project_id" - t.text "data" - t.text "encrypted_credentials" - t.string "encrypted_credentials_iv" - t.string "encrypted_credentials_salt" + t.text "data" + t.text "encrypted_credentials" + t.string "encrypted_credentials_iv" + t.string "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" - t.text "description" + t.string "name" + t.string "path" + t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.integer "creator_id" - t.integer "namespace_id" + t.integer "creator_id" + t.integer "namespace_id" t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 - t.text "import_error" - t.integer "ci_id" - t.boolean "shared_runners_enabled", default: true, null: false - t.string "runners_token" - t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false - t.boolean "last_repository_check_failed" + t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar" + t.string "import_status" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" + t.integer "commit_count", default: 0 + t.text "import_error" + t.integer "ci_id" + t.boolean "shared_runners_enabled", default: true, null: false + t.string "runners_token" + t.string "build_coverage_regex" + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false + t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" - t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false - t.boolean "has_external_issue_tracker" - t.string "repository_storage", default: "default", null: false - t.boolean "request_access_enabled", default: true, null: false - t.boolean "has_external_wiki" - t.boolean "lfs_enabled" - t.text "description_html" + t.boolean "container_registry_enabled" + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "has_external_issue_tracker" + t.string "repository_storage", default: "default", null: false + t.boolean "request_access_enabled", default: true, null: false + t.boolean "has_external_wiki" + t.boolean "lfs_enabled" + t.text "description_html" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree @@ -907,26 +907,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branch_merge_access_levels", force: :cascade do |t| - t.integer "protected_branch_id", null: false - t.integer "access_level", default: 40, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "protected_branch_merge_access_levels", ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree create_table "protected_branch_push_access_levels", force: :cascade do |t| - t.integer "protected_branch_id", null: false - t.integer "access_level", default: 40, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "protected_branch_push_access_levels", ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false + t.integer "project_id", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -934,12 +934,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag" - t.text "description" - t.integer "project_id" + t.string "tag" + t.text "description" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.text "description_html" + t.text "description_html" end add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree @@ -948,54 +948,54 @@ ActiveRecord::Schema.define(version: 20161007133303) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type" + t.string "noteable_type" t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" - t.string "note_type" - t.text "position" + t.string "commit_id" + t.string "reply_key", null: false + t.string "line_code" + t.string "note_type" + t.text "position" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id" + t.string "type" + t.string "title" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false - t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false - t.boolean "wiki_page_events", default: true - t.boolean "pipeline_events", default: false, null: false - t.boolean "confidential_issues_events", default: true, null: false + t.boolean "active", default: false, null: false + t.text "properties" + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false + t.boolean "wiki_page_events", default: true + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: true, null: false end add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title" - t.text "content" - t.integer "author_id", null: false - t.integer "project_id" + t.string "title" + t.text "content" + t.integer "author_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name" - t.string "type" - t.integer "visibility_level", default: 0, null: false - t.text "title_html" - t.text "content_html" + t.string "file_name" + t.string "type" + t.integer "visibility_level", default: 0, null: false + t.text "title_html" + t.text "content_html" end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -1006,23 +1006,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree create_table "spam_logs", force: :cascade do |t| - t.integer "user_id" - t.string "source_ip" - t.string "user_agent" - t.boolean "via_api" - t.string "noteable_type" - t.string "title" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "submitted_as_ham", default: false, null: false + t.integer "user_id" + t.string "source_ip" + t.string "user_agent" + t.boolean "via_api" + t.string "noteable_type" + t.string "title" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "submitted_as_ham", default: false, null: false end create_table "subscriptions", force: :cascade do |t| - t.integer "user_id" - t.integer "subscribable_id" - t.string "subscribable_type" - t.boolean "subscribed" + t.integer "user_id" + t.integer "subscribable_id" + t.string "subscribable_type" + t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" end @@ -1030,12 +1030,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree create_table "taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context" + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context" t.datetime "created_at" end @@ -1043,24 +1043,24 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name" + t.string "name" t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "todos", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "project_id", null: false - t.integer "target_id" - t.string "target_type", null: false - t.integer "author_id" - t.integer "action", null: false - t.string "state", null: false + t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "target_id" + t.string "target_type", null: false + t.integer "author_id" + t.integer "action", null: false + t.string "state", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "note_id" - t.string "commit_id" + t.integer "note_id" + t.string "commit_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree @@ -1077,88 +1077,88 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree create_table "u2f_registrations", force: :cascade do |t| - t.text "certificate" - t.string "key_handle" - t.string "public_key" - t.integer "counter" - t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "name" + t.text "certificate" + t.string "key_handle" + t.string "public_key" + t.integer "counter" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" end add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree create_table "user_agent_details", force: :cascade do |t| - t.string "user_agent", null: false - t.string "ip_address", null: false - t.integer "subject_id", null: false - t.string "subject_type", null: false - t.boolean "submitted", default: false, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "user_agent", null: false + t.string "ip_address", null: false + t.integer "subject_id", null: false + t.string "subject_type", null: false + t.boolean "submitted", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false + t.string "username" + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state" + t.integer "color_scheme_id", default: 1, null: false t.datetime "password_expires_at" - t.integer "created_by_id" + t.integer "created_by_id" t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" + t.string "avatar" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false - t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 - t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false - t.string "unlock_token" + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location" + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.boolean "otp_required_for_login", default: false, null: false + t.text "otp_backup_codes" + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 + t.integer "consumed_timestep" + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false + t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false - t.string "organization" + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false + t.string "organization" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -1176,8 +1176,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} create_table "users_star_projects", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "user_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -1187,23 +1187,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree create_table "web_hooks", force: :cascade do |t| - t.string "url", limit: 2000 - t.integer "project_id" + t.string "url", limit: 2000 + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" - t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false - t.boolean "enable_ssl_verification", default: true - t.boolean "build_events", default: false, null: false - t.boolean "wiki_page_events", default: false, null: false - t.string "token" - t.boolean "pipeline_events", default: false, null: false - t.boolean "confidential_issues_events", default: false, null: false + t.string "type", default: "ProjectHook" + t.integer "service_id" + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: true + t.boolean "build_events", default: false, null: false + t.boolean "wiki_page_events", default: false, null: false + t.string "token" + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: false, null: false end add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree -- cgit v1.2.1 From 2e3bc85421ddfc814e81f75e3adb8a8c1d53093e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 15:21:11 +0200 Subject: Do not return from proc-closure in pipeline transition --- app/models/commit_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index aac5f7a5937..7b554be4f9a 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -86,7 +86,7 @@ class CommitStatus < ActiveRecord::Base end after_transition do |commit_status, transition| - return if transition.loopback? + next if transition.loopback? commit_status.run_after_commit do pipeline.try do |pipeline| -- cgit v1.2.1 From c143003bfbd5cda725451c38ff1eca8ba469409b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 13:53:18 +0300 Subject: Bump gitlab_git to 10.6.8 Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c5f1ce26daf..59ff78c8fae 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.7' +gem 'gitlab_git', '~> 10.6.8' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 2feec4c4eb5..edccf870821 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.6.7) + gitlab_git (10.6.8) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -863,7 +863,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.6.7) + gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) -- cgit v1.2.1 From ac4db38094f4a68a81b0a7570c5835f663c01cfd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 14:04:50 +0300 Subject: Use straight diff approach when compare merge request versions Signed-off-by: Dmitriy Zaporozhets --- app/models/compare.rb | 5 ++++- app/models/merge_request_diff.rb | 7 +++++-- app/services/compare_service.rb | 7 ++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/models/compare.rb b/app/models/compare.rb index 4856510f526..4b568a1d11c 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -11,9 +11,10 @@ class Compare end end - def initialize(compare, project) + def initialize(compare, project, straight = false) @compare = compare @project = project + @straight = straight end def commits @@ -36,6 +37,8 @@ class Compare alias_method :commit, :head_commit def base_commit + return start_commit if @straight + return @base_commit if defined?(@base_commit) @base_commit = if start_commit && head_commit diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 3f7e96186a1..0ea9e892be2 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -167,8 +167,11 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end - def compare_with(sha) - CompareService.new.execute(project, head_commit_sha, project, sha) + def compare_with(sha, straight = true) + # When compare merge request versions we want diff A..B instead of A...B + # so we handle cases when user squash and rebase commits in one of versions. + # For this reason we set straight to true by default. + CompareService.new.execute(project, head_commit_sha, project, sha, straight) end private diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 6d6075628af..6df3b958b8a 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch) + def execute(source_project, source_branch, target_project, target_branch, straight = false) source_commit = source_project.commit(source_branch) return unless source_commit @@ -23,9 +23,10 @@ class CompareService raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, - source_sha + source_sha, + straight ) - Compare.new(raw_compare, target_project) + Compare.new(raw_compare, target_project, straight) end end -- cgit v1.2.1 From 1bb64ec99476451d67463e3508b449aeea7e694f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:21:19 +0300 Subject: Add spec for compare service Signed-off-by: Dmitriy Zaporozhets --- spec/services/compare_service_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/services/compare_service_spec.rb diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb new file mode 100644 index 00000000000..d809f291b44 --- /dev/null +++ b/spec/services/compare_service_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe CompareService, services: true do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new } + + describe '#execute' do + context 'compare with base, like feature...fix' do + subject { service.execute(project, 'feature', project, 'fix', false) } + + it { expect(subject.diffs.size).to eq(1) } + end + + context 'straight compare, like feature..fix' do + subject { service.execute(project, 'feature', project, 'fix', true) } + + it { expect(subject.diffs.size).to eq(3) } + end + end +end -- cgit v1.2.1 From afe28b7be3a3a5a9db6e5a32465c0e3a8e9cb83d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:23:36 +0300 Subject: Add mr version improvement to changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..51870619fb4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -127,6 +127,9 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only + - Improve the way merge request versions are compared with each other + +v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From cdcc11d48fe6d04f6a1cfebc39bcea71d35b3e4a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:46:47 +0300 Subject: Improve tests for merge request diff model Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 2 +- spec/models/merge_request_diff_spec.rb | 40 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0ea9e892be2..6ad2201573d 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -169,7 +169,7 @@ class MergeRequestDiff < ActiveRecord::Base def compare_with(sha, straight = true) # When compare merge request versions we want diff A..B instead of A...B - # so we handle cases when user squash and rebase commits in one of versions. + # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. CompareService.new.execute(project, head_commit_sha, project, sha, straight) end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index f27de0948ee..69b8afd22a3 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -74,27 +74,37 @@ describe MergeRequestDiff, models: true do end end end + end - describe '#commits_sha' do - shared_examples 'returning all commits SHA' do - it 'returns all commits SHA' do - commits_sha = subject.commits_sha + describe '#commits_sha' do + shared_examples 'returning all commits SHA' do + it 'returns all commits SHA' do + commits_sha = subject.commits_sha - expect(commits_sha).to eq(subject.commits.map(&:sha)) - end + expect(commits_sha).to eq(subject.commits.map(&:sha)) end + end - context 'when commits were loaded' do - before do - subject.commits - end - - it_behaves_like 'returning all commits SHA' + context 'when commits were loaded' do + before do + subject.commits end - context 'when commits were not loaded' do - it_behaves_like 'returning all commits SHA' - end + it_behaves_like 'returning all commits SHA' + end + + context 'when commits were not loaded' do + it_behaves_like 'returning all commits SHA' + end + end + + describe '#compare_with' do + subject { create(:merge_request).merge_request_diff } + + it 'delegates compare to the service' do + expect(CompareService).to receive(:new).and_call_original + + subject.compare_with('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') end end end -- cgit v1.2.1 From 7421c08ace8a129b37c4942eb21a0b5cf1a73a53 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 18:02:06 +0300 Subject: Better tests for MergeRequestDiff#compare_with method Signed-off-by: Dmitriy Zaporozhets --- spec/models/merge_request_diff_spec.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 69b8afd22a3..e5007424041 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -99,12 +99,18 @@ describe MergeRequestDiff, models: true do end describe '#compare_with' do - subject { create(:merge_request).merge_request_diff } + subject { create(:merge_request, source_branch: 'fix').merge_request_diff } it 'delegates compare to the service' do expect(CompareService).to receive(:new).and_call_original - subject.compare_with('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') + subject.compare_with(nil) + end + + it 'uses git diff A..B approach by default' do + diffs = subject.compare_with('0b4bc9a49b562e85de7cc9e834518ea6828729b9').diffs + + expect(diffs.size).to eq(3) end end end -- cgit v1.2.1 From b48c4b2662e7db9d68052392fb34dd2b27d12cf5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 13:27:59 +0300 Subject: Refactor straight compare diff code Signed-off-by: Dmitriy Zaporozhets --- app/models/compare.rb | 22 ++++++++++++++++------ app/models/merge_request_diff.rb | 4 ++-- app/services/compare_service.rb | 4 ++-- spec/services/compare_service_spec.rb | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/models/compare.rb b/app/models/compare.rb index 4b568a1d11c..3a8bbcb1acd 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -11,7 +11,7 @@ class Compare end end - def initialize(compare, project, straight = false) + def initialize(compare, project, straight: false) @compare = compare @project = project @straight = straight @@ -37,8 +37,6 @@ class Compare alias_method :commit, :head_commit def base_commit - return start_commit if @straight - return @base_commit if defined?(@base_commit) @base_commit = if start_commit && head_commit @@ -48,6 +46,18 @@ class Compare end end + def start_commit_sha + start_commit.try(:sha) + end + + def base_commit_sha + base_commit.try(:sha) + end + + def head_commit_sha + commit.try(:sha) + end + def raw_diffs(*args) @compare.diffs(*args) end @@ -61,9 +71,9 @@ class Compare def diff_refs Gitlab::Diff::DiffRefs.new( - base_sha: base_commit.try(:sha), - start_sha: start_commit.try(:sha), - head_sha: commit.try(:sha) + base_sha: @straight ? start_commit_sha : base_commit_sha, + start_sha: start_commit_sha, + head_sha: head_commit_sha ) end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 6ad2201573d..b8a10b7968e 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -167,11 +167,11 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end - def compare_with(sha, straight = true) + def compare_with(sha, straight: true) # When compare merge request versions we want diff A..B instead of A...B # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. - CompareService.new.execute(project, head_commit_sha, project, sha, straight) + CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) end private diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 6df3b958b8a..5e8fafca98c 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, straight = false) + def execute(source_project, source_branch, target_project, target_branch, straight: false) source_commit = source_project.commit(source_branch) return unless source_commit @@ -27,6 +27,6 @@ class CompareService straight ) - Compare.new(raw_compare, target_project, straight) + Compare.new(raw_compare, target_project, straight: straight) end end diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index d809f291b44..3760f19aaa2 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -7,13 +7,13 @@ describe CompareService, services: true do describe '#execute' do context 'compare with base, like feature...fix' do - subject { service.execute(project, 'feature', project, 'fix', false) } + subject { service.execute(project, 'feature', project, 'fix', straight: false) } it { expect(subject.diffs.size).to eq(1) } end context 'straight compare, like feature..fix' do - subject { service.execute(project, 'feature', project, 'fix', true) } + subject { service.execute(project, 'feature', project, 'fix', straight: true) } it { expect(subject.diffs.size).to eq(3) } end -- cgit v1.2.1 From c65c800f8e466ffcfb58613fc775c4afa11f91e6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 15:09:34 +0300 Subject: Inform user when comparing 2 version with different base Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 11 ++++++----- app/views/projects/merge_requests/show/_versions.html.haml | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1006b3e62e8..c712d6e36b1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -401,8 +401,13 @@ padding: 16px; } + .content-block { + padding: $gl-padding-top $gl-padding; + } + .comments-disabled-notif { - padding: 10px 16px; + border-top: 1px solid $border-color; + .btn { margin-left: 5px; } @@ -413,10 +418,6 @@ margin: 0 7px; } - .comments-disabled-notif { - border-top: 1px solid $border-color; - } - .dropdown-title { color: $gl-text-color; } diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 988ac0feae1..c282e3868cc 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -64,6 +64,13 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + - if @start_version && @start_version.base_commit_sha != @merge_request_diff.base_commit_sha + .content-block + Selected versions have different base commits. + Changes will include + = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do + new commits + from #{@merge_request.target_branch} - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block = icon('info-circle') -- cgit v1.2.1 From d0fb1835158467ce58c59f489384b13ad0f0ff7c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 15:45:14 +0300 Subject: Fix CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 51870619fb4..445566db95f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -129,8 +129,6 @@ v 8.12.4 - Set GitLab project exported file permissions to owner only - Improve the way merge request versions are compared with each other -v 8.12.4 (unreleased) - v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From 45418734311561bb72aa5ab7a89d81bbd5705f7a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 14:51:56 +0200 Subject: Added documentation chapter for Git attributes As discussed in https://gitlab.com/gitlab-org/gitlab_git/issues/28 we'll need to clearly document the need for .gitattributes files being encoded using UTF-8. [ci skip] --- CHANGELOG | 1 + doc/README.md | 1 + doc/user/project/git_attributes.md | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 doc/user/project/git_attributes.md diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..894f4bc9af0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Clarify documentation for Runners API (Gennady Trafimenkov) - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 + - Added documentation for .gitattributes files - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/doc/README.md b/doc/README.md index 42ee44f83dc..7e3d9b00900 100644 --- a/doc/README.md +++ b/doc/README.md @@ -20,6 +20,7 @@ - [Webhooks](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. - [University](university/README.md) Learn Git and GitLab through videos and courses. +- [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file. ## Administrator documentation diff --git a/doc/user/project/git_attributes.md b/doc/user/project/git_attributes.md new file mode 100644 index 00000000000..21ef94e61f7 --- /dev/null +++ b/doc/user/project/git_attributes.md @@ -0,0 +1,22 @@ +# Git Attributes + +GitLab supports defining custom [Git attributes][gitattributes] such as what +files to treat as binary, and what language to use for syntax highlighting +diffs. + +To define these attributes, create a file called `.gitattributes` in the root +directory of your repository and push it to the default branch of your project. + +## Encoding Requirements + +The `.gitattributes` file _must_ be encoded in UTF-8 and _must not_ contain a +Byte Order Mark. If a different encoding is used, the file's contents will be +ignored. + +## Syntax Highlighting + +The `.gitattributes` file can be used to define which language to use when +syntax highlighting files and diffs. See ["Syntax +Highlighting"](highlighting.md) for more information. + +[gitattributes]: https://git-scm.com/docs/gitattributes -- cgit v1.2.1 From 3dd7ad5064afa49aa74a7d40ba02fff7e67846d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 17:42:32 +0300 Subject: Improve mr compare message when base is different Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 3 +-- app/helpers/merge_requests_helper.rb | 4 ++++ app/views/projects/merge_requests/show/_versions.html.haml | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c712d6e36b1..7cf69c56d15 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -402,12 +402,11 @@ } .content-block { + border-top: 1px solid $border-color; padding: $gl-padding-top $gl-padding; } .comments-disabled-notif { - border-top: 1px solid $border-color; - .btn { margin-left: 5px; } diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index b0a76765d97..249cb44e9d5 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -123,4 +123,8 @@ module MergeRequestsHelper def version_index(merge_request_diff) @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) end + + def different_base?(version1, version2) + version1 && version2 && version1.base_commit_sha != version2.base_commit_sha + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index c282e3868cc..eab48b78cb3 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -64,13 +64,16 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} - - if @start_version && @start_version.base_commit_sha != @merge_request_diff.base_commit_sha + - if different_base?(@start_version, @merge_request_diff) .content-block + = icon('info-circle') Selected versions have different base commits. Changes will include = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do new commits - from #{@merge_request.target_branch} + from + %code #{@merge_request.target_branch} + - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block = icon('info-circle') -- cgit v1.2.1 From dd3934f7a2fdcafd6607d893da15c9a195065d54 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 16:44:47 +0200 Subject: Update to gitlab-shell 3.6.6 --- CHANGELOG | 2 +- GITLAB_SHELL_VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 894f4bc9af0..edbd7454548 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,7 @@ v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Use gitlab-shell v3.6.2 (GIT TRACE logging) + - Use gitlab-shell v3.6.6 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 0f44168a4d5..4f2c1d15f6d 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.4 +3.6.6 -- cgit v1.2.1 From 8aa381c9c408baef295e721f4f11964de8db1aa0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 14:45:26 +0000 Subject: Revert "Merge branch 'tests-use-tmpfs' into 'master'" This reverts merge request !6730 --- .gitlab-ci.yml | 2 -- spec/spec_helper.rb | 5 ----- 2 files changed, 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb6f691058e..8645488335e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,8 +19,6 @@ variables: before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml - - mkdir -p tmp/tests - - mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc" - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f313bd4f249..b19f5824236 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -50,11 +50,6 @@ RSpec.configure do |config| example.run Rails.cache = caching_store end - - config.after(:each) do - FileUtils.rm_rf("tmp/tests/repositories") - FileUtils.mkdir_p("tmp/tests/repositories") - end end FactoryGirl::SyntaxRunner.class_eval do -- cgit v1.2.1 From b998479c81512b7c9a2cff28e7aabff3c4be0424 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 12 Oct 2016 13:32:48 +0200 Subject: API: Version information --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/version.md | 23 +++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/version.rb | 12 ++++++++++++ spec/requests/api/version_spec.rb | 27 +++++++++++++++++++++++++++ 6 files changed, 65 insertions(+) create mode 100644 doc/api/version.md create mode 100644 lib/api/version.rb create mode 100644 spec/requests/api/version_spec.rb diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..401d0d66de3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods + - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) - Revert "Label list shows all issues (opened or closed) with that label" diff --git a/doc/api/README.md b/doc/api/README.md index 9e907689c80..75b098f2eeb 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -46,6 +46,7 @@ following locations: - [Todos](todos.md) - [Users](users.md) - [Validate CI configuration](ci/lint.md) +- [Version](version.md) ### Internal CI API diff --git a/doc/api/version.md b/doc/api/version.md new file mode 100644 index 00000000000..287d17cf97f --- /dev/null +++ b/doc/api/version.md @@ -0,0 +1,23 @@ +# Version API + +>**Note:** This feature was introduced in GitLab 8.13 + +Retrieve version information for this GitLab instance. Responds `200 OK` for +authenticated users. + +``` +GET /version +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/version +``` + +Example response: + +```json +{ + "version": "8.13.0-pre", + "revision": "4e963fe" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..eb4e1fc504f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,6 +73,7 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::Version route :any, '*path' do error!('404 Not Found', 404) diff --git a/lib/api/version.rb b/lib/api/version.rb new file mode 100644 index 00000000000..9ba576bd828 --- /dev/null +++ b/lib/api/version.rb @@ -0,0 +1,12 @@ +module API + class Version < Grape::API + before { authenticate! } + + desc 'Get the version information of the GitLab instance.' do + detail 'This feature was introduced in GitLab 8.13.' + end + get '/version' do + { version: Gitlab::VERSION, revision: Gitlab::REVISION } + end + end +end diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb new file mode 100644 index 00000000000..54b69a0cae7 --- /dev/null +++ b/spec/requests/api/version_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + describe 'GET /version' do + context 'when unauthenticated' do + it 'returns authentication error' do + get api('/version') + + expect(response).to have_http_status(401) + end + end + + context 'when authenticated' do + let(:user) { create(:user) } + + it 'returns the version information' do + get api('/version', user) + + expect(response).to have_http_status(200) + expect(json_response['version']).to eq(Gitlab::VERSION) + expect(json_response['revision']).to eq(Gitlab::REVISION) + end + end + end +end -- cgit v1.2.1 From 2c5a95cbeb3ac4a19ad177d03d2703235e1f1c3c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 18:49:41 +0300 Subject: Rename users routing from /u/:username to /users/:username for consistency with other routes Signed-off-by: Dmitriy Zaporozhets --- config/routes/user.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/routes/user.rb b/config/routes/user.rb index 54bbcb18f6a..ae15b9d02a3 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -1,8 +1,5 @@ require 'constraints/user_url_constrainer' -get '/u/:username', to: redirect('/%{username}'), - constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Wed, 12 Oct 2016 17:43:20 +0100 Subject: Updated JS to work with issue index & show --- app/assets/javascripts/dispatcher.js | 1 + app/assets/javascripts/labels_select.js | 4 ++-- app/assets/javascripts/milestone_select.js | 2 +- app/assets/javascripts/users_select.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index adff73af79c..f3ef13ce20e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -21,6 +21,7 @@ shortcut_handler = null; switch (page) { case 'projects:boards:show': + case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); break; case 'projects:merge_requests:index': diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index e356872624a..f1e719937c7 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -292,7 +292,7 @@ return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { return; } if ($dropdown.hasClass('js-multiselect')) { @@ -334,7 +334,7 @@ page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; } diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 26cc6eb0e96..cee42633c79 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -110,7 +110,7 @@ e.preventDefault(); return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..4c071f334fd 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -160,7 +160,7 @@ selectedId = user.id; return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); -- cgit v1.2.1 From f32bc1f52662664048876b27e181a1cacda02580 Mon Sep 17 00:00:00 2001 From: Georg G Date: Wed, 12 Oct 2016 18:48:53 +0200 Subject: Add spec for Projects::GraphsController#languages --- .../controllers/projects/graphs_controller_spec.rb | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/controllers/projects/graphs_controller_spec.rb diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb new file mode 100644 index 00000000000..3f4535b7a24 --- /dev/null +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'ostruct' + +describe Projects::GraphsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.team << [user, :master] + end + + describe 'GET #languages' do + let(:linguist_repository) do + OpenStruct.new(languages: { + 'Ruby' => 1000, + 'CoffeeScript' => 350, + 'PowerShell' => 15 + }) + end + + let(:expected_values) do + ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" + [ + # colors from Linguist: + { value: 73.26, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 25.64, label: "CoffeeScript", color: "#244776", highlight: "#244776" }, + # colors from SHA256 fallback: + { value: 1.1, label: "PowerShell", color: ps_color, highlight: ps_color } + ] + end + + before do + allow(Linguist::Repository).to receive(:new).and_return(linguist_repository) + end + + it 'sets the correct colour according to language' do + get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + + expect(assigns(:languages)).to eq(expected_values) + end + end +end -- cgit v1.2.1 From da64f9e544bc5f05818c3f66b278e6e30584a8b4 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 12 Oct 2016 18:54:23 +0200 Subject: Update health_check gem to `~> 2.2.0` Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/22278 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 9d98a34a0d5..901e60ef9e2 100644 --- a/Gemfile +++ b/Gemfile @@ -343,7 +343,7 @@ gem 'oauth2', '~> 1.2.0' gem 'paranoia', '~> 2.0' # Health check -gem 'health_check', '~> 2.1.0' +gem 'health_check', '~> 2.2.0' # System information gem 'vmstat', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 69804c8c533..924e3a6781a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -336,7 +336,7 @@ GEM thor tilt hashie (3.4.4) - health_check (2.1.0) + health_check (2.2.1) rails (>= 4.0) hipchat (1.5.2) httparty @@ -875,7 +875,7 @@ DEPENDENCIES grape-entity (~> 0.4.2) haml_lint (~> 0.18.2) hamlit (~> 2.6.1) - health_check (~> 2.1.0) + health_check (~> 2.2.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) -- cgit v1.2.1 From abfb4f6e32ac13929678039e324307ee3ef2af43 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 19:00:38 +0200 Subject: Document Capybara errors from es6 in es5 file. --- doc/development/frontend.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index f879cd57e25..56c8516508e 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -223,3 +223,14 @@ For our currently-supported browsers, see our [requirements][requirements]. [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [scss-style-guide]: scss_styleguide.md [requirements]: ../install/requirements.md#supported-web-browsers + +## Common Errors + +### Rspec (Capybara/Poltergeist) chokes on general JavaScript errors + +If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being thrown in tests, but +can't reproduce them manually, you may have included `ES6`-style JavaScript in files that don't +have the `.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file you're +working in (`git mv .js> `). + + -- cgit v1.2.1 From 2461e10912b484c6942cd26f8fffd5c5557befa7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 13:44:33 +0200 Subject: Execute pipeline hooks asynchronously --- app/models/ci/pipeline.rb | 7 ++++++- app/workers/pipeline_hooks_worker.rb | 9 +++++++++ spec/workers/pipeline_hooks_worker_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 app/workers/pipeline_hooks_worker.rb create mode 100644 spec/workers/pipeline_hooks_worker_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..13a58a7a308 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,6 +3,7 @@ module Ci extend Ci::Model include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_commits' @@ -71,7 +72,11 @@ module Ci end after_transition do |pipeline, transition| - pipeline.execute_hooks unless transition.loopback? + next if transition.loopback? + + pipeline.run_after_commit do + PipelineHooksWorker.perform_async(id) + end end end diff --git a/app/workers/pipeline_hooks_worker.rb b/app/workers/pipeline_hooks_worker.rb new file mode 100644 index 00000000000..ab5e9f6daad --- /dev/null +++ b/app/workers/pipeline_hooks_worker.rb @@ -0,0 +1,9 @@ +class PipelineHooksWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:execute_hooks) + end +end diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb new file mode 100644 index 00000000000..035e329839f --- /dev/null +++ b/spec/workers/pipeline_hooks_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe PipelineHooksWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'executes hooks for the pipeline' do + expect_any_instance_of(Ci::Pipeline) + .to receive(:execute_hooks) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From ce4dad45557888066733bb20470bf56b87d4c8b7 Mon Sep 17 00:00:00 2001 From: Georg G Date: Thu, 13 Oct 2016 07:01:34 +0200 Subject: Use test double and matchers --- spec/controllers/projects/graphs_controller_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 3f4535b7a24..dd743356145 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require 'ostruct' describe Projects::GraphsController do let(:project) { create(:project) } @@ -12,7 +11,7 @@ describe Projects::GraphsController do describe 'GET #languages' do let(:linguist_repository) do - OpenStruct.new(languages: { + double(languages: { 'Ruby' => 1000, 'CoffeeScript' => 350, 'PowerShell' => 15 @@ -23,10 +22,10 @@ describe Projects::GraphsController do ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" [ # colors from Linguist: - { value: 73.26, label: "Ruby", color: "#701516", highlight: "#701516" }, - { value: 25.64, label: "CoffeeScript", color: "#244776", highlight: "#244776" }, + { label: "Ruby", color: "#701516", highlight: "#701516" }, + { label: "CoffeeScript", color: "#244776", highlight: "#244776" }, # colors from SHA256 fallback: - { value: 1.1, label: "PowerShell", color: ps_color, highlight: ps_color } + { label: "PowerShell", color: ps_color, highlight: ps_color } ] end @@ -37,7 +36,9 @@ describe Projects::GraphsController do it 'sets the correct colour according to language' do get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') - expect(assigns(:languages)).to eq(expected_values) + expected_values.each do |val| + expect(assigns(:languages)).to include(include(val)) + end end end end -- cgit v1.2.1 From 48015deda12cdff6b814785c97316e5b77c4a03e Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 11:16:29 +0500 Subject: Union examples in event spec fo speed up --- spec/models/event_spec.rb | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 06cac929bbf..733b79079ed 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -27,17 +27,17 @@ describe Event, models: true do end describe "Push event" do - before do - project = create(:project) - @user = project.owner - @event = create_event(project, @user) + let(:project) { create(:project) } + let(:user) { project.owner } + let(:event) { create_event(project, user) } + + it do + expect(event.push?).to be_truthy + expect(event.visible_to_user?).to be_truthy + expect(event.tag?).to be_falsey + expect(event.branch_name).to eq("master") + expect(event.author).to eq(user) end - - it { expect(@event.push?).to be_truthy } - it { expect(@event.visible_to_user?).to be_truthy } - it { expect(@event.tag?).to be_falsey } - it { expect(@event.branch_name).to eq("master") } - it { expect(@event.author).to eq(@user) } end describe '#note?' do @@ -59,8 +59,8 @@ describe Event, models: true do describe '#visible_to_user?' do let(:project) { create(:empty_project, :public) } let(:non_member) { create(:user) } - let(:member) { create(:user) } - let(:guest) { create(:user) } + let(:member) { create(:user) } + let(:guest) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:user) } let(:admin) { create(:admin) } @@ -79,23 +79,27 @@ describe Event, models: true do context 'for non confidential issues' do let(:target) { issue } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end end context 'for confidential issues' do let(:target) { confidential_issue } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end @@ -103,23 +107,27 @@ describe Event, models: true do context 'on non confidential issues' do let(:target) { note_on_issue } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end end context 'on confidential issues' do let(:target) { note_on_confidential_issue } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end @@ -129,22 +137,26 @@ describe Event, models: true do let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end context 'private project' do let(:project) { create(:project, :private) } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end end @@ -214,6 +226,6 @@ describe Event, models: true do action: Event::PUSHED, data: data, author_id: user.id - }.merge(attrs)) + }.merge!(attrs)) end end -- cgit v1.2.1 From 9521736ebc062ba6f693da389f895061ac7a8b3a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 13 Oct 2016 09:19:30 +0200 Subject: updated var name based on feedback --- app/models/cycle_analytics.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index 0f3fd995681..8ed4a56b19b 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -2,7 +2,7 @@ class CycleAnalytics include Gitlab::Database::Median include Gitlab::Database::DateTime - DEPLOYED_CHECK_METRICS = %i[production staging] + DEPLOYMENT_METRIC_STAGES = %i[production staging] def initialize(project, from:) @project = project @@ -93,7 +93,7 @@ class CycleAnalytics join(MergeRequest::Metrics.arel_table). on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) - if DEPLOYED_CHECK_METRICS.include?(name) + if DEPLOYMENT_METRIC_STAGES.include?(name) # Limit to merge requests that have been deployed to production after `@from` query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) end -- cgit v1.2.1 From 6c88a9dfbbee692e872d6f237cb4ca325ebc4a26 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 09:30:18 +0200 Subject: Add more info on the new CI permissions model [ci skip] --- doc/user/project/new_ci_build_permissions_model.md | 54 ++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 5253825d507..19385a99d64 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -87,20 +87,6 @@ your Runners in the most possible secure way, by avoiding the following: By using an insecure GitLab Runner configuration, you allow the rogue developers to steal the tokens of other builds. -## Debugging problems - -With the new permission model in place, there may be times that your build will -fail. This is most likely because your project tries to access other project's -sources, and you don't have the appropriate permissions. In the build log look -for information about 403 or forbidden access messages - -As an Administrator, you can verify that the user is a member of the group or -project they're trying to have access to, and you can impersonate the user to -retry the failing build in order to verify that everything is correct. - -You need to make sure that your installation has HTTPS cloning enabled. -HTTPS support is required by GitLab CI to clone all sources. - ## Build triggers [Build triggers][triggers] do not support the new permission model. @@ -152,17 +138,46 @@ with GitLab 8.12. ## Making use of the new CI build permissions model -With the new build permission model, there is now an easy way to access all +With the new build permissions model, there is now an easy way to access all dependent source code in a project. That way, we can: 1. Access a project's Git submodules 1. Access private container images 1. Access project's and submodule LFS objects -Let's see how that works with Git submodules and private Docker images hosted on +Below you can see the prerequisites needed to make use of the new permissions +model and how that works with Git submodules and private Docker images hosted on the container registry. -## Git submodules +### Prerequisites to use the new permissions model + +With the new permissions model in place, there may be times that your build will +fail. This is most likely because your project tries to access other project's +sources, and you don't have the appropriate permissions. In the build log look +for information about 403 or forbidden access messages. + +In short here's what you need to do should you encounter any issues. + +As an administrator: + +- **500 errors**: You will need to update [GitLab Workhorse][workhorse] to at + least 0.8.2. This is done automatically for Omnibus installations, you need to + check manually for installations from source. +- **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, + Apache, etc.). It might be a good idea to let GitLab use the internal NGINX + web server and not disable it completely. See [this comment][comment] for an + example. +- **403 errors**: You need to make sure that your installation has [HTTP(S) + cloning enabled][https]. HTTP(S) support is now a **requirement** by GitLab CI + to clone all sources. + +As a user: + +- Make sure you are a member of the group or project you're trying to have + access to. As an Administrator, you can verify that by impersonating the user + and retry the failing build in order to verify that everything is correct. + +### Git submodules > It often happens that while working on one project, you need to use another @@ -286,7 +301,10 @@ test: - docker run $CI_REGISTRY/group/other-project:latest ``` -[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules [build permissions]: ../permissions.md#builds-permissions +[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302 [ext]: ../permissions.md#external-users +[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md +[workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -- cgit v1.2.1 From 3d6f18cec57fc6c7776bd05558e0d03dd3b141e1 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 12 Oct 2016 20:38:33 +0200 Subject: GrapeDSL for variables --- lib/api/variables.rb | 89 +++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index f6495071a11..b9fb3c21dbb 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -4,27 +4,29 @@ module API before { authenticate! } before { authorize! :admin_build, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects do - # Get project variables - # - # Parameters: - # id (required) - The ID of a project - # page (optional) - The page number for pagination - # per_page (optional) - The value of items per page to show - # Example Request: - # GET /projects/:id/variables + desc 'Get project variables' do + success Entities::Variable + end + params do + optional :page, type: Integer, desc: 'The page number for pagination' + optional :per_page, type: Integer, desc: 'The value of items per page to show' + end get ':id/variables' do variables = user_project.variables present paginate(variables), with: Entities::Variable end - # Get specific variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The `key` of variable - # Example Request: - # GET /projects/:id/variables/:key + desc 'Get a specific variable from a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end get ':id/variables/:key' do key = params[:key] variable = user_project.variables.find_by(key: key.to_s) @@ -34,18 +36,15 @@ module API present variable, with: Entities::Variable end - # Create a new variable in project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The key of variable - # value (required) - The value of variable - # Example Request: - # POST /projects/:id/variables + desc 'Create a new variable in a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + requires :value, type: String, desc: 'The value of the variable' + end post ':id/variables' do - required_attributes! [:key, :value] - - variable = user_project.variables.create(key: params[:key], value: params[:value]) + variable = user_project.variables.create(declared(params, include_parent_namespaces: false).to_h) if variable.valid? present variable, with: Entities::Variable @@ -54,41 +53,37 @@ module API end end - # Update existing variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (optional) - The `key` of variable - # value (optional) - New value for `value` field of variable - # Example Request: - # PUT /projects/:id/variables/:key + desc 'Update an existing variable from a project' do + success Entities::Variable + end + params do + optional :key, type: String, desc: 'The key of the variable' + optional :value, type: String, desc: 'The value of the variable' + end put ':id/variables/:key' do - variable = user_project.variables.find_by(key: params[:key].to_s) + variable = user_project.variables.find_by(key: params[:key]) return not_found!('Variable') unless variable - attrs = attributes_for_keys [:value] - if variable.update(attrs) + if variable.update(value: params[:value]) present variable, with: Entities::Variable else render_validation_error!(variable) end end - # Delete existing variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The ID of a variable - # Example Request: - # DELETE /projects/:id/variables/:key + desc 'Delete an existing variable from a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end delete ':id/variables/:key' do - variable = user_project.variables.find_by(key: params[:key].to_s) + variable = user_project.variables.find_by(key: params[:key]) return not_found!('Variable') unless variable - variable.destroy - present variable, with: Entities::Variable + present variable.destroy, with: Entities::Variable end end end -- cgit v1.2.1 From cfd0d66c8308c0f259e39322193c8ddb34ec28f9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 10:04:58 +0200 Subject: Reassign secret token when regenerating one --- lib/gitlab/backend/shell.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index d0060fbaca1..9cec71a3222 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -47,8 +47,8 @@ module Gitlab unless File.size?(secret_file) # Generate a new token of 16 random hexadecimal characters and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) + @secret_token = SecureRandom.hex(16) + File.write(secret_file, @secret_token) end link_path = File.join(shell_path, '.gitlab_shell_secret') -- cgit v1.2.1 From 3939d4dfbfda5f7bb5133e12573c80f600ce451f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 09:58:06 +0100 Subject: Fix secret names in HA docs --- doc/administration/high_availability/gitlab.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index 8a881ce8863..137fed35a73 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -101,9 +101,9 @@ need some additional configuration. ```ruby gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860' - gitlab_rails['secret_token'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa' - gitlab_ci['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d' - gitlab_ci['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964' + gitlab_rails['otp_key_base'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa' + gitlab_rails['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d' + gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964' ``` 1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations -- cgit v1.2.1 From 96ee975805dd9bbb20bd26edf2dcc7bfdc858c78 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 10:25:38 +0100 Subject: Moved how we remove event listeners --- app/assets/javascripts/members.js.es6 | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 5dc6990bdb6..a0cd20f21e8 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -3,20 +3,13 @@ class Members { constructor() { - this.removeListeners(); this.addListeners(); } - removeListeners() { - $('.project_member, .group_member').off('ajax:success'); - $('.js-member-update-control').off('change'); - $('.js-edit-member-form').off('ajax:success'); - } - addListeners() { - $('.project_member, .group_member').on('ajax:success', this.removeRow); - $('.js-member-update-control').on('change', this.formSubmit); - $('.js-edit-member-form').on('ajax:success', this.formSuccess); + $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); + $('.js-member-update-control').off('change').on('change', this.formSubmit); + $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); } removeRow(e) { -- cgit v1.2.1 From 3c476ee62512a0642ca3e5e6b228f9abdd867a34 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 11 Oct 2016 16:52:49 +0100 Subject: Maintain "force_remove_source_branch" options on Merge Request unless specified --- CHANGELOG | 1 + app/services/merge_requests/update_service.rb | 5 ++++- spec/services/merge_requests/update_service_spec.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e026b4ec94..279501518c1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.13.0 (unreleased) - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references + - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - Simplify Mentionable concern instance methods - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 9dbec49d163..a37cc3fdf21 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -15,7 +15,10 @@ module MergeRequests params.except!(:target_branch, :force_remove_source_branch) end - merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) + if params[:force_remove_source_branch].present? + merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) + end + handle_wip_event(merge_request) update(merge_request) end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index fd5f94047c2..2433a7dad06 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -105,6 +105,18 @@ describe MergeRequests::UpdateService, services: true do expect(note).not_to be_nil expect(note.note).to eq 'Target branch changed from `master` to `target`' end + + context 'when not including source branch removal options' do + before do + opts.delete(:force_remove_source_branch) + end + + it 'maintains the original options' do + update_merge_request(opts) + + expect(@merge_request.merge_params["force_remove_source_branch"]).to eq("1") + end + end end context 'todos' do -- cgit v1.2.1 From 9ec7aeac2362151e15e59531f347f2d7924437f8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 11:18:56 +0100 Subject: Tweaked position of badge in panel headings Various UI tweaks --- app/assets/stylesheets/framework/panels.scss | 5 +++++ app/assets/stylesheets/framework/selects.scss | 2 +- app/assets/stylesheets/pages/members.scss | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index c6f30e144fd..5ba0486177f 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -13,6 +13,11 @@ .dropdown-menu-toggle { line-height: 20px; } + + .badge { + margin-top: -2px; + margin-left: 5px; + } } .panel-body { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index b309e2ad9f4..58f9db0fb21 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -94,7 +94,7 @@ } .select2-search-choice { - margin: 8px 0 0 8px; + margin: 5px 0 0 8px; box-shadow: none; border-color: $input-border; color: $gl-text-color; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index ff4fd751f26..756efa9c7fa 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -9,6 +9,10 @@ float: left; width: 50%; } + + strong { + font-weight: 600; + } } .controls { -- cgit v1.2.1 From fafc5a1777484ba6b1b12c5e88f3fc783fb4d839 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 12:45:16 +0200 Subject: Perform CI build hooks asynchronously using worker --- app/models/ci/build.rb | 5 +++-- app/workers/build_hooks_worker.rb | 9 +++++++++ spec/workers/build_hooks_worker_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 app/workers/build_hooks_worker.rb create mode 100644 spec/workers/build_hooks_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5dbf66173de..b0d13c1bf06 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,6 +1,7 @@ module Ci class Build < CommitStatus include TokenAuthenticatable + include AfterCommitQueue belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' @@ -75,12 +76,12 @@ module Ci state_machine :status do after_transition pending: :running do |build| - build.execute_hooks + build.run_after_commit { BuildHooksWorker.perform_async(id) } end after_transition any => [:success, :failed, :canceled] do |build| build.update_coverage - build.execute_hooks + build.run_after_commit { BuildHooksWorker.perform_async(id) } end after_transition any => [:success] do |build| diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb new file mode 100644 index 00000000000..e22ececb3fd --- /dev/null +++ b/app/workers/build_hooks_worker.rb @@ -0,0 +1,9 @@ +class BuildHooksWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id) + .try(:execute_hooks) + end +end diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb new file mode 100644 index 00000000000..97654a93f5c --- /dev/null +++ b/spec/workers/build_hooks_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe BuildHooksWorker do + describe '#perform' do + context 'when build exists' do + let!(:build) { create(:ci_build) } + + it 'calls build hooks' do + expect_any_instance_of(Ci::Build) + .to receive(:execute_hooks) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 2273b5d6d6f64a14d41bc8d25287d615502d2622 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 12:52:51 +0200 Subject: Sort API mounts --- lib/api/api.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..a6f32a9fc04 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -31,11 +31,12 @@ module API # Keep in alphabetical order mount ::API::AccessRequests mount ::API::AwardEmoji + mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages mount ::API::Builds - mount ::API::CommitStatuses mount ::API::Commits + mount ::API::CommitStatuses mount ::API::DeployKeys mount ::API::Deployments mount ::API::Environments @@ -43,22 +44,21 @@ module API mount ::API::Groups mount ::API::Internal mount ::API::Issues - mount ::API::Boards mount ::API::Keys mount ::API::Labels mount ::API::LicenseTemplates mount ::API::Lint mount ::API::Members - mount ::API::MergeRequests mount ::API::MergeRequestDiffs + mount ::API::MergeRequests mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings mount ::API::Pipelines mount ::API::ProjectHooks - mount ::API::ProjectSnippets mount ::API::Projects + mount ::API::ProjectSnippets mount ::API::Repositories mount ::API::Runners mount ::API::Services -- cgit v1.2.1 From 204fdcb1abb9c76b2d4bd6260c6e5ce91529aeb8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 12:58:25 +0200 Subject: Add build success worker that runs asynchronously --- app/models/ci/build.rb | 21 +++++++++------------ app/workers/build_success_worker.rb | 27 +++++++++++++++++++++++++++ spec/workers/build_success_worker_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 app/workers/build_success_worker.rb create mode 100644 spec/workers/build_success_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b0d13c1bf06..ff70f3deb52 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -76,25 +76,22 @@ module Ci state_machine :status do after_transition pending: :running do |build| - build.run_after_commit { BuildHooksWorker.perform_async(id) } + build.run_after_commit do + BuildHooksWorker.perform_async(id) + end end after_transition any => [:success, :failed, :canceled] do |build| build.update_coverage - build.run_after_commit { BuildHooksWorker.perform_async(id) } + + build.run_after_commit do + BuildHooksWorker.perform_async(id) + end end after_transition any => [:success] do |build| - if build.environment.present? - service = CreateDeploymentService.new( - build.project, build.user, - environment: build.environment, - sha: build.sha, - ref: build.ref, - tag: build.tag, - options: build.options.to_h[:environment], - variables: build.variables) - service.execute(build) + build.run_after_commit do + BuildSuccessWorker.perform_async(id) end end end diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb new file mode 100644 index 00000000000..d6db7642d82 --- /dev/null +++ b/app/workers/build_success_worker.rb @@ -0,0 +1,27 @@ +class BuildSuccessWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + perform_deloyment(build) + end + end + + private + + def perform_deloyment(build) + return if build.environment.blank? + + service = CreateDeploymentService.new( + build.project, build.user, + environment: build.environment, + sha: build.sha, + ref: build.ref, + tag: build.tag, + options: build.options.to_h[:environment], + variables: build.variables) + + service.execute(build) + end +end diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb new file mode 100644 index 00000000000..c42bcd45d50 --- /dev/null +++ b/spec/workers/build_success_worker_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BuildSuccessWorker do + describe '#perform' do + context 'when build exists' do + context 'when build belogs to the environment' do + let!(:build) { create(:ci_build, environment: 'production') } + + it 'executes deployment service' do + expect_any_instance_of(CreateDeploymentService) + .to receive(:execute) + + described_class.new.perform(build.id) + end + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 85324ff8d36165709d5c98569ca0745b32ba9095 Mon Sep 17 00:00:00 2001 From: Georg G Date: Thu, 13 Oct 2016 13:08:19 +0200 Subject: Fix indentation and change inner matcher --- spec/controllers/projects/graphs_controller_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index dd743356145..74e6603b0cb 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -12,10 +12,10 @@ describe Projects::GraphsController do describe 'GET #languages' do let(:linguist_repository) do double(languages: { - 'Ruby' => 1000, - 'CoffeeScript' => 350, - 'PowerShell' => 15 - }) + 'Ruby' => 1000, + 'CoffeeScript' => 350, + 'PowerShell' => 15 + }) end let(:expected_values) do @@ -37,7 +37,7 @@ describe Projects::GraphsController do get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') expected_values.each do |val| - expect(assigns(:languages)).to include(include(val)) + expect(assigns(:languages)).to include(a_hash_including(val)) end end end -- cgit v1.2.1 From bc4d732af126277b3e85aedce795158bd389fbea Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 14:11:26 +0300 Subject: Update users routing spec Signed-off-by: Dmitriy Zaporozhets --- spec/features/users_spec.rb | 12 ++++++++++++ spec/routing/routing_spec.rb | 12 ++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 6498b7317b4..c78c84dc5d0 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -49,6 +49,18 @@ feature 'Users', feature: true do expect(current_path).to eq user_path(user) expect(page).to have_text(user.name) end + + scenario '/u/user1/groups redirects to user groups page' do + visit '/u/user1/groups' + + expect(current_path).to eq user_groups_path(user) + end + + scenario '/u/user1/projects redirects to user projects page' do + visit '/u/user1/projects' + + expect(current_path).to eq user_projects_path(user) + end end def errors_on_page(page) diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0ee1c811dfb..f848d96c729 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -15,27 +15,27 @@ describe UsersController, "routing" do end it "to #groups" do - expect(get("/u/User/groups")).to route_to('users#groups', username: 'User') + expect(get("/users/User/groups")).to route_to('users#groups', username: 'User') end it "to #projects" do - expect(get("/u/User/projects")).to route_to('users#projects', username: 'User') + expect(get("/users/User/projects")).to route_to('users#projects', username: 'User') end it "to #contributed" do - expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User') + expect(get("/users/User/contributed")).to route_to('users#contributed', username: 'User') end it "to #snippets" do - expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User') + expect(get("/users/User/snippets")).to route_to('users#snippets', username: 'User') end it "to #calendar" do - expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User') + expect(get("/users/User/calendar")).to route_to('users#calendar', username: 'User') end it "to #calendar_activities" do - expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') + expect(get("/users/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') end end -- cgit v1.2.1 From ae95118a4ff3242a210d93e57370f6ef400db886 Mon Sep 17 00:00:00 2001 From: Johan H Date: Tue, 11 Oct 2016 16:27:09 +0200 Subject: Convert UTF-8 Emoji to Gitlab emoji --- CHANGELOG | 1 + lib/banzai/filter/emoji_filter.rb | 39 ++++++++++++++++------------- lib/gitlab/emoji.rb | 7 ++++-- spec/lib/banzai/filter/emoji_filter_spec.rb | 16 ++++++++++++ 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e193132ac2..16b05d1d972 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.13.0 (unreleased) - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) + - Unicode emoji are now converted to images - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 23ae6dfc79a..a8c1ca0c60a 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -14,15 +14,16 @@ module Banzai search_text_nodes(doc).each do |node| content = node.to_html next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) - if content.include?(':') || node.text.match(emoji_unicode_pattern) - html = emoji_name_image_filter(content) - html = emoji_unicode_image_filter(html) - next if html == content - node.replace(html) - end - end + next unless content.include?(':') || node.text.match(emoji_unicode_pattern) + + html = emoji_name_image_filter(content) + html = emoji_unicode_image_filter(html) + next if html == content + + node.replace(html) + end doc end @@ -34,31 +35,35 @@ module Banzai def emoji_name_image_filter(text) text.gsub(emoji_pattern) do |match| name = $1 - ":#{name}:" + emoji_image_tag(name, emoji_url(name)) end end - - # Replace unicode emojis with corresponding images if they exist. + # Replace unicode emoji with corresponding images if they exist. # - # text - String text to replace unicode emojis in. + # text - String text to replace unicode emoji in. # - # Returns a String with unicode emojis replaced with images. - + # Returns a String with unicode emoji replaced with images. def emoji_unicode_image_filter(text) text.gsub(emoji_unicode_pattern) do |moji| - ":#{Gitlab::Emoji.emojis_by_moji[moji][" + emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji)) end end + + def emoji_image_tag(emoji_name, emoji_url) + ":#{emoji_name}:" + end + # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ end + # Build a regexp that matches all valid unicode emojis names. def self.emoji_unicode_pattern @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/ - end + private def emoji_url(name) @@ -80,13 +85,10 @@ module Banzai emoji_unicode_path = emoji_unicode_filename(moji) if context[:asset_host] - # Asset host is specified. url_to_image(emoji_unicode_path) elsif context[:asset_root] - # Gitlab url is specified File.join(context[:asset_root], url_to_image(emoji_unicode_path)) else - # All other cases url_to_image(emoji_unicode_path) end end @@ -102,6 +104,7 @@ module Banzai def emoji_filename(name) "#{Gitlab::Emoji.emoji_filename(name)}.png" end + def emoji_unicode_pattern self.class.emoji_unicode_pattern end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index 803b0c2d057..bbbca8acc40 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -9,16 +9,19 @@ module Gitlab def emojis_by_moji Gemojione.index.instance_variable_get(:@emoji_by_moji) end + def emojis_unicodes - emojis_by_moji.keys.sort + emojis_by_moji.keys end + def emojis_names - emojis.keys.sort + emojis.keys end def emoji_filename(name) emojis[name]["unicode"] end + def emoji_unicode_filename(moji) emojis_by_moji[moji]["unicode"] end diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index 475160bb5ec..c8e62f528df 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -16,10 +16,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do doc = filter('

:heart:

') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end + it 'replaces supported unicode emoji' do doc = filter('

❤️

') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end + it 'ignores unsupported emoji' do exp = act = '

:foo:

' doc = filter(act) @@ -162,4 +164,18 @@ describe Banzai::Filter::EmojiFilter, lib: true do doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') end + + it 'uses a custom asset_root context' do + root = Gitlab.config.gitlab.url + 'gitlab/root' + + doc = filter("'🎱'", asset_root: root) + expect(doc.css('img').first.attr('src')).to start_with(root) + end + + it 'uses a custom asset_host context' do + ActionController::Base.asset_host = 'https://cdn.example.com' + + doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?') + expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') + end end -- cgit v1.2.1 From 03a8ed971154cb218b82e8be53943612de94999f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 13:23:23 +0200 Subject: Use trasaction to process build deployment --- app/services/create_deployment_service.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 799ad3e1bd0..c6dc2148c11 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,25 +2,29 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) - environment = find_or_create_environment + ActiveRecord::Base.transaction do + @deployable = deployable + @environment = prepare_environment - deployment = project.deployments.create( - environment: environment, + deploy.tap do |deployment| + deployment.update_merge_request_metrics! + end + end + end + + private + + def deploy + project.deployments.create( + environment: @environment, ref: params[:ref], tag: params[:tag], sha: params[:sha], user: current_user, - deployable: deployable - ) - - deployment.update_merge_request_metrics! - - deployment + deployable: @deployable) end - private - - def find_or_create_environment + def prepare_environment project.environments.find_or_create_by(name: expanded_name) do |environment| environment.external_url = expanded_url end -- cgit v1.2.1 From cb7872c3a0c789f9e906492098bb7d643f135e52 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 14:24:09 +0300 Subject: Remove /u/ prefix from user pages in documentation Signed-off-by: Dmitriy Zaporozhets --- doc/api/award_emoji.md | 18 +++++++++--------- doc/api/builds.md | 8 ++++---- doc/api/commits.md | 8 ++++---- doc/api/deployments.md | 12 ++++++------ doc/api/issues.md | 38 +++++++++++++++++++------------------- doc/api/keys.md | 2 +- doc/api/merge_requests.md | 18 +++++++++--------- doc/api/notes.md | 6 +++--- doc/api/pipelines.md | 10 +++++----- doc/api/projects.md | 10 +++++----- doc/api/todos.md | 18 +++++++++--------- doc/api/users.md | 24 ++++++++++++------------ doc/development/performance.md | 2 +- 13 files changed, 87 insertions(+), 87 deletions(-) diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md index c464e3f3f71..06111f4ab67 100644 --- a/doc/api/award_emoji.md +++ b/doc/api/award_emoji.md @@ -43,7 +43,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-15T10:09:34.206Z", "updated_at": "2016-06-15T10:09:34.206Z", @@ -59,7 +59,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.177Z", "updated_at": "2016-06-15T10:09:34.177Z", @@ -103,7 +103,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.177Z", "updated_at": "2016-06-15T10:09:34.177Z", @@ -146,7 +146,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T17:47:29.266Z", "updated_at": "2016-06-17T17:47:29.266Z", @@ -190,7 +190,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T17:47:29.266Z", "updated_at": "2016-06-17T17:47:29.266Z", @@ -238,7 +238,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.197Z", "updated_at": "2016-06-15T10:09:34.197Z", @@ -279,7 +279,7 @@ Example Response: "id": 26, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7e65550957227bd38fe2d7fbc6fd2f7b?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/user4" + "web_url": "http://gitlab.example.com/user4" }, "created_at": "2016-06-15T10:09:34.197Z", "updated_at": "2016-06-15T10:09:34.197Z", @@ -319,7 +319,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T19:59:55.888Z", "updated_at": "2016-06-17T19:59:55.888Z", @@ -362,7 +362,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://gitlab.example.com/u/root" + "web_url": "http://gitlab.example.com/root" }, "created_at": "2016-06-17T19:59:55.888Z", "updated_at": "2016-06-17T19:59:55.888Z", diff --git a/doc/api/builds.md b/doc/api/builds.md index e8a9e4743d3..e40f198696d 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -64,7 +64,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } }, @@ -108,7 +108,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } } @@ -212,7 +212,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } } @@ -279,7 +279,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://gitlab.dev/u/root", + "web_url": "http://gitlab.dev/root", "website_url": "" } } diff --git a/doc/api/commits.md b/doc/api/commits.md index 3e20beefb8a..6e0882a94de 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -288,7 +288,7 @@ Example response: ```json { "author" : { - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", "username" : "thedude", "state" : "active", @@ -343,7 +343,7 @@ Example response: "author" : { "username" : "thedude", "state" : "active", - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", "id" : 28, "name" : "Jeff Lebowski" @@ -370,7 +370,7 @@ Example response: "id" : 28, "name" : "Jeff Lebowski", "username" : "thedude", - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "state" : "active", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png" }, @@ -408,7 +408,7 @@ Example response: ```json { "author" : { - "web_url" : "https://gitlab.example.com/u/thedude", + "web_url" : "https://gitlab.example.com/thedude", "name" : "Jeff Lebowski", "avatar_url" : "https://gitlab.example.com/uploads/user/avatar/28/The-Big-Lebowski-400-400.png", "username" : "thedude", diff --git a/doc/api/deployments.md b/doc/api/deployments.md index 417962de82d..3d95c4cde60 100644 --- a/doc/api/deployments.md +++ b/doc/api/deployments.md @@ -56,7 +56,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://localhost:3000/u/root", + "web_url": "http://localhost:3000/root", "website_url": "" } }, @@ -75,7 +75,7 @@ Example of response "name": "Administrator", "state": "active", "username": "root", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } }, { @@ -114,7 +114,7 @@ Example of response "state": "active", "twitter": "", "username": "root", - "web_url": "http://localhost:3000/u/root", + "web_url": "http://localhost:3000/root", "website_url": "" } }, @@ -133,7 +133,7 @@ Example of response "name": "Administrator", "state": "active", "username": "root", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } } ] @@ -169,7 +169,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "environment": { "id": 9, @@ -193,7 +193,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root", + "web_url": "http://localhost:3000/root", "created_at": "2016-08-11T07:09:20.351Z", "is_admin": true, "bio": null, diff --git a/doc/api/issues.md b/doc/api/issues.md index eed0d2fce51..134263d27b4 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -46,7 +46,7 @@ Example response: "author" : { "state" : "active", "id" : 18, - "web_url" : "https://gitlab.example.com/u/eileen.lowe", + "web_url" : "https://gitlab.example.com/eileen.lowe", "name" : "Alexandra Bashirian", "avatar_url" : null, "username" : "eileen.lowe" @@ -67,7 +67,7 @@ Example response: "state" : "active", "id" : 1, "name" : "Administrator", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root" }, @@ -134,7 +134,7 @@ Example response: }, "author" : { "state" : "active", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root", "id" : 1, @@ -145,7 +145,7 @@ Example response: "iid" : 1, "assignee" : { "avatar_url" : null, - "web_url" : "https://gitlab.example.com/u/lennie", + "web_url" : "https://gitlab.example.com/lennie", "state" : "active", "username" : "lennie", "id" : 9, @@ -215,7 +215,7 @@ Example response: }, "author" : { "state" : "active", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root", "id" : 1, @@ -226,7 +226,7 @@ Example response: "iid" : 1, "assignee" : { "avatar_url" : null, - "web_url" : "https://gitlab.example.com/u/lennie", + "web_url" : "https://gitlab.example.com/lennie", "state" : "active", "username" : "lennie", "id" : 9, @@ -281,7 +281,7 @@ Example response: }, "author" : { "state" : "active", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root", "id" : 1, @@ -292,7 +292,7 @@ Example response: "iid" : 1, "assignee" : { "avatar_url" : null, - "web_url" : "https://gitlab.example.com/u/lennie", + "web_url" : "https://gitlab.example.com/lennie", "state" : "active", "username" : "lennie", "id" : 9, @@ -357,7 +357,7 @@ Example response: "name" : "Alexandra Bashirian", "avatar_url" : null, "state" : "active", - "web_url" : "https://gitlab.example.com/u/eileen.lowe", + "web_url" : "https://gitlab.example.com/eileen.lowe", "id" : 18, "username" : "eileen.lowe" }, @@ -414,7 +414,7 @@ Example response: "username" : "eileen.lowe", "id" : 18, "state" : "active", - "web_url" : "https://gitlab.example.com/u/eileen.lowe" + "web_url" : "https://gitlab.example.com/eileen.lowe" }, "state" : "closed", "title" : "Issues with auth", @@ -500,7 +500,7 @@ Example response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/axel.block" + "web_url": "https://gitlab.example.com/axel.block" }, "author": { "name": "Kris Steuber", @@ -508,7 +508,7 @@ Example response: "id": 10, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/solon.cremin" + "web_url": "https://gitlab.example.com/solon.cremin" }, "due_date": null, "web_url": "http://example.com/example/example/issues/11", @@ -557,7 +557,7 @@ Example response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/46f6f7dc858ada7be1853f7fb96e81da?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/axel.block" + "web_url": "https://gitlab.example.com/axel.block" }, "author": { "name": "Kris Steuber", @@ -565,7 +565,7 @@ Example response: "id": 10, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/solon.cremin" + "web_url": "https://gitlab.example.com/solon.cremin" }, "due_date": null, "web_url": "http://example.com/example/example/issues/11", @@ -614,7 +614,7 @@ Example response: "id": 21, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/3e6f06a86cf27fa8b56f3f74f7615987?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/keyon" + "web_url": "https://gitlab.example.com/keyon" }, "author": { "name": "Vivian Hermann", @@ -622,7 +622,7 @@ Example response: "id": 11, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/orville" + "web_url": "https://gitlab.example.com/orville" }, "subscribed": false, "due_date": null, @@ -669,7 +669,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "Issue", @@ -700,7 +700,7 @@ Example response: "id": 14, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/francisca" + "web_url": "https://gitlab.example.com/francisca" }, "author": { "name": "Maxie Medhurst", @@ -708,7 +708,7 @@ Example response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "subscribed": true, "user_notes_count": 7, diff --git a/doc/api/keys.md b/doc/api/keys.md index faa6f212b43..b68f08a007d 100644 --- a/doc/api/keys.md +++ b/doc/api/keys.md @@ -24,7 +24,7 @@ Parameters: "id": 25, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/cfa35b8cd2ec278026357769582fa563?s=40\u0026d=identicon", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2015-09-03T07:24:01.670Z", "is_admin": false, "bio": null, diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 494040a1ce8..f4167403c2c 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -621,7 +621,7 @@ Example response when the GitLab issue tracker is used: "author" : { "state" : "active", "id" : 18, - "web_url" : "https://gitlab.example.com/u/eileen.lowe", + "web_url" : "https://gitlab.example.com/eileen.lowe", "name" : "Alexandra Bashirian", "avatar_url" : null, "username" : "eileen.lowe" @@ -642,7 +642,7 @@ Example response when the GitLab issue tracker is used: "state" : "active", "id" : 1, "name" : "Administrator", - "web_url" : "https://gitlab.example.com/u/root", + "web_url" : "https://gitlab.example.com/root", "avatar_url" : null, "username" : "root" }, @@ -711,7 +711,7 @@ Example response: "id": 19, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/leila" + "web_url": "https://gitlab.example.com/leila" }, "assignee": { "name": "Celine Wehner", @@ -719,7 +719,7 @@ Example response: "id": 16, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/carli" + "web_url": "https://gitlab.example.com/carli" }, "source_project_id": 5, "target_project_id": 5, @@ -787,7 +787,7 @@ Example response: "id": 19, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/leila" + "web_url": "https://gitlab.example.com/leila" }, "assignee": { "name": "Celine Wehner", @@ -795,7 +795,7 @@ Example response: "id": 16, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/carli" + "web_url": "https://gitlab.example.com/carli" }, "source_project_id": 5, "target_project_id": 5, @@ -858,7 +858,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "MergeRequest", @@ -881,7 +881,7 @@ Example response: "id": 14, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a7fa515d53450023c83d62986d0658a8?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/francisca" + "web_url": "https://gitlab.example.com/francisca" }, "assignee": { "name": "Dr. Gabrielle Strosin", @@ -889,7 +889,7 @@ Example response: "id": 4, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/733005fcd7e6df12d2d8580171ccb966?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/barrett.krajcik" + "web_url": "https://gitlab.example.com/barrett.krajcik" }, "source_project_id": 3, "target_project_id": 3, diff --git a/doc/api/notes.md b/doc/api/notes.md index 572844b8b3f..58d40eecf3e 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -143,7 +143,7 @@ Example Response: "state": "active", "created_at": "2013-09-30T13:46:01Z", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/pipin" + "web_url": "https://gitlab.example.com/pipin" }, "created_at": "2016-04-05T22:10:44.164Z", "system": false, @@ -268,7 +268,7 @@ Example Response: "state": "active", "created_at": "2013-09-30T13:46:01Z", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/pipin" + "web_url": "https://gitlab.example.com/pipin" }, "created_at": "2016-04-06T16:51:53.239Z", "system": false, @@ -398,7 +398,7 @@ Example Response: "state": "active", "created_at": "2013-09-30T13:46:01Z", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/pipin" + "web_url": "https://gitlab.example.com/pipin" }, "created_at": "2016-04-05T22:11:59.923Z", "system": false, diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 847408a7f61..a29b3eb6f44 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -34,7 +34,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-16T10:23:19.007Z", "updated_at": "2016-08-16T10:23:19.216Z", @@ -57,7 +57,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-16T10:23:21.184Z", "updated_at": "2016-08-16T10:23:21.314Z", @@ -103,7 +103,7 @@ Example of response "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-11T11:28:34.085Z", "updated_at": "2016-08-11T11:32:35.169Z", @@ -148,7 +148,7 @@ Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-11T11:28:34.085Z", "updated_at": "2016-08-11T11:32:35.169Z", @@ -193,7 +193,7 @@ Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2016-08-11T11:28:34.085Z", "updated_at": "2016-08-11T11:32:35.169Z", diff --git a/doc/api/projects.md b/doc/api/projects.md index f96bf7f6d63..b7791b4748a 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -465,7 +465,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -482,7 +482,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "john", "data": { @@ -528,7 +528,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -552,7 +552,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2015-12-04T10:33:56.698Z", "system": false, @@ -567,7 +567,7 @@ Parameters: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" } diff --git a/doc/api/todos.md b/doc/api/todos.md index 0cd644dfd2f..a5e81801024 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -44,7 +44,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "MergeRequest", @@ -67,7 +67,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "assignee": { "name": "Administrator", @@ -75,7 +75,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "source_project_id": 2, "target_project_id": 2, @@ -117,7 +117,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "action_name": "assigned", "target_type": "MergeRequest", @@ -140,7 +140,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "assignee": { "name": "Administrator", @@ -148,7 +148,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "source_project_id": 2, "target_project_id": 2, @@ -215,7 +215,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "action_name": "marked", "target_type": "MergeRequest", @@ -238,7 +238,7 @@ Example Response: "id": 12, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/a0d477b3ea21970ce6ffcbb817b0b435?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/craig_rutherford" + "web_url": "https://gitlab.example.com/craig_rutherford" }, "assignee": { "name": "Administrator", @@ -246,7 +246,7 @@ Example Response: "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "https://gitlab.example.com/u/root" + "web_url": "https://gitlab.example.com/root" }, "source_project_id": 2, "target_project_id": 2, diff --git a/doc/api/users.md b/doc/api/users.md index a52b2d51d78..2b12770d5a5 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -20,7 +20,7 @@ GET /users "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", - "web_url": "http://localhost:3000/u/john_smith" + "web_url": "http://localhost:3000/john_smith" }, { "id": 2, @@ -28,7 +28,7 @@ GET /users "name": "Jack Smith", "state": "blocked", "avatar_url": "http://gravatar.com/../e32131cd8.jpeg", - "web_url": "http://localhost:3000/u/jack_smith" + "web_url": "http://localhost:3000/jack_smith" } ] ``` @@ -48,7 +48,7 @@ GET /users "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -81,7 +81,7 @@ GET /users "name": "Jack Smith", "state": "blocked", "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg", - "web_url": "http://localhost:3000/u/jack_smith", + "web_url": "http://localhost:3000/jack_smith", "created_at": "2012-05-23T08:01:01Z", "is_admin": false, "bio": null, @@ -141,7 +141,7 @@ Parameters: "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -172,7 +172,7 @@ Parameters: "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -293,7 +293,7 @@ GET /user "name": "John Smith", "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg", - "web_url": "http://localhost:3000/u/john_smith", + "web_url": "http://localhost:3000/john_smith", "created_at": "2012-05-23T08:00:58Z", "is_admin": false, "bio": null, @@ -665,7 +665,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -682,7 +682,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "john", "data": { @@ -728,7 +728,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" }, @@ -752,7 +752,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "created_at": "2015-12-04T10:33:56.698Z", "system": false, @@ -767,7 +767,7 @@ Example response: "id": 1, "state": "active", "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" }, "author_username": "root" } diff --git a/doc/development/performance.md b/doc/development/performance.md index 7ff603e2c4a..a00c0fe07b5 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -253,5 +253,5 @@ impact on runtime performance, and as such, using a constant instead of referencing an object directly may even slow code down. [#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607 -[yorickpeterse]: https://gitlab.com/u/yorickpeterse +[yorickpeterse]: https://gitlab.com/yorickpeterse [anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern -- cgit v1.2.1 From 41de407837dec94925af40c9c556ec9303f4de4e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 14:25:53 +0300 Subject: Add user routing rename to CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..2cbf5bc3277 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -102,6 +102,7 @@ v 8.13.0 (unreleased) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects + - Change user pages routing from /u/:username/PATH to /users/:username/PATH v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 -- cgit v1.2.1 From 0eb5b0a08af7ebe68d515a68e2a94b3d0213393f Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Mon, 3 Oct 2016 05:44:58 +0200 Subject: Use forked github-markup gem to enable python3 support with omnibus --- Gemfile | 2 +- Gemfile.lock | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 230561f90d3..7323776d24c 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' -gem 'github-markup', '~> 1.4' +gem 'github-markup', '~> 1.4', git: 'https://github.com/brodock/github-markup.git', branch: 'python3-fix' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' diff --git a/Gemfile.lock b/Gemfile.lock index 067908af3fb..dde92fff044 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/brodock/github-markup.git + revision: 62ff5993081920ad75016f2b87019dd14e3af137 + branch: python3-fix + specs: + github-markup (1.4.0) + GEM remote: https://rubygems.org/ specs: @@ -272,7 +279,6 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) - github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -864,7 +870,7 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.4) + github-markup (~> 1.4)! gitlab-flowdock-git-hook (~> 1.0.1) gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) -- cgit v1.2.1 From 2549db7aaed7cad60d5ddf129a70da25b09aded2 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 13 Oct 2016 04:44:23 +0200 Subject: Updated gitlab-markup forked gem to download from rubygems --- CHANGELOG | 3 +++ Gemfile | 2 +- Gemfile.lock | 11 +++-------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e13e0eef87a..b2a406b0def 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -109,6 +109,9 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects +v 8.12.7 + - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/Gemfile b/Gemfile index 7323776d24c..1204a546983 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' -gem 'github-markup', '~> 1.4', git: 'https://github.com/brodock/github-markup.git', branch: 'python3-fix' +gem 'gitlab-markup', '~> 1.5.0' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' diff --git a/Gemfile.lock b/Gemfile.lock index dde92fff044..25a176da8fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: https://github.com/brodock/github-markup.git - revision: 62ff5993081920ad75016f2b87019dd14e3af137 - branch: python3-fix - specs: - github-markup (1.4.0) - GEM remote: https://rubygems.org/ specs: @@ -279,6 +272,7 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) + github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -288,6 +282,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) + gitlab-markup (1.5.0) gitlab_git (10.6.8) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) @@ -870,8 +865,8 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.4)! gitlab-flowdock-git-hook (~> 1.0.1) + gitlab-markup (~> 1.5.0) gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) -- cgit v1.2.1 From 1290df050bc338776749f8f1965b44aae6baf726 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 14:00:56 +0200 Subject: Add job to trigger a docs.gitlab.com build --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 441f77740a8..62e06683124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -299,6 +299,14 @@ coverage: - coverage/index.html - coverage/assets/ +# Trigger docs build +trigger_docs: + stage: post-test + script: + - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds" + only: + - master + # Notify slack in the end notify:slack: -- cgit v1.2.1 From 8efa041a730515e57f127850414b3557c7af60b4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 14:55:44 +0200 Subject: Do not process build success if project was removed --- app/workers/build_success_worker.rb | 6 ++++-- spec/workers/build_success_worker_spec.rb | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index d6db7642d82..5afb15f52b4 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -4,13 +4,15 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - perform_deloyment(build) + return unless build.project + + create_deloyment(build) end end private - def perform_deloyment(build) + def create_deloyment(build) return if build.environment.blank? service = CreateDeploymentService.new( diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index c42bcd45d50..dba70883130 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -13,6 +13,17 @@ describe BuildSuccessWorker do described_class.new.perform(build.id) end end + + context 'when build is not associated with project' do + let!(:build) { create(:ci_build, project: nil) } + + it 'does not create deployment' do + expect_any_instance_of(CreateDeploymentService) + .not_to receive(:execute) + + described_class.new.perform(build.id) + end + end end context 'when build does not exist' do -- cgit v1.2.1 From 2709f679d07a5fc08f5cc31a416e00e61a04d2c1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 13 Oct 2016 15:02:05 +0200 Subject: Loosen requirement on request_store version This gem follows semantic versioning and will not introduce breaking changes in a minor version. See https://github.com/steveklabnik/request_store#semantic-versioning Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/2868 --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 230561f90d3..2601c33a5b4 100644 --- a/Gemfile +++ b/Gemfile @@ -225,7 +225,7 @@ gem 'gon', '~> 6.1.0' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' gem 'jquery-ui-rails', '~> 5.0.0' -gem 'request_store', '~> 1.3.0' +gem 'request_store', '~> 1.3' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' gem 'net-ssh', '~> 3.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 067908af3fb..77370e8bdaf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -937,7 +937,7 @@ DEPENDENCIES redis (~> 3.2) redis-namespace (~> 1.5.2) redis-rails (~> 4.0.0) - request_store (~> 1.3.0) + request_store (~> 1.3) rerun (~> 0.11.0) responders (~> 2.0) rouge (~> 2.0) -- cgit v1.2.1 From 5fd635d18b77f56f6acd264ccaa1a357e2fa1cdd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 15:22:55 +0200 Subject: Update code coverage for CI build asynchronously --- app/workers/build_coverage_worker.rb | 9 +++++++++ spec/workers/build_coverage_worker_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 app/workers/build_coverage_worker.rb create mode 100644 spec/workers/build_coverage_worker_spec.rb diff --git a/app/workers/build_coverage_worker.rb b/app/workers/build_coverage_worker.rb new file mode 100644 index 00000000000..0680645a8db --- /dev/null +++ b/app/workers/build_coverage_worker.rb @@ -0,0 +1,9 @@ +class BuildCoverageWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(build_id) + Ci::Build.find_by(id: build_id) + .try(:update_coverage) + end +end diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb new file mode 100644 index 00000000000..ba20488f663 --- /dev/null +++ b/spec/workers/build_coverage_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe BuildCoverageWorker do + describe '#perform' do + context 'when build exists' do + let!(:build) { create(:ci_build) } + + it 'updates code coverage' do + expect_any_instance_of(Ci::Build) + .to receive(:update_coverage) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 4e9342599b472a28928a95fd43efc4df851f8e33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 15:24:33 +0200 Subject: Fix typo in build success worker for deployment --- app/workers/build_success_worker.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index 5afb15f52b4..a9dc34166a1 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -6,13 +6,13 @@ class BuildSuccessWorker Ci::Build.find_by(id: build_id).try do |build| return unless build.project - create_deloyment(build) + create_deployment(build) end end private - def create_deloyment(build) + def create_deployment(build) return if build.environment.blank? service = CreateDeploymentService.new( -- cgit v1.2.1 From 17237e14466dd75e703b93daf56e511e13bdd28b Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Thu, 13 Oct 2016 14:43:05 +0000 Subject: Update README.md --- docker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index ee1f32adc26..f9e12c5733b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,6 +2,6 @@ * The official GitLab Community Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ce/). * The official GitLab Enterprise Edition Docker image is [available on Docker Hub](https://hub.docker.com/r/gitlab/gitlab-ee/). -* The complete usage guide can be found in [Using GitLab Docker images](http://doc.gitlab.com/omnibus/docker/) +* The complete usage guide can be found in [Using GitLab Docker images](https://docs.gitlab.com/omnibus/docker/) * The Dockerfile used for building public images is in [Omnibus Repository](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker) -* Check the guide for [creating Omnibus-based Docker Image](http://doc.gitlab.com/omnibus/build/README.html#build-docker-image) +* Check the guide for [creating Omnibus-based Docker Image](https://docs.gitlab.com/omnibus/build/README.html#build-docker-image) -- cgit v1.2.1 From ae851edc3c76fd7b81de2e3d48ec8a531f3609b3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 03:19:55 +0100 Subject: Added no-template functionality Added tests --- .../templates/issuable_template_selector.js.es6 | 8 ++++- app/views/shared/issuable/_form.html.haml | 2 ++ spec/features/projects/issuable_templates_spec.rb | 40 ++++++++++++++++++---- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6 index 2ecf3b18975..bd4e3c3d00d 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js.es6 +++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6 @@ -16,7 +16,13 @@ if (initialQuery.name) this.requestFile(initialQuery); $('.reset-template', this.dropdown.parent()).on('click', () => { - if (this.currentTemplate) this.setInputValueToTemplateContent(false); + this.setInputValueToTemplateContent(); + }); + + $('.no-template', this.dropdown.parent()).on('click', () => { + this.currentTemplate = ''; + this.setInputValueToTemplateContent(); + $('.dropdown-toggle-text', this.dropdown).text('Choose a template'); }); } diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c3f4e10c954..16834c3db29 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -23,6 +23,8 @@ data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do %ul.dropdown-footer-list %li + %a.no-template + No template %a.reset-template Reset template %div{ class: issuable_template_names.any? ? 'col-sm-7 col-lg-8' : 'col-sm-10' } diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index cd79c4f512d..d886909ce85 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -15,6 +15,7 @@ feature 'issuable templates', feature: true, js: true do let(:template_content) { 'this is a test "bug" template' } let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) } let(:issue) { create(:issue, author: user, assignee: user, project: project) } + let(:description_addition) { ' appending to description' } background do project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) @@ -26,7 +27,26 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "bug" template' do select_template 'bug' wait_for_ajax - preview_template(template_content) + preview_template + save_changes + end + + scenario 'user selects "bug" template and then "no template"' do + select_template 'bug' + wait_for_ajax + select_option 'No template' + wait_for_ajax + preview_template('') + save_changes('') + end + + scenario 'user selects "bug" template, edits description and then selects "reset template"' do + select_template 'bug' + wait_for_ajax + find_field('issue_description').send_keys(description_addition) + preview_template(template_content + description_addition) + select_option 'Reset template' + preview_template save_changes end @@ -37,7 +57,7 @@ feature 'issuable templates', feature: true, js: true do wait_for_ajax end_height = page.evaluate_script('$(".markdown-area").outerHeight()') - + expect(end_height).not_to eq(start_height) end end @@ -75,7 +95,7 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects "feature-proposal" template' do select_template 'feature-proposal' wait_for_ajax - preview_template(template_content) + preview_template save_changes end end @@ -102,25 +122,31 @@ feature 'issuable templates', feature: true, js: true do scenario 'user selects template' do select_template 'feature-proposal' wait_for_ajax - preview_template(template_content) + preview_template save_changes end end end end - def preview_template(expected_content) + def preview_template(expected_content = template_content) click_link 'Preview' expect(page).to have_content expected_content + click_link 'Write' end - def save_changes + def save_changes(expected_content = template_content) click_button "Save changes" - expect(page).to have_content template_content + expect(page).to have_content expected_content end def select_template(name) first('.js-issuable-selector').click first('.js-issuable-selector-wrap .dropdown-content a', text: name).click end + + def select_option(name) + first('.js-issuable-selector').click + first('.js-issuable-selector-wrap .dropdown-footer-list a', text: name).click + end end -- cgit v1.2.1 From 776cea4c00d883cafc2bc5381f3b61b146a93976 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 14:19:52 +0100 Subject: Handle case where deployment ref no longer exists Keep-around refs for deployments were only introduced in 8.10, so any deployment created in 8.9 could have a SHA pointing to a commit that no longer exists in the repository. We can't do anything useful with those deployments, so make `#includes_commit?` always return false for those. --- app/models/deployment.rb | 9 ++++++++- spec/models/deployment_spec.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 82b27b78229..f63cc179b9e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base def includes_commit?(commit) return false unless commit - project.repository.is_ancestor?(commit.id, sha) + # Before 8.10, deployments didn't have keep-around refs. Any deployment + # created before then could have a `sha` referring to a commit that no + # longer exists in the repository, so just ignore those. + begin + project.repository.is_ancestor?(commit.id, sha) + rescue Rugged::OdbError + false + end end def update_merge_request_metrics! diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index bfff639ad78..01a4a53a264 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -38,5 +38,14 @@ describe Deployment, models: true do expect(deployment.includes_commit?(commit)).to be true end end + + context 'when the SHA for the deployment does not exist in the repo' do + it 'returns false' do + deployment.update(sha: Gitlab::Git::BLANK_SHA) + commit = project.commit + + expect(deployment.includes_commit?(commit)).to be false + end + end end end -- cgit v1.2.1 From 0f3960cffaf80eccc2b85e4f5d2d8558dfc633f6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 29 Sep 2016 14:28:33 -0500 Subject: Replace unique keyframes mixin with specific keyframe animation names --- CHANGELOG | 1 + app/assets/stylesheets/framework/logo.scss | 78 +++++++++++++--------------- app/assets/stylesheets/framework/mixins.scss | 7 +++ 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1cb9b8acf51..cb17a856d66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Speed-up group milestones show page - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) + - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Fix permission for setting an issue's due date diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index 3ee3fb4cee5..c214eabcad7 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -1,15 +1,3 @@ -@mixin unique-keyframes { - $animation-name: unique-id(); - @include webkit-prefix(animation-name, $animation-name); - - @-webkit-keyframes #{$animation-name} { - @content; - } - @keyframes #{$animation-name} { - @content; - } -} - @mixin tanuki-logo-colors($path-color) { fill: $path-color; transition: all 0.8s; @@ -20,28 +8,6 @@ } } -@mixin tanuki-second-highlight-animations($tanuki-color) { - @include unique-keyframes { - 10%, 80% { - fill: #{$tanuki-color} - } - 20%, 90% { - fill: lighten($tanuki-color, 25%); - } - } -} - -@mixin tanuki-forth-highlight-animations($tanuki-color) { - @include unique-keyframes { - 30%, 60% { - fill: #{$tanuki-color}; - } - 40%, 70% { - fill: lighten($tanuki-color, 25%); - } - } -} - .tanuki-logo { .tanuki-left-ear, @@ -67,7 +33,7 @@ } .tanuki-left-cheek { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-left-cheek) { 0%, 10%, 100% { fill: lighten($tanuki-yellow, 25%); } @@ -78,15 +44,29 @@ } .tanuki-left-eye { - @include tanuki-second-highlight-animations($tanuki-orange); + @include include-keyframes(animate-tanuki-left-eye) { + 10%, 80% { + fill: $tanuki-orange; + } + 20%, 90% { + fill: lighten($tanuki-orange, 25%); + } + } } .tanuki-left-ear { - @include tanuki-second-highlight-animations($tanuki-red); + @include include-keyframes(animate-tanuki-left-ear) { + 10%, 80% { + fill: $tanuki-red; + } + 20%, 90% { + fill: lighten($tanuki-red, 25%); + } + } } .tanuki-nose { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-nose) { 20%, 70% { fill: $tanuki-red; } @@ -97,15 +77,29 @@ } .tanuki-right-eye { - @include tanuki-forth-highlight-animations($tanuki-orange); + @include include-keyframes(animate-tanuki-right-eye) { + 30%, 60% { + fill: $tanuki-orange; + } + 40%, 70% { + fill: lighten($tanuki-orange, 25%); + } + } } .tanuki-right-ear { - @include tanuki-forth-highlight-animations($tanuki-red); + @include include-keyframes(animate-tanuki-right-ear) { + 30%, 60% { + fill: $tanuki-red; + } + 40%, 70% { + fill: lighten($tanuki-red, 25%); + } + } } .tanuki-right-cheek { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-right-cheek) { 40% { fill: $tanuki-yellow; } @@ -115,4 +109,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1ec08cdef23..5d9f341aa5e 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -95,3 +95,10 @@ @content; } } + +@mixin include-keyframes($animation-name) { + @include webkit-prefix(animation-name, $animation-name); + @include keyframes($animation-name) { + @content; + } +} -- cgit v1.2.1 From bba47886264d0ca7650d2b4b202d69984650b0ae Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 10 Oct 2016 09:40:14 +0200 Subject: Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService --- CHANGELOG | 1 + app/models/project.rb | 5 --- app/services/git_push_service.rb | 15 ++------- app/workers/update_merge_requests_worker.rb | 16 +++++++++ spec/models/project_spec.rb | 21 ------------ spec/services/git_push_service_spec.rb | 4 +-- .../merge_requests/refresh_service_spec.rb | 3 +- spec/workers/post_receive_spec.rb | 8 ++++- spec/workers/update_merge_requests_worker_spec.rb | 38 ++++++++++++++++++++++ 9 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 app/workers/update_merge_requests_worker.rb create mode 100644 spec/workers/update_merge_requests_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 4b1ac9d2c3c..472601fa336 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.13.0 (unreleased) - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) + - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment diff --git a/app/models/project.rb b/app/models/project.rb index 74d54e69648..8c488f4aeef 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -827,11 +827,6 @@ class Project < ActiveRecord::Base end end - def update_merge_requests(oldrev, newrev, ref, user) - MergeRequests::RefreshService.new(self, user). - execute(oldrev, newrev, ref) - end - def valid_repo? repository.exists? rescue diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index c499427605a..e8415862de5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -63,13 +63,12 @@ class GitPushService < BaseService protected def update_merge_requests - @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) + UpdateMergeRequestsWorker.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) EventCreateService.new.push(@project, current_user, build_push_data) - SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks) - Ci::CreatePipelineService.new(project, current_user, build_push_data).execute + Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute ProjectCacheWorker.perform_async(@project.id) end @@ -148,16 +147,6 @@ class GitPushService < BaseService push_commits) end - def build_push_data_system_hook - @push_data_system ||= Gitlab::DataBuilder::Push.build( - @project, - current_user, - params[:oldrev], - params[:newrev], - params[:ref], - []) - end - def push_to_existing_branch? # Return if this is not a push to a branch (e.g. new commits) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb new file mode 100644 index 00000000000..03f0528cdae --- /dev/null +++ b/app/workers/update_merge_requests_worker.rb @@ -0,0 +1,16 @@ +class UpdateMergeRequestsWorker + include Sidekiq::Worker + + def perform(project_id, user_id, oldrev, newrev, ref) + project = Project.find_by(id: project_id) + return unless project + + user = User.find_by(id: user_id) + return unless user + + MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref) + + push_data = Gitlab::DataBuilder::Push.build(project, user, oldrev, newrev, ref, []) + SystemHooksService.new.execute_hooks(push_data, :push_hooks) + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dae546a0cdc..7435713d4c0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -219,7 +219,6 @@ describe Project, models: true do describe 'Respond to' do it { is_expected.to respond_to(:url_to_repo) } it { is_expected.to respond_to(:repo_exists?) } - it { is_expected.to respond_to(:update_merge_requests) } it { is_expected.to respond_to(:execute_hooks) } it { is_expected.to respond_to(:owner) } it { is_expected.to respond_to(:path_with_namespace) } @@ -380,26 +379,6 @@ describe Project, models: true do end end - describe '#update_merge_requests' do - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:key) { create(:key, user_id: project.owner.id) } - let(:prev_commit_id) { merge_request.commits.last.id } - let(:commit_id) { merge_request.commits.first.id } - - it 'closes merge request if last commit from source branch was pushed to target branch' do - project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user) - merge_request.reload - expect(merge_request.merged?).to be_truthy - end - - it 'updates merge request commits with new one if pushed to source branch' do - project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user) - merge_request.reload - expect(merge_request.diff_head_sha).to eq(commit_id) - end - end - describe '.find_with_namespace' do context 'with namespace' do before do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8e3e12114f2..dd2a9e9903a 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -184,8 +184,8 @@ describe GitPushService, services: true do context "Updates merge requests" do it "when pushing a new branch for the first time" do - expect(project).to receive(:update_merge_requests). - with(@blankrev, 'newrev', 'refs/heads/master', user) + expect(UpdateMergeRequestsWorker).to receive(:perform_async). + with(project.id, user.id, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 5b4e4908add..e515bc9f89c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -62,7 +62,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } - it { expect(@merge_request.merge_when_build_succeeds).to be_falsey} + it { expect(@merge_request.merge_when_build_succeeds).to be_falsey } + it { expect(@merge_request.diff_head_sha).to eq(@newrev) } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } it { expect(@build_failed_todo).to be_done } diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index ffeaafe654a..984acdade36 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -92,7 +92,13 @@ describe PostReceive do allow(Project).to receive(:find_with_namespace).and_return(project) expect(project).to receive(:execute_hooks).twice expect(project).to receive(:execute_services).twice - expect(project).to receive(:update_merge_requests) + + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + + it "enqueues a UpdateMergeRequestsWorker job" do + allow(Project).to receive(:find_with_namespace).and_return(project) + expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args) PostReceive.new.perform(pwd(project), key_id, base64_changes) end diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb new file mode 100644 index 00000000000..c78a69eda67 --- /dev/null +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe UpdateMergeRequestsWorker do + include RepoHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + subject { described_class.new } + + describe '#perform' do + let(:oldrev) { "123456" } + let(:newrev) { "789012" } + let(:ref) { "refs/heads/test" } + + def perform + subject.perform(project.id, user.id, oldrev, newrev, ref) + end + + it 'executes MergeRequests::RefreshService with expected values' do + expect(MergeRequests::RefreshService).to receive(:new).with(project, user).and_call_original + expect_any_instance_of(MergeRequests::RefreshService).to receive(:execute).with(oldrev, newrev, ref) + + perform + end + + it 'executes SystemHooksService with expected values' do + push_data = double('push_data') + expect(Gitlab::DataBuilder::Push).to receive(:build).with(project, user, oldrev, newrev, ref, []).and_return(push_data) + + system_hook_service = double('system_hook_service') + expect(SystemHooksService).to receive(:new).and_return(system_hook_service) + expect(system_hook_service).to receive(:execute_hooks).with(push_data, :push_hooks) + + perform + end + end +end -- cgit v1.2.1 From 069f2d347585a0f79ab8e3ddfb194ebbc86176c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 29 Sep 2016 19:31:14 +0200 Subject: Draft a quick CE->EE merge check rake task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 7 +++ lib/tasks/ce_to_ee_merge_check.rake | 6 +++ lib/tasks/gitlab/dev.rake | 96 +++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 lib/tasks/ce_to_ee_merge_check.rake create mode 100644 lib/tasks/gitlab/dev.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..7e2d705a888 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,6 +210,13 @@ rake brakeman: *exec rake flay: *exec license_finder: *exec rake downtime_check: *exec +rake ce_to_ee_merge_check: + <<: *exec + only: + - branches + except: + - tags + - master rake db:migrate:reset: stage: test diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake new file mode 100644 index 00000000000..bb87f85cd9c --- /dev/null +++ b/lib/tasks/ce_to_ee_merge_check.rake @@ -0,0 +1,6 @@ +desc 'Checks if the branch would apply cleanly to EE' +task ce_to_ee_merge_check: :environment do + return if defined?(Gitlab::License) + + Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke +end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake new file mode 100644 index 00000000000..bf17ba499bc --- /dev/null +++ b/lib/tasks/gitlab/dev.rake @@ -0,0 +1,96 @@ +namespace :gitlab do + namespace :dev do + desc 'Checks if the branch would apply cleanly to EE' + task ce_to_ee_merge_check: :environment do + ce_repo = ENV['CI_BUILD_REPO'] + ce_branch = ENV['CI_BUILD_REF_NAME'] + + ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git' + ee_branch = "#{ce_branch}-ee" + ee_dir = 'gitlab-ee-merge-check' + + puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n" + `git clone #{ee_repo} #{ee_dir} --depth 1` + Dir.chdir(ee_dir) do + puts "\n => Fetching #{ce_repo}/#{ce_branch}\n" + `git fetch #{ce_repo} #{ce_branch} --depth 1` + + # Try to merge the current tested branch to EE/master... + puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" + `git merge --ff-only FETCH_HEAD` + + exit 0 if $?.success? + + # Try to merge a possible -ee branch to EE/master... + puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" + `git merge --ff-only #{ee_branch}` + + # The -ee doesn't exist + if $?.exitstatus == 1 + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to the + current EE/master, and no #{ee_branch} branch was detected in + the EE repository. + + Please create a #{ee_branch} branch that includes changes + #{ce_branch} but also specific changes than can be applied cleanly + to EE/master. + + You can create this branch as follow: + + 1. In the EE repo: + $ git fetch origin + $ git fetch #{ce_repo} #{ce_branch} + $ git checkout -b #{ee_branch} FETCH_HEAD + $ git rebase origin/master + 2. At this point you will likely have conflicts, solve them, and + continue/finish the rebase. + 3. You can squash all the original #{ce_branch} commits into a + single "Port of #{ce_branch} to EE". + 4. Push your branch to #{ee_repo}: + $ git push origin #{ee_branch} + =================================================================\n + MSG + + exit 1 + end + + # The -ee cannot be merged cleanly to EE/master... + unless $?.success? + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to + EE/master, and even though the #{ee_branch} branch exists in the EE + repository, it cannot be merged without conflicts to EE/master. + + Please update the #{ee_branch}, push it again to #{ee_repo}, and + retry this job. + =================================================================\n + MSG + + exit 2 + end + + puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" + `git merge --ff-only FETCH_HEAD` + exit 0 if $?.success? + + # The -ee can be merged cleanly to EE/master, but still + # cannot be merged cleanly to EE/master... + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to EE, and + even though the #{ee_branch} branch exists in the EE repository and + applies cleanly to EE/master, it doesn't prevent conflicts when + merging #{ce_branch} into EE. + + We may be in a complex situation here. + =================================================================\n + MSG + + exit 3 + end + end + end +end -- cgit v1.2.1 From 2650d5f895c6f7e56f7ba76e5fa448d38fd15d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 17:21:51 +0200 Subject: Improve the branch existence and merge checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add a safeguard for non-CI env. Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 2 +- lib/tasks/ce_to_ee_merge_check.rake | 2 -- lib/tasks/gitlab/dev.rake | 35 +++++++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e2d705a888..8708eae1b2a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -216,7 +216,7 @@ rake ce_to_ee_merge_check: - branches except: - tags - - master + allow_failure: yes rake db:migrate:reset: stage: test diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake index bb87f85cd9c..424e7883060 100644 --- a/lib/tasks/ce_to_ee_merge_check.rake +++ b/lib/tasks/ce_to_ee_merge_check.rake @@ -1,6 +1,4 @@ desc 'Checks if the branch would apply cleanly to EE' task ce_to_ee_merge_check: :environment do - return if defined?(Gitlab::License) - Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake index bf17ba499bc..47bdb2d32d2 100644 --- a/lib/tasks/gitlab/dev.rake +++ b/lib/tasks/gitlab/dev.rake @@ -2,6 +2,9 @@ namespace :gitlab do namespace :dev do desc 'Checks if the branch would apply cleanly to EE' task ce_to_ee_merge_check: :environment do + return if defined?(Gitlab::License) + return unless ENV['CI'] + ce_repo = ENV['CI_BUILD_REPO'] ce_branch = ENV['CI_BUILD_REF_NAME'] @@ -17,27 +20,28 @@ namespace :gitlab do # Try to merge the current tested branch to EE/master... puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge --ff-only FETCH_HEAD` + `git merge FETCH_HEAD` exit 0 if $?.success? - # Try to merge a possible -ee branch to EE/master... - puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" - `git merge --ff-only #{ee_branch}` + # Check if the -ee branch exists... + puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n" + `git rev-parse --verify #{ee_branch}` # The -ee doesn't exist - if $?.exitstatus == 1 + unless $?.success? + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to the current EE/master, and no #{ee_branch} branch was detected in the EE repository. - Please create a #{ee_branch} branch that includes changes + Please create a #{ee_branch} branch that includes changes from #{ce_branch} but also specific changes than can be applied cleanly to EE/master. - You can create this branch as follow: + You can create this branch as follows: 1. In the EE repo: $ git fetch origin @@ -45,7 +49,8 @@ namespace :gitlab do $ git checkout -b #{ee_branch} FETCH_HEAD $ git rebase origin/master 2. At this point you will likely have conflicts, solve them, and - continue/finish the rebase. + continue/finish the rebase. Note: You can squash the CE commits + before rebasing. 3. You can squash all the original #{ce_branch} commits into a single "Port of #{ce_branch} to EE". 4. Push your branch to #{ee_repo}: @@ -56,10 +61,15 @@ namespace :gitlab do exit 1 end + # Try to merge the -ee branch to EE/master... + puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" + `git merge #{ee_branch} master` + # The -ee cannot be merged cleanly to EE/master... unless $?.success? + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to EE/master, and even though the #{ee_branch} branch exists in the EE repository, it cannot be merged without conflicts to EE/master. @@ -73,13 +83,14 @@ namespace :gitlab do end puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge --ff-only FETCH_HEAD` + `git merge FETCH_HEAD` exit 0 if $?.success? # The -ee can be merged cleanly to EE/master, but still # cannot be merged cleanly to EE/master... + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to EE, and even though the #{ee_branch} branch exists in the EE repository and applies cleanly to EE/master, it doesn't prevent conflicts when -- cgit v1.2.1 From e836b904b77e857ad2e1a0bc65129380d5f0e6dc Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 17:52:21 +0200 Subject: Grapify system hooks API --- lib/api/system_hooks.rb | 60 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 22b8f90dc5c..2e76b91051f 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -7,38 +7,36 @@ module API end resource :hooks do - # Get the list of system hooks - # - # Example Request: - # GET /hooks + desc 'Get the list of system hooks' do + success Entities::Hook + end get do - @hooks = SystemHook.all - present @hooks, with: Entities::Hook + hooks = SystemHook.all + present hooks, with: Entities::Hook end - # Create new system hook - # - # Parameters: - # url (required) - url for system hook - # Example Request - # POST /hooks + desc 'Create a new system hook' do + success Entities::Hook + end + params do + requires :url, type: String, desc: 'The URL for the system hook' + end post do - attrs = attributes_for_keys [:url] - required_attributes! [:url] - @hook = SystemHook.new attrs - if @hook.save - present @hook, with: Entities::Hook + hook = SystemHook.new declared(params).to_h + + if hook.save + present hook, with: Entities::Hook else not_found! end end - # Test a hook - # - # Example Request - # GET /hooks/:id + desc 'Test a hook' + params do + requires :id, type: Integer, desc: 'The ID of the system hook' + end get ":id" do - @hook = SystemHook.find(params[:id]) + hook = SystemHook.find(params[:id]) data = { event_name: "project_create", name: "Ruby", @@ -47,20 +45,20 @@ module API owner_name: "Someone", owner_email: "example@gitlabhq.com" } - @hook.execute(data, 'system_hooks') + hook.execute(data, 'system_hooks') data end - # Delete a hook. This is an idempotent function. - # - # Parameters: - # id (required) - ID of the hook - # Example Request: - # DELETE /hooks/:id + desc 'Delete a hook' do + success Entities::Hook + end + params do + requires :id, type: Integer, desc: 'The ID of the system hook' + end delete ":id" do begin - @hook = SystemHook.find(params[:id]) - @hook.destroy + hook = SystemHook.find(params[:id]) + present hook.destroy, with: Entities::Hook rescue # SystemHook raises an Error if no hook with id found end -- cgit v1.2.1 From 36309374df8feeea99c4eef5b0fd1e27d41ff6c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 19:16:45 +0200 Subject: Do not run before_script, artifacts, cache in trigger_docs job --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..6b54d07d1d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -302,6 +302,9 @@ coverage: # Trigger docs build trigger_docs: stage: post-test + before_script: [] + cache: {} + artifacts: {} script: - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds" only: -- cgit v1.2.1 From 8751491b8db471dc661daa19bc82a9dbd58e4aae Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 13 Oct 2016 13:58:31 -0500 Subject: Improve tabbing usability for sign in page --- CHANGELOG | 1 + app/assets/stylesheets/pages/login.scss | 10 ++++++++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7f96a9cba4e..a6091d3f4f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.13.0 (unreleased) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page + - Improve tabbing usability for sign in page (ClemMakesApps) - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a5ca509163d..a08b033dff9 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -48,6 +48,16 @@ margin: 0 0 10px; } + .new_user { + position: relative; + padding-bottom: 35px; + } + + .sign-in { + position: absolute; + bottom: 0; + } + .login-footer { margin-top: 10px; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 9f5520603cd..781fd1b32a6 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,8 @@ = form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off" = f.password_field :password, class: "form-control bottom", placeholder: "Password" + .sign-in + = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -8,5 +10,3 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div - = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From 5c5259335f8bcc4de117c1e36648a269911281fb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 11:39:37 +0100 Subject: Add instrumentation to conflict classes --- config/initializers/metrics.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index be22085b0df..3b8771543e4 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -67,6 +67,7 @@ if Gitlab::Metrics.enabled? ['app', 'finders'] => ['app', 'finders'], ['app', 'mailers', 'emails'] => ['app', 'mailers'], ['app', 'services', '**'] => ['app', 'services'], + ['lib', 'gitlab', 'conflicts'] => ['lib'], ['lib', 'gitlab', 'diff'] => ['lib'], ['lib', 'gitlab', 'email', 'message'] => ['lib'], ['lib', 'gitlab', 'checks'] => ['lib'] -- cgit v1.2.1 From 3f71c43e88c56bb5310c8814cd9f95cafb4f53ef Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 13:59:10 +0100 Subject: Allow setting content for resolutions When reading conflicts: 1. Add a `type` field. `text` works as before, and has `sections`; `text-editor` is a file with ambiguous conflict markers that can only be resolved in an editor. 2. Add a `content_path` field pointing to a JSON representation of the file's content for a single file. 3. Hitting `content_path` returns a similar datastructure to the `file`, but without the `content_path` and `sections` fields, and with a `content` field containing the full contents of the file (with conflict markers). When writing conflicts: 1. Instead of `sections` being at the top level, they are now in a `files` array. This matches the read format better. 2. The `files` array contains file hashes, each of which must contain: a. `new_path` b. `old_path` c. EITHER `sections` (which works as before) or `content` (with the full content of the resolved file). --- app/controllers/application_controller.rb | 7 +- .../projects/merge_requests_controller.rb | 20 ++- app/models/merge_request.rb | 2 +- app/services/merge_requests/resolve_service.rb | 24 ++- config/routes/project.rb | 1 + lib/gitlab/conflict/file.rb | 51 ++++++- lib/gitlab/conflict/file_collection.rb | 4 + lib/gitlab/conflict/parser.rb | 15 +- lib/gitlab/conflict/resolution_error.rb | 6 + .../projects/merge_requests_controller_spec.rb | 168 +++++++++++++++++++-- .../merge_requests/resolve_service_spec.rb | 139 +++++++++++++++-- 11 files changed, 395 insertions(+), 42 deletions(-) create mode 100644 lib/gitlab/conflict/resolution_error.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b3455e04c29..bf37421771f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -114,7 +114,12 @@ class ApplicationController < ActionController::Base end def render_404 - render file: Rails.root.join("public", "404"), layout: false, status: "404" + respond_to do |format| + format.json { head :not_found } + format.any do + render file: Rails.root.join("public", "404"), layout: false, status: "404" + end + end end def no_cache_headers diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 869d96b86f4..2c7a0062f2b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -9,15 +9,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ - :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, + :edit, :update, :show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines, :merge, :merge_check, :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] - before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] + before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines] before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check] before_action :define_commit_vars, only: [:diffs] before_action :define_diff_comment_vars, only: [:diffs] - before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :pipelines] + before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines] before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :apply_diff_view_cookie!, only: [:new_diffs] before_action :build_merge_request, only: [:new, :new_diffs] @@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :authenticate_user!, only: [:assign_related_issues] - before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :conflict_for_path, :resolve_conflicts] def index @merge_requests = merge_requests_collection @@ -170,6 +170,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def conflict_for_path + return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? + + file = @merge_request.conflicts.file_for_path(params[:old_path], params[:new_path]) + + return render_404 unless file + + render json: file, full_content: true + end + def resolve_conflicts return render_404 unless @merge_request.conflicts_can_be_resolved_in_ui? @@ -184,7 +194,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) } - rescue Gitlab::Conflict::File::MissingResolution => e + rescue Gitlab::Conflict::ResolutionError => e render status: :bad_request, json: { message: e.message } end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..1fb0371fe06 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -868,7 +868,7 @@ class MergeRequest < ActiveRecord::Base # files. conflicts.files.each(&:lines) @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0 - rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing + rescue Rugged::OdbError, Gitlab::Conflict::Parser::UnresolvableError, Gitlab::Conflict::FileCollection::ConflictSideMissing @conflicts_can_be_resolved_in_ui = false end end diff --git a/app/services/merge_requests/resolve_service.rb b/app/services/merge_requests/resolve_service.rb index 19caa038c44..d22a1d3e0ad 100644 --- a/app/services/merge_requests/resolve_service.rb +++ b/app/services/merge_requests/resolve_service.rb @@ -1,5 +1,8 @@ module MergeRequests class ResolveService < MergeRequests::BaseService + class MissingFiles < Gitlab::Conflict::ResolutionError + end + attr_accessor :conflicts, :rugged, :merge_index, :merge_request def execute(merge_request) @@ -10,8 +13,16 @@ module MergeRequests fetch_their_commit! - conflicts.files.each do |file| - write_resolved_file_to_index(file, params[:sections]) + params[:files].each do |file_params| + conflict_file = merge_request.conflicts.file_for_path(file_params[:old_path], file_params[:new_path]) + + write_resolved_file_to_index(conflict_file, file_params) + end + + unless merge_index.conflicts.empty? + missing_files = merge_index.conflicts.map { |file| file[:ours][:path] } + + raise MissingFiles, "Missing resolutions for the following files: #{missing_files.join(', ')}" end commit_params = { @@ -23,8 +34,13 @@ module MergeRequests project.repository.resolve_conflicts(current_user, merge_request.source_branch, commit_params) end - def write_resolved_file_to_index(file, resolutions) - new_file = file.resolve_lines(resolutions).map(&:text).join("\n") + def write_resolved_file_to_index(file, params) + new_file = if params[:sections] + file.resolve_lines(params[:sections]).map(&:text).join("\n") + elsif params[:content] + file.resolve_content(params[:content]) + end + our_path = file.our_path merge_index.add(path: our_path, oid: rugged.write(new_file, :blob), mode: file.our_mode) diff --git a/config/routes/project.rb b/config/routes/project.rb index f9d58f5d5b2..cbce9cd47a0 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -267,6 +267,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get :commits get :diffs get :conflicts + get :conflict_for_path get :builds get :pipelines get :merge_check diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index dff9e29c6a5..26a9f170298 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -4,12 +4,12 @@ module Gitlab include Gitlab::Routing.url_helpers include IconsHelper - class MissingResolution < StandardError + class MissingResolution < ResolutionError end CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository + attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository, :type def initialize(merge_file_result, conflict, merge_request:) @merge_file_result = merge_file_result @@ -21,12 +21,24 @@ module Gitlab @match_line_headers = {} end + def content + merge_file_result[:data] + end + # Array of Gitlab::Diff::Line objects def lines - @lines ||= Gitlab::Conflict::Parser.new.parse(merge_file_result[:data], + return @lines if defined?(@lines) + + begin + @type = 'text' + @lines = Gitlab::Conflict::Parser.new.parse(content, our_path: our_path, their_path: their_path, parent_file: self) + rescue Gitlab::Conflict::Parser::ParserError + @type = 'text-editor' + @lines = nil + end end def resolve_lines(resolution) @@ -53,6 +65,14 @@ module Gitlab end.compact end + def resolve_content(resolution) + if resolution == content + raise MissingResolution, "Resolved content has no changes for file #{our_path}" + end + + resolution + end + def highlight_lines! their_file = lines.reject { |line| line.type == 'new' }.map(&:text).join("\n") our_file = lines.reject { |line| line.type == 'old' }.map(&:text).join("\n") @@ -170,21 +190,36 @@ module Gitlab match_line.text = "@@ -#{match_line.old_pos},#{line.old_pos} +#{match_line.new_pos},#{line.new_pos} @@#{header}" end - def as_json(opts = nil) - { + def as_json(opts = {}) + json_hash = { old_path: their_path, new_path: our_path, blob_icon: file_type_icon_class('file', our_mode, our_path), blob_path: namespace_project_blob_path(merge_request.project.namespace, merge_request.project, - ::File.join(merge_request.diff_refs.head_sha, our_path)), - sections: sections + ::File.join(merge_request.diff_refs.head_sha, our_path)) } + + if opts[:full_content] + json_hash.merge(content: content) + else + json_hash.merge!(sections: sections) if type == 'text' + + json_hash.merge(type: type, content_path: content_path) + end + end + + def content_path + conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace, + merge_request.project, + merge_request, + old_path: their_path, + new_path: our_path) end # Don't try to print merge_request or repository. def inspect - instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode].map do |instance_variable| + instance_variables = [:merge_file_result, :their_path, :our_path, :our_mode, :type].map do |instance_variable| value = instance_variable_get("@#{instance_variable}") "#{instance_variable}=\"#{value}\"" diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index bbd0427a2c8..fa5bd4649d4 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -30,6 +30,10 @@ module Gitlab end end + def file_for_path(old_path, new_path) + files.find { |file| file.their_path == old_path && file.our_path == new_path } + end + def as_json(opts = nil) { target_branch: merge_request.target_branch, diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 98e842cded3..ddd657903fb 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -1,19 +1,24 @@ module Gitlab module Conflict class Parser - class ParserError < StandardError + class UnresolvableError < StandardError end - class UnexpectedDelimiter < ParserError + class UnmergeableFile < UnresolvableError end - class MissingEndDelimiter < ParserError + class UnsupportedEncoding < UnresolvableError + end + + # Recoverable errors - the conflict can be resolved in an editor, but not with + # sections. + class ParserError < StandardError end - class UnmergeableFile < ParserError + class UnexpectedDelimiter < ParserError end - class UnsupportedEncoding < ParserError + class MissingEndDelimiter < ParserError end def parse(text, our_path:, their_path:, parent_file: nil) diff --git a/lib/gitlab/conflict/resolution_error.rb b/lib/gitlab/conflict/resolution_error.rb new file mode 100644 index 00000000000..a0f2006bc24 --- /dev/null +++ b/lib/gitlab/conflict/resolution_error.rb @@ -0,0 +1,6 @@ +module Gitlab + module Conflict + class ResolutionError < StandardError + end + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 84298f8bef4..1311b4aa264 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -570,7 +570,7 @@ describe Projects::MergeRequestsController do context 'when the conflicts cannot be resolved in the UI' do before do allow_any_instance_of(Gitlab::Conflict::Parser). - to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnexpectedDelimiter) + to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) get :conflicts, namespace_id: merge_request_with_conflicts.project.namespace.to_param, @@ -655,6 +655,61 @@ describe Projects::MergeRequestsController do id: merge_request.iid expect(merge_request.reload.title).to eq(merge_request.wipless_title) + end + + describe 'GET conflict_for_path' do + let(:json_response) { JSON.parse(response.body) } + + def conflict_for_path(path) + get :conflict_for_path, + namespace_id: merge_request_with_conflicts.project.namespace.to_param, + project_id: merge_request_with_conflicts.project.to_param, + id: merge_request_with_conflicts.iid, + old_path: path, + new_path: path, + format: 'json' + end + + context 'when the conflicts cannot be resolved in the UI' do + before do + allow_any_instance_of(Gitlab::Conflict::Parser). + to receive(:parse).and_raise(Gitlab::Conflict::Parser::UnmergeableFile) + + conflict_for_path('files/ruby/regex.rb') + end + + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'when the file does not exist cannot be resolved in the UI' do + before { conflict_for_path('files/ruby/regexp.rb') } + + it 'returns a 404 status code' do + expect(response).to have_http_status(:not_found) + end + end + + context 'with an existing file' do + let(:path) { 'files/ruby/regex.rb' } + + before { conflict_for_path(path) } + + it 'returns a 200 status code' do + expect(response).to have_http_status(:ok) + end + + it 'returns the file in JSON format' do + content = merge_request_with_conflicts.conflicts.file_for_path(path, path).content + + expect(json_response).to include('old_path' => path, + 'new_path' => path, + 'blob_icon' => 'file-text-o', + 'blob_path' => a_string_ending_with(path), + 'content' => content) + end end end @@ -662,22 +717,37 @@ describe Projects::MergeRequestsController do let(:json_response) { JSON.parse(response.body) } let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } - def resolve_conflicts(sections) + def resolve_conflicts(files) post :resolve_conflicts, namespace_id: merge_request_with_conflicts.project.namespace.to_param, project_id: merge_request_with_conflicts.project.to_param, id: merge_request_with_conflicts.iid, format: 'json', - sections: sections, + files: files, commit_message: 'Commit message' end context 'with valid params' do before do - resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin') + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) end it 'creates a new commit on the branch' do @@ -692,7 +762,23 @@ describe Projects::MergeRequestsController do context 'when sections are missing' do before do - resolve_conflicts('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head') + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'sections' => { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' + } + } + ] + + resolve_conflicts(resolved_files) end it 'returns a 400 error' do @@ -700,7 +786,71 @@ describe Projects::MergeRequestsController do end it 'has a message with the name of the first missing section' do - expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9') + expect(json_response['message']).to include('6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when files are missing' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the name of the missing file' do + expect(json_response['message']).to include('files/ruby/popen.rb') + end + + it 'does not create a new commit' do + expect(original_head_sha).to eq(merge_request_with_conflicts.source_branch_head.sha) + end + end + + context 'when a file has identical content to the conflict' do + before do + resolved_files = [ + { + 'new_path' => 'files/ruby/popen.rb', + 'old_path' => 'files/ruby/popen.rb', + 'content' => merge_request_with_conflicts.conflicts.file_for_path('files/ruby/popen.rb', 'files/ruby/popen.rb').content + }, { + 'new_path' => 'files/ruby/regex.rb', + 'old_path' => 'files/ruby/regex.rb', + 'sections' => { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ] + + resolve_conflicts(resolved_files) + end + + it 'returns a 400 error' do + expect(response).to have_http_status(:bad_request) + end + + it 'has a message with the path of the problem file' do + expect(json_response['message']).to include('files/ruby/popen.rb') end it 'does not create a new commit' do diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb index d71932458fa..e667e93bea4 100644 --- a/spec/services/merge_requests/resolve_service_spec.rb +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -24,15 +24,26 @@ describe MergeRequests::ResolveService do end describe '#execute' do - context 'with valid params' do + context 'with section params' do let(:params) do { - sections: { - '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', - '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' - }, + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + sections: { + '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' + } + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + sections: { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ], commit_message: 'This is a commit message!' } end @@ -74,8 +85,96 @@ describe MergeRequests::ResolveService do end end - context 'when a resolution is missing' do - let(:invalid_params) { { sections: { '2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_14' => 'head' } } } + context 'with content and sections params' do + let(:popen_content) { "class Popen\nend" } + + let(:params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: popen_content + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + sections: { + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_21_21' => 'origin', + '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_49_49' => 'origin' + } + } + ], + commit_message: 'This is a commit message!' + } + end + + before do + MergeRequests::ResolveService.new(project, user, params).execute(merge_request) + end + + it 'creates a commit with the message' do + expect(merge_request.source_branch_head.message).to eq(params[:commit_message]) + end + + it 'creates a commit with the correct parents' do + expect(merge_request.source_branch_head.parents.map(&:id)). + to eq(['1450cd639e0bc6721eb02800169e464f212cde06', + '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + end + + it 'sets the content to the content given' do + blob = merge_request.source_project.repository.blob_at(merge_request.source_branch_head.sha, + 'files/ruby/popen.rb') + + expect(blob.data).to eq(popen_content) + end + end + + context 'when a resolution section is missing' do + let(:invalid_params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: '' + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + sections: { '6eb14e00385d2fb284765eb1cd8d420d33d63fc9_9_9' => 'head' } + } + ], + commit_message: 'This is a commit message!' + } + end + + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } + + it 'raises a MissingResolution error' do + expect { service.execute(merge_request) }. + to raise_error(Gitlab::Conflict::File::MissingResolution) + end + end + + context 'when the content of a file is unchanged' do + let(:invalid_params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: '' + }, { + old_path: 'files/ruby/regex.rb', + new_path: 'files/ruby/regex.rb', + content: merge_request.conflicts.file_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb').content + } + ], + commit_message: 'This is a commit message!' + } + end + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } it 'raises a MissingResolution error' do @@ -83,5 +182,27 @@ describe MergeRequests::ResolveService do to raise_error(Gitlab::Conflict::File::MissingResolution) end end + + context 'when a file is missing' do + let(:invalid_params) do + { + files: [ + { + old_path: 'files/ruby/popen.rb', + new_path: 'files/ruby/popen.rb', + content: '' + } + ], + commit_message: 'This is a commit message!' + } + end + + let(:service) { MergeRequests::ResolveService.new(project, user, invalid_params) } + + it 'raises a MissingFiles error' do + expect { service.execute(merge_request) }. + to raise_error(MergeRequests::ResolveService::MissingFiles) + end + end end end -- cgit v1.2.1 From 9727366b5a0a39a125925e2a7a78ed47e29b02f7 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:19:16 +0100 Subject: Make RuboCop happy --- spec/controllers/projects/merge_requests_controller_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 1311b4aa264..b4f637c93e2 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -678,7 +678,6 @@ describe Projects::MergeRequestsController do conflict_for_path('files/ruby/regex.rb') end - it 'returns a 404 status code' do expect(response).to have_http_status(:not_found) end -- cgit v1.2.1 From 241cca011f9a35c03e8f5fae1381a4af8a8f26bb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:20:29 +0100 Subject: Fix specs --- app/controllers/application_controller.rb | 4 ++-- spec/support/test_env.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf37421771f..81c0fa26d18 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -115,10 +115,10 @@ class ApplicationController < ActionController::Base def render_404 respond_to do |format| - format.json { head :not_found } - format.any do + format.html do render file: Rails.root.join("public", "404"), layout: false, status: "404" end + format.any { head :not_found } end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index d56274d0979..243d671c521 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -26,10 +26,10 @@ module TestEnv 'expand-collapse-lines' => '238e82d', 'video' => '8879059', 'crlf-diff' => '5938907', - 'conflict-start' => '75284c7', + 'conflict-start' => '824be60', 'conflict-resolvable' => '1450cd6', 'conflict-binary-file' => '259a6fb', - 'conflict-contains-conflict-markers' => '5e0964c', + 'conflict-contains-conflict-markers' => '78a3086', 'conflict-missing-side' => 'eb227b3', 'conflict-non-utf8' => 'd0a293c', 'conflict-too-large' => '39fa04f', -- cgit v1.2.1 From 082be0917cf15d410b1db3ca49b32049a56c117e Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 16:50:53 +0100 Subject: Fix MR model spec --- spec/models/merge_request_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5884b4cff8c..91a423b670c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1155,12 +1155,6 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey end - it 'returns a falsey value when the conflicts contain a file with ambiguous conflict markers' do - merge_request = create_merge_request('conflict-contains-conflict-markers') - - expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_falsey - end - it 'returns a falsey value when the conflicts contain a file edited in one branch and deleted in another' do merge_request = create_merge_request('conflict-missing-side') @@ -1172,6 +1166,12 @@ describe MergeRequest, models: true do expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy end + + it 'returns a truthy value when the conflicts have to be resolved in an editor' do + merge_request = create_merge_request('conflict-contains-conflict-markers') + + expect(merge_request.conflicts_can_be_resolved_in_ui?).to be_truthy + end end describe "#forked_source_project_missing?" do -- cgit v1.2.1 From 98dd958df3b4dd1b58b3a3387ea9d52dc78af46b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 1 Sep 2016 17:44:35 +0100 Subject: Fix resolve service specs --- spec/services/merge_requests/resolve_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb index e667e93bea4..388abb6a0df 100644 --- a/spec/services/merge_requests/resolve_service_spec.rb +++ b/spec/services/merge_requests/resolve_service_spec.rb @@ -60,7 +60,7 @@ describe MergeRequests::ResolveService do it 'creates a commit with the correct parents' do expect(merge_request.source_branch_head.parents.map(&:id)). to eq(['1450cd639e0bc6721eb02800169e464f212cde06', - '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + '824be604a34828eb682305f0d963056cfac87b2d']) end end @@ -120,7 +120,7 @@ describe MergeRequests::ResolveService do it 'creates a commit with the correct parents' do expect(merge_request.source_branch_head.parents.map(&:id)). to eq(['1450cd639e0bc6721eb02800169e464f212cde06', - '75284c70dd26c87f2a3fb65fd5a1f0b0138d3a6b']) + '824be604a34828eb682305f0d963056cfac87b2d']) end it 'sets the content to the content given' do -- cgit v1.2.1 From 7529bbae944823041e8690c011c4cfb39534074b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Sep 2016 16:16:54 +0100 Subject: Add JSON Schema --- .../projects/merge_requests_controller_spec.rb | 4 + spec/fixtures/api/schemas/conflicts.json | 137 +++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 spec/fixtures/api/schemas/conflicts.json diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index b4f637c93e2..06b37aa4997 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -597,6 +597,10 @@ describe Projects::MergeRequestsController do format: 'json' end + it 'matches the schema' do + expect(response).to match_response_schema('conflicts') + end + it 'includes meta info about the MR' do expect(json_response['commit_message']).to include('Merge branch') expect(json_response['commit_sha']).to match(/\h{40}/) diff --git a/spec/fixtures/api/schemas/conflicts.json b/spec/fixtures/api/schemas/conflicts.json new file mode 100644 index 00000000000..a947783d505 --- /dev/null +++ b/spec/fixtures/api/schemas/conflicts.json @@ -0,0 +1,137 @@ +{ + "type": "object", + "required": [ + "commit_message", + "commit_sha", + "source_branch", + "target_branch", + "files" + ], + "properties": { + "commit_message": {"type": "string"}, + "commit_sha": {"type": "string", "pattern": "^[0-9a-f]{40}$"}, + "source_branch": {"type": "string"}, + "target_branch": {"type": "string"}, + "files": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/conflict-text-with-sections" }, + { "$ref": "#/definitions/conflict-text-for-editor" } + ] + } + } + }, + "definitions": { + "conflict-base": { + "type": "object", + "required": [ + "old_path", + "new_path", + "blob_icon", + "blob_path" + ], + "properties": { + "old_path": {"type": "string"}, + "new_path": {"type": "string"}, + "blob_icon": {"type": "string"}, + "blob_path": {"type": "string"} + } + }, + "conflict-text-for-editor": { + "allOf": [ + {"$ref": "#/definitions/conflict-base"}, + { + "type": "object", + "required": [ + "type", + "content_path" + ], + "properties": { + "type": {"type": {"enum": ["text-editor"]}}, + "content_path": {"type": "string"} + } + } + ] + }, + "conflict-text-with-sections": { + "allOf": [ + {"$ref": "#/definitions/conflict-base"}, + { + "type": "object", + "required": [ + "type", + "content_path", + "sections" + ], + "properties": { + "type": {"type": {"enum": ["text"]}}, + "content_path": {"type": "string"}, + "sections": { + "type": "array", + "items": { + "oneOf": [ + { "$ref": "#/definitions/section-context" }, + { "$ref": "#/definitions/section-conflict" } + ] + } + } + } + } + ] + }, + "section-base": { + "type": "object", + "required": [ + "conflict", + "lines" + ], + "properties": { + "conflict": {"type": "boolean"}, + "lines": { + "type": "array", + "items": { + "type": "object", + "required": [ + "old_line", + "new_line", + "text", + "rich_text" + ], + "properties": { + "type": {"type": "string"}, + "old_line": {"type": "string"}, + "new_line": {"type": "string"}, + "text": {"type": "string"}, + "rich_text": {"type": "string"} + } + } + } + } + }, + "section-context": { + "allOf": [ + {"$ref": "#/definitions/section-base"}, + { + "type": "object", + "properties": { + "conflict": {"enum": [false]} + } + } + ] + }, + "section-conflict": { + "allOf": [ + {"$ref": "#/definitions/section-base"}, + { + "type": "object", + "required": ["id"], + "properties": { + "conflict": {"enum": [true]}, + "id": {"type": "string"} + } + } + ] + } + } +} -- cgit v1.2.1 From 4743d19463e7aef965665e43238af73820d18d7f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 7 Sep 2016 12:28:13 +0100 Subject: Simplify conflict file JSON creation --- lib/gitlab/conflict/file.rb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 26a9f170298..661e43d3fa9 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -9,7 +9,7 @@ module Gitlab CONTEXT_LINES = 3 - attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository, :type + attr_reader :merge_file_result, :their_path, :our_path, :our_mode, :merge_request, :repository def initialize(merge_file_result, conflict, merge_request:) @merge_file_result = merge_file_result @@ -25,6 +25,12 @@ module Gitlab merge_file_result[:data] end + def type + lines unless @type + + @type.inquiry + end + # Array of Gitlab::Diff::Line objects def lines return @lines if defined?(@lines) @@ -200,12 +206,14 @@ module Gitlab ::File.join(merge_request.diff_refs.head_sha, our_path)) } - if opts[:full_content] - json_hash.merge(content: content) - else - json_hash.merge!(sections: sections) if type == 'text' - - json_hash.merge(type: type, content_path: content_path) + json_hash.tap do |json_hash| + if opts[:full_content] + json_hash[:content] = content + else + json_hash[:sections] = sections if type.text? + json_hash[:type] = type + json_hash[:content_path] = content_path + end end end -- cgit v1.2.1 From 6af52d7d23cf9dbfcd58a2d3031ed19887f7a558 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 20 Sep 2016 09:16:35 +0300 Subject: We now support resolving conflicts with ambiguous markers --- spec/features/merge_requests/conflicts_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 759edf8ec80..1e8aeacea05 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -42,7 +42,6 @@ feature 'Merge request conflict resolution', js: true, feature: true do UNRESOLVABLE_CONFLICTS = { 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', - 'conflict-contains-conflict-markers' => 'when the conflicts contain a file with ambiguous conflict markers', 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another', 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file', } -- cgit v1.2.1 From 26f658decd3943bbc66c567ea91e7b859b32e0e6 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 8 Sep 2016 11:44:04 -0500 Subject: Implement editor to manually resolve merge conflicts --- .../merge_conflict_data_provider.js.es6 | 165 ++++++++++++++++----- .../javascripts/merge_conflict_resolver.js.es6 | 60 ++++++-- .../components/diff_file_editor.js.es6 | 63 ++++++++ app/assets/stylesheets/pages/merge_conflicts.scss | 26 ++++ .../projects/merge_requests/conflicts.html.haml | 3 + .../conflicts/_diff_file_editor.html.haml | 9 ++ .../conflicts/_file_actions.html.haml | 12 ++ .../conflicts/_inline_view.html.haml | 10 +- .../conflicts/_parallel_view.html.haml | 10 +- .../conflicts/_submit_form.html.haml | 9 +- .../components/_diff_file_editor.html.haml | 6 + 11 files changed, 300 insertions(+), 73 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 create mode 100644 app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_file_actions.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index 13ee794ba38..bfed78ff99c 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -2,7 +2,9 @@ const HEAD_HEADER_TEXT = 'HEAD//our changes'; const ORIGIN_HEADER_TEXT = 'origin//their changes'; const HEAD_BUTTON_TITLE = 'Use ours'; const ORIGIN_BUTTON_TITLE = 'Use theirs'; - +const INTERACTIVE_RESOLVE_MODE = 'interactive'; +const EDIT_RESOLVE_MODE = 'edit'; +const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; class MergeConflictDataProvider { @@ -18,8 +20,7 @@ class MergeConflictDataProvider { diffViewType : diffViewType, fixedLayout : fixedLayout, isSubmitting : false, - conflictsData : {}, - resolutionData : {} + conflictsData : {} } } @@ -35,9 +36,9 @@ class MergeConflictDataProvider { data.shortCommitSha = data.commit_sha.slice(0, 7); data.commitMessage = data.commit_message; + this.decorateFiles(data); this.setParallelLines(data); this.setInlineLines(data); - this.updateResolutionsData(data); } vueInstance.conflictsData = data; @@ -47,16 +48,12 @@ class MergeConflictDataProvider { vueInstance.conflictsData.conflictsText = conflictsText; } - - updateResolutionsData(data) { - const vi = this.vueInstance; - - data.files.forEach( (file) => { - file.sections.forEach( (section) => { - if (section.conflict) { - vi.$set(`resolutionData['${section.id}']`, false); - } - }); + decorateFiles(data) { + data.files.forEach((file) => { + file.content = ''; + file.resolutionData = {}; + file.promptDiscardConfirmation = false; + file.resolveMode = DEFAULT_RESOLVE_MODE; }); } @@ -165,11 +162,14 @@ class MergeConflictDataProvider { } - handleSelected(sectionId, selection) { + handleSelected(file, sectionId, selection) { const vi = this.vueInstance; + let files = vi.conflictsData.files; + + vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection); + - vi.resolutionData[sectionId] = selection; - vi.conflictsData.files.forEach( (file) => { + files.forEach( (file) => { file.inlineLines.forEach( (line) => { if (line.id === sectionId && (line.hasConflict || line.isHeader)) { this.markLine(line, selection); @@ -208,6 +208,48 @@ class MergeConflictDataProvider { .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); } + setFileResolveMode(file, mode) { + const vi = this.vueInstance; + + // Restore Interactive mode when switching to Edit mode + if (mode === EDIT_RESOLVE_MODE) { + file.resolutionData = {}; + + this.restoreFileLinesState(file); + } + + file.resolveMode = mode; + } + + + restoreFileLinesState(file) { + file.inlineLines.forEach((line) => { + if (line.hasConflict || line.isHeader) { + line.isSelected = false; + line.isUnselected = false; + } + }); + + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; + + if (isLeftMatch || isRightMatch) { + left.isSelected = false; + left.isUnselected = false; + right.isSelected = false; + right.isUnselected = false; + } + }); + } + + + setPromptConfirmationState(file, state) { + file.promptDiscardConfirmation = state; + } + markLine(line, selection) { if (selection === 'head' && line.isHead) { @@ -226,31 +268,54 @@ class MergeConflictDataProvider { getConflictsCount() { - return Object.keys(this.vueInstance.resolutionData).length; - } - - - getResolvedCount() { - let count = 0; - const data = this.vueInstance.resolutionData; + const files = this.vueInstance.conflictsData.files; + let count = 0; - for (const id in data) { - const resolution = data[id]; - if (resolution) { - count++; - } - } + files.forEach((file) => { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + }); return count; } isReadyToCommit() { - const { conflictsData, isSubmitting } = this.vueInstance - const allResolved = this.getConflictsCount() === this.getResolvedCount(); - const hasCommitMessage = $.trim(conflictsData.commitMessage).length; + const vi = this.vueInstance; + const files = this.vueInstance.conflictsData.files; + const hasCommitMessage = $.trim(this.vueInstance.conflictsData.commitMessage).length; + let unresolved = 0; + + for (let i = 0, l = files.length; i < l; i++) { + let file = files[i]; + + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + let numberConflicts = 0; + let resolvedConflicts = Object.keys(file.resolutionData).length + + for (let j = 0, k = file.sections.length; j < k; j++) { + if (file.sections[j].conflict) { + numberConflicts++; + } + } + + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + // Unlikely to happen since switching to Edit mode saves content automatically. + // Checking anyway in case the save strategy changes in the future + if (!file.content) { + unresolved++; + continue; + } + } + } - return !isSubmitting && hasCommitMessage && allResolved; + return !vi.isSubmitting && hasCommitMessage && !unresolved; } @@ -332,10 +397,33 @@ class MergeConflictDataProvider { getCommitData() { - return { - commit_message: this.vueInstance.conflictsData.commitMessage, - sections: this.vueInstance.resolutionData - } + let conflictsData = this.vueInstance.conflictsData; + let commitData = {}; + + commitData = { + commitMessage: conflictsData.commitMessage, + files: [] + }; + + conflictsData.files.forEach((file) => { + let addFile; + + addFile = { + old_path: file.old_path, + new_path: file.new_path + }; + + // Submit only one data for type of editing + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + addFile.sections = file.resolutionData; + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + addFile.content = file.content; + } + + commitData.files.push(addFile); + }); + + return commitData; } @@ -343,5 +431,4 @@ class MergeConflictDataProvider { const { old_path, new_path } = file; return old_path === new_path ? new_path : `${old_path} → ${new_path}`; } - } diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 index 7e756433bf5..c317a6521b5 100644 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ b/app/assets/javascripts/merge_conflict_resolver.js.es6 @@ -1,13 +1,16 @@ //= require vue +//= require ./merge_conflicts/components/diff_file_editor + +const INTERACTIVE_RESOLVE_MODE = 'interactive'; +const EDIT_RESOLVE_MODE = 'edit'; class MergeConflictResolver { constructor() { - this.dataProvider = new MergeConflictDataProvider() - this.initVue() + this.dataProvider = new MergeConflictDataProvider(); + this.initVue(); } - initVue() { const that = this; this.vue = new Vue({ @@ -17,15 +20,28 @@ class MergeConflictResolver { created : this.fetchData(), computed : this.setComputedProperties(), methods : { - handleSelected(sectionId, selection) { - that.dataProvider.handleSelected(sectionId, selection); + handleSelected(file, sectionId, selection) { + that.dataProvider.handleSelected(file, sectionId, selection); }, handleViewTypeChange(newType) { that.dataProvider.updateViewType(newType); }, commit() { that.commit(); - } + }, + onClickResolveModeButton(file, mode) { + that.toggleResolveMode(file, mode); + }, + acceptDiscardConfirmation(file) { + that.dataProvider.setPromptConfirmationState(file, false); + that.dataProvider.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); + }, + cancelDiscardConfirmation(file) { + that.dataProvider.setPromptConfirmationState(file, false); + }, + }, + components: { + 'diff-file-editor': window.gl.diffFileEditor } }) } @@ -36,7 +52,6 @@ class MergeConflictResolver { return { conflictsCount() { return dp.getConflictsCount() }, - resolvedCount() { return dp.getResolvedCount() }, readyToCommit() { return dp.isReadyToCommit() }, commitButtonText() { return dp.getCommitButtonText() } } @@ -69,14 +84,29 @@ class MergeConflictResolver { commit() { this.vue.isSubmitting = true; - $.post($('#conflicts').data('resolveConflictsPath'), this.dataProvider.getCommitData()) - .done((data) => { - window.location.href = data.redirect_to; - }) - .error(() => { - this.vue.isSubmitting = false; - new Flash('Something went wrong!'); - }); + $.ajax({ + url: $('#conflicts').data('resolveConflictsPath'), + data: JSON.stringify(this.dataProvider.getCommitData()), + contentType: "application/json", + dataType: 'json', + method: 'POST' + }) + .done((data) => { + window.location.href = data.redirect_to; + }) + .error(() => { + this.vue.isSubmitting = false; + new Flash('Something went wrong!'); + }); } + + toggleResolveMode(file, mode) { + if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { + this.dataProvider.setPromptConfirmationState(file, true); + return; + } + + this.dataProvider.setFileResolveMode(file, mode); + } } diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 new file mode 100644 index 00000000000..570d9ff877c --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -0,0 +1,63 @@ +((global) => { + global.diffFileEditor = Vue.extend({ + props: ['file', 'loadFile'], + template: '#diff-file-editor', + data() { + return { + originalState: '', + saved: false, + loading: false, + fileLoaded: false + } + }, + computed: { + classObject() { + return { + 'load-file': this.loadFile, + 'saved': this.saved, + 'is-loading': this.loading + }; + } + }, + watch: { + loadFile(val) { + const self = this; + + if (!val || this.fileLoaded || this.loading) { + return + } + + this.loading = true; + + $.get(this.file.content_path) + .done((file) => { + $(self.$el).find('textarea').val(file.content); + + self.originalState = file.content; + self.fileLoaded = true; + self.saveDiffResolution(); + }) + .fail(() => { + console.log('error'); + }) + .always(() => { + self.loading = false; + }); + } + }, + methods: { + saveDiffResolution() { + this.saved = true; + + // This probably be better placed in the data provider + this.file.content = this.$el.querySelector('textarea').value; + this.file.resolveEditChanged = this.file.content !== this.originalState; + this.file.promptDiscardConfirmation = false; + }, + onInput() { + this.saveDiffResolution(); + } + } + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5ec660799e3..577a97e8c0e 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -235,4 +235,30 @@ $colors: ( .btn-success .fa-spinner { color: #fff; } + + .editor-wrap { + &.is-loading { + .editor { + display: none; + } + + .loading-text { + display: block; + } + } + + &.saved { + .editor { + border-top: solid 1px green; + } + } + + .editor { + border-top: solid 1px yellow; + } + + .loading-text { + display: none; + } + } } diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index a524936f73c..e3add1f799f 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -27,3 +27,6 @@ = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/submit_form" + +-# Components += render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml new file mode 100644 index 00000000000..d0c518e5249 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -0,0 +1,9 @@ +- if_condition = local_assigns.fetch(:if_condition, '') + +.diff-editor-wrap{ "v-show" => if_condition } + .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } + %p + Are you sure to discard your changes? + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel + %diff-file-editor{":file" => "file", ":load-file" => if_condition } diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml new file mode 100644 index 00000000000..124dfde7b22 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -0,0 +1,12 @@ +.file-actions + .btn-group + %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", + '@click' => "onClickResolveModeButton(file, 'interactive')", + type: 'button' } + Interactive mode + %button.btn{ ':class' => "{ 'active': file.resolveMode == 'edit' }", + '@click' => "onClickResolveModeButton(file, 'edit')", + type: 'button' } + Edit inline + %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} + View file @{{conflictsData.shortCommitSha}} diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml index 19c7da4b5e3..7120b6ff48d 100644 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml @@ -3,12 +3,9 @@ .file-title %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} - .file-actions - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} - View file @{{conflictsData.shortCommitSha}} - + = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } %table %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} %template{"v-if" => "!line.isHeader"} @@ -24,5 +21,6 @@ %td.diff-line-num.header{":class" => class_bindings} %td.line_content.header{":class" => class_bindings} %strong {{{line.richText}}} - %button.btn{"@click" => "handleSelected(line.id, line.section)"} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } {{line.buttonTitle}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && !isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml index 2e6f67c2eaf..18c830ddb17 100644 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml @@ -3,12 +3,9 @@ .file-title %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} - .file-actions - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} - View file @{{conflictsData.shortCommitSha}} - + = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } %table %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} %template{"v-for" => "line in section"} @@ -17,7 +14,7 @@ %td.diff-line-num.header{":class" => class_bindings} %td.line_content.header{":class" => class_bindings} %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(line.id, line.section)"} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} {{line.buttonTitle}} %template{"v-if" => "!line.isHeader"} @@ -25,3 +22,4 @@ {{line.lineNumber}} %td.line_content.parallel{":class" => class_bindings} {{{line.richText}}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 78bd4133ea2..380f8722186 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,15 +1,10 @@ -.content-block.oneline-block.files-changed - %strong.resolved-count {{resolvedCount}} - of - %strong.total-count {{conflictsCount}} - conflicts have been resolved - +.content-block .commit-message-container.form-group .max-width-marker %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"} {{{conflictsData.commitMessage}}} - %button{type: "button", class: "btn btn-success js-submit-button", ":disabled" => "!readyToCommit", "@click" => "commit()"} + %button{type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } %span {{commitButtonText}} = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml new file mode 100644 index 00000000000..0556341fd64 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -0,0 +1,6 @@ +%template{ id: "diff-file-editor" } + %div + .editor-wrap{ ":class" => "classObject" } + %p.loading-text Loading... + .editor + %textarea{ "@input" => "onInput", cols: '80', rows: '20' } -- cgit v1.2.1 From a3eb39a1068df93d732585272c5eaff380801430 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 16 Sep 2016 19:41:15 -0500 Subject: Replace textarea with Ace editor --- .../components/diff_file_editor.js.es6 | 27 ++++++++++++++++------ app/assets/stylesheets/pages/merge_conflicts.scss | 7 ++++++ .../projects/merge_requests/conflicts.html.haml | 2 ++ .../components/_diff_file_editor.html.haml | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 570d9ff877c..abdf73febb4 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -4,7 +4,7 @@ template: '#diff-file-editor', data() { return { - originalState: '', + originalContent: '', saved: false, loading: false, fileLoaded: false @@ -23,6 +23,8 @@ loadFile(val) { const self = this; + this.resetEditorContent(); + if (!val || this.fileLoaded || this.loading) { return } @@ -31,10 +33,19 @@ $.get(this.file.content_path) .done((file) => { - $(self.$el).find('textarea').val(file.content); - self.originalState = file.content; + let content = self.$el.querySelector('pre'); + let fileContent = document.createTextNode(file.content); + + content.textContent = fileContent.textContent; + + self.originalContent = file.content; self.fileLoaded = true; + self.editor = ace.edit(content); + self.editor.$blockScrolling = Infinity; // Turn off annoying warning + self.editor.on('change', () => { + self.saveDiffResolution(); + }); self.saveDiffResolution(); }) .fail(() => { @@ -50,12 +61,14 @@ this.saved = true; // This probably be better placed in the data provider - this.file.content = this.$el.querySelector('textarea').value; - this.file.resolveEditChanged = this.file.content !== this.originalState; + this.file.content = this.editor.getValue(); + this.file.resolveEditChanged = this.file.content !== this.originalContent; this.file.promptDiscardConfirmation = false; }, - onInput() { - this.saveDiffResolution(); + resetEditorContent() { + if (this.fileLoaded) { + this.editor.setValue(this.originalContent, -1); + } } } }); diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 577a97e8c0e..2a8c693b2a8 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -255,6 +255,13 @@ $colors: ( .editor { border-top: solid 1px yellow; + + pre { + height: 350px; + border: none; + border-radius: 0; + margin-bottom: 0; + } } .loading-text { diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index e3add1f799f..ff641b90b86 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -6,6 +6,8 @@ 'unselected': line.isUnselected }" - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('lib/ace.js') = render "projects/merge_requests/show/mr_title" .merge-request-details.issuable-details diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 0556341fd64..940558e02e0 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -3,4 +3,4 @@ .editor-wrap{ ":class" => "classObject" } %p.loading-text Loading... .editor - %textarea{ "@input" => "onInput", cols: '80', rows: '20' } + %pre{ "style" => "height: 350px" } -- cgit v1.2.1 From 17830296a6143c4d76155449ff4da4377e263657 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 10:10:17 -0500 Subject: Styles for discard alert --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/merge_conflicts.scss | 16 +++++++++++++--- .../merge_requests/conflicts/_diff_file_editor.html.haml | 7 ++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 4c34ed3ebf7..7690d65de8e 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -56,6 +56,7 @@ $border-gray-light: #dcdcdc; $border-gray-normal: #d7d7d7; $border-gray-dark: #c6cacf; +$border-green-extra-light: #9adb84; $border-green-light: #2faa60; $border-green-normal: #2ca05b; $border-green-dark: #279654; diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 2a8c693b2a8..ea848b2c2c0 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -249,13 +249,11 @@ $colors: ( &.saved { .editor { - border-top: solid 1px green; + border-top: solid 2px $border-green-extra-light; } } .editor { - border-top: solid 1px yellow; - pre { height: 350px; border: none; @@ -268,4 +266,16 @@ $colors: ( display: none; } } + + .discard-changes-alert { + background-color: $background-color; + text-align: right; + padding: $gl-padding-top $gl-padding; + color: $gl-text-color; + + .discard-actions { + display: inline-block; + margin-left: 10px; + } + } } diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml index d0c518e5249..a335470de75 100644 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -2,8 +2,9 @@ .diff-editor-wrap{ "v-show" => if_condition } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } - %p + .discard-changes-alert Are you sure to discard your changes? - %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes - %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel + .discard-actions + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel %diff-file-editor{":file" => "file", ":load-file" => if_condition } -- cgit v1.2.1 From 02bfb0ff1bf53abf44c50d8310ad0856d26860cf Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 10:51:09 -0500 Subject: Style for resolve conflicts form --- app/assets/stylesheets/pages/merge_conflicts.scss | 4 ++++ .../conflicts/_submit_form.html.haml | 27 ++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index ea848b2c2c0..c851bd52b1a 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -278,4 +278,8 @@ $colors: ( margin-left: 10px; } } + + .resolve-conflicts-form { + padding-top: $gl-padding; + } } diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 380f8722186..fbb15c307c4 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,10 +1,17 @@ -.content-block - .commit-message-container.form-group - .max-width-marker - %textarea.form-control.js-commit-message{"v-model" => "conflictsData.commitMessage"} - {{{conflictsData.commitMessage}}} - - %button{type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } - %span {{commitButtonText}} - - = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" +.form-horizontal.resolve-conflicts-form + .form-group + %label.col-sm-2.control-label{ "for" => "commit-message" } + Commit message + .col-sm-10 + .commit-message-container + .max-width-marker + %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } + {{{conflictsData.commitMessage}}} + .form-group + .col-sm-offset-2.col-sm-10 + .row + .col-xs-6 + %button{ type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } + %span {{commitButtonText}} + .col-xs-6.text-right + = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" -- cgit v1.2.1 From 0402a18367a7a08d3f40f8b63b961a0e1abb345a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Sep 2016 11:04:52 -0500 Subject: Replace loading text with spinner --- app/assets/stylesheets/pages/merge_conflicts.scss | 4 ++-- .../merge_requests/conflicts/components/_diff_file_editor.html.haml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index c851bd52b1a..d447ca53c16 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -242,7 +242,7 @@ $colors: ( display: none; } - .loading-text { + .loading { display: block; } } @@ -262,7 +262,7 @@ $colors: ( } } - .loading-text { + .loading { display: none; } } diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 940558e02e0..9965bdf9028 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,6 +1,7 @@ %template{ id: "diff-file-editor" } %div .editor-wrap{ ":class" => "classObject" } - %p.loading-text Loading... + .loading + %i.fa.fa-spinner.fa-spin .editor %pre{ "style" => "height: 350px" } -- cgit v1.2.1 From 12ca16080b86b2b0cee8037800952347c2dcd8c4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 20 Sep 2016 12:37:53 -0500 Subject: check if files is set before counting --- app/assets/javascripts/merge_conflict_data_provider.js.es6 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index bfed78ff99c..6d1d3f36b33 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -268,6 +268,10 @@ class MergeConflictDataProvider { getConflictsCount() { + if (!this.vueInstance.conflictsData.files) { + return 0; + } + const files = this.vueInstance.conflictsData.files; let count = 0; -- cgit v1.2.1 From 4178ddee18918c5186ba75aaf9b303138fb97b30 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Sun, 25 Sep 2016 15:45:58 -0500 Subject: Add tests to check if files are resolved with Edit Inline mode --- spec/features/merge_requests/conflicts_spec.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 1e8aeacea05..d2057aa2fe7 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -36,6 +36,32 @@ feature 'Merge request conflict resolution', js: true, feature: true do retry end end + + context 'when in inline mode' do + it 'resolves files manually' do + within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");'); + end + + within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");'); + end + + click_button 'Commit conflict resolution' + wait_for_ajax + expect(page).to have_content('All merge conflicts were resolved') + + click_on 'Changes' + wait_for_ajax + + expect(page).to have_content('One morning') + expect(page).to have_content('Gregor Samsa woke from troubled dreams') + end + end end end -- cgit v1.2.1 From e84f959ae47e35eaebdc6c0adaf1e089326601ce Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 26 Sep 2016 15:49:04 +0100 Subject: Fix editor spec --- spec/features/merge_requests/conflicts_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index d2057aa2fe7..721360b207e 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -54,6 +54,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do click_button 'Commit conflict resolution' wait_for_ajax expect(page).to have_content('All merge conflicts were resolved') + merge_request.reload_diff click_on 'Changes' wait_for_ajax -- cgit v1.2.1 From a8ac9089afb664e569b34c61dc6782d20d1019d1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 28 Sep 2016 05:12:13 -0500 Subject: Refactor JS code - Use a store base object to manage application state. - Add a service to handle ajax requests. - Load code only when needed --- app/assets/javascripts/dispatcher.js | 3 - .../components/diff_file_editor.js.es6 | 5 +- .../merge_conflicts/merge_conflict_service.js.es6 | 30 ++ .../merge_conflicts/merge_conflict_store.js.es6 | 419 +++++++++++++++++++++ .../merge_conflicts/merge_conflicts_bundle.js.es6 | 84 +++++ .../projects/merge_requests/conflicts.html.haml | 3 +- .../conflicts/_commit_stats.html.haml | 6 +- .../conflicts/_submit_form.html.haml | 1 - config/application.rb | 1 + spec/features/merge_requests/conflicts_spec.rb | 4 +- 10 files changed, 545 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f3ef13ce20e..2c277766d2d 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -97,9 +97,6 @@ new ZenMode(); new MergedButtons(); break; - case "projects:merge_requests:conflicts": - window.mcui = new MergeConflictResolver() - break; case 'projects:merge_requests:index': shortcut_handler = new ShortcutsNavigation(); Issuable.init(); diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index abdf73febb4..49d30f6e9e8 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -1,5 +1,8 @@ ((global) => { - global.diffFileEditor = Vue.extend({ + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.diffFileEditor = Vue.extend({ props: ['file', 'loadFile'], template: '#diff-file-editor', data() { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 new file mode 100644 index 00000000000..da2fb8b1323 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 @@ -0,0 +1,30 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + class mergeConflictsService { + constructor(options) { + this.conflictsPath = options.conflictsPath; + this.resolveConflictsPath = options.resolveConflictsPath; + } + + fetchConflictsData() { + return $.ajax({ + dataType: 'json', + url: this.conflictsPath + }); + } + + submitResolveConflicts(data) { + return $.ajax({ + url: this.resolveConflictsPath, + data: JSON.stringify(data), + contentType: 'application/json', + dataType: 'json', + method: 'POST' + }); + } + }; + + global.mergeConflicts.mergeConflictsService = mergeConflictsService; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 new file mode 100644 index 00000000000..d35e6d8aed6 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -0,0 +1,419 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + const diffViewType = $.cookie('diff_view'); + const HEAD_HEADER_TEXT = 'HEAD//our changes'; + const ORIGIN_HEADER_TEXT = 'origin//their changes'; + const HEAD_BUTTON_TITLE = 'Use ours'; + const ORIGIN_BUTTON_TITLE = 'Use theirs'; + const INTERACTIVE_RESOLVE_MODE = 'interactive'; + const EDIT_RESOLVE_MODE = 'edit'; + const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; + const VIEW_TYPES = { + INLINE: 'inline', + PARALLEL: 'parallel' + }; + + global.mergeConflicts.mergeConflictsStore = { + state: { + isLoading: true, + hasError: false, + isSubmitting: false, + isParallel: diffViewType === VIEW_TYPES.PARALLEL, + diffViewType: diffViewType, + conflictsData: {} + }, + + setConflictsData(data) { + this.decorateFiles(data.files); + this.setInlineLines(data.files); + this.setParallelLines(data.files); + + this.state.conflictsData = { + files: data.files, + commitMessage: data.commit_message, + sourceBranch: data.source_branch, + targetBranch: data.target_branch, + commitMessage: data.commit_message, + shortCommitSha: data.commit_sha.slice(0, 7), + }; + }, + + decorateFiles(files) { + files.forEach((file) => { + file.content = ''; + file.resolutionData = {}; + file.promptDiscardConfirmation = false; + file.resolveMode = DEFAULT_RESOLVE_MODE; + }); + }, + + setInlineLines(files) { + files.forEach((file) => { + file.iconClass = `fa-${file.blob_icon}`; + file.blobPath = file.blob_path; + file.filePath = this.getFilePath(file); + file.inlineLines = []; + + file.sections.forEach((section) => { + let currentLineType = 'new'; + const { conflict, lines, id } = section; + + if (conflict) { + file.inlineLines.push(this.getHeadHeaderLine(id)); + } + + lines.forEach((line) => { + const { type } = line; + + if ((type === 'new' || type === 'old') && currentLineType !== type) { + currentLineType = type; + file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); + } + + this.decorateLineForInlineView(line, id, conflict); + file.inlineLines.push(line); + }) + + if (conflict) { + file.inlineLines.push(this.getOriginHeaderLine(id)); + } + }); + }); + }, + + setParallelLines(files) { + files.forEach((file) => { + file.filePath = this.getFilePath(file); + file.iconClass = `fa-${file.blob_icon}`; + file.blobPath = file.blob_path; + file.parallelLines = []; + const linesObj = { left: [], right: [] }; + + file.sections.forEach((section) => { + const { conflict, lines, id } = section; + + if (conflict) { + linesObj.left.push(this.getOriginHeaderLine(id)); + linesObj.right.push(this.getHeadHeaderLine(id)); + } + + lines.forEach((line) => { + const { type } = line; + + if (conflict) { + if (type === 'old') { + linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); + } + else if (type === 'new') { + linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); + } + } + else { + const lineType = type || 'context'; + + linesObj.left.push (this.getLineForParallelView(line, id, lineType)); + linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + } + }); + + this.checkLineLengths(linesObj); + }); + + for (let i = 0, len = linesObj.left.length; i < len; i++) { + file.parallelLines.push([ + linesObj.right[i], + linesObj.left[i] + ]); + } + + return file; + }); + }, + + setLoadingState(state) { + this.state.isLoading = state; + }, + + setErrorState(state) { + this.state.hasError = state; + }, + + setFailedRequest(message) { + console.log('setFailedRequest'); + this.state.hasError = true; + this.state.conflictsData.errorMessage = message; + }, + + getConflictsCount() { + if (!this.state.conflictsData.files) { + return 0; + } + + const files = this.state.conflictsData.files; + let count = 0; + + files.forEach((file) => { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + }); + + return count; + }, + + getConflictsCountText() { + const count = this.getConflictsCount(); + const text = count ? 'conflicts' : 'conflict'; + + return `${count} ${text}`; + }, + + setViewType(viewType) { + this.state.diffView = viewType; + this.state.isParallel = viewType === VIEW_TYPES.PARALLEL; + + $.cookie('diff_view', viewType, { + path: gon.relative_url_root || '/' + }); + }, + + getHeadHeaderLine(id) { + return { + id: id, + richText: HEAD_HEADER_TEXT, + buttonTitle: HEAD_BUTTON_TITLE, + type: 'new', + section: 'head', + isHeader: true, + isHead: true, + isSelected: false, + isUnselected: false + } + }, + + decorateLineForInlineView(line, id, conflict) { + const { type } = line; + line.id = id; + line.hasConflict = conflict; + line.isHead = type === 'new'; + line.isOrigin = type === 'old'; + line.hasMatch = type === 'match'; + line.richText = line.rich_text; + line.isSelected = false; + line.isUnselected = false; + }, + + getLineForParallelView(line, id, lineType, isHead) { + const { old_line, new_line, rich_text } = line; + const hasConflict = lineType === 'conflict'; + + return { + id, + lineType, + hasConflict, + isHead: hasConflict && isHead, + isOrigin: hasConflict && !isHead, + hasMatch: lineType === 'match', + lineNumber: isHead ? new_line : old_line, + section: isHead ? 'head' : 'origin', + richText: rich_text, + isSelected: false, + isUnselected: false + } + }, + + getOriginHeaderLine(id) { + return { + id: id, + richText: ORIGIN_HEADER_TEXT, + buttonTitle: ORIGIN_BUTTON_TITLE, + type: 'old', + section: 'origin', + isHeader: true, + isOrigin: true, + isSelected: false, + isUnselected: false + } + }, + + getFilePath(file) { + const { old_path, new_path } = file; + return old_path === new_path ? new_path : `${old_path} → ${new_path}`; + }, + + checkLineLengths(linesObj) { + let { left, right } = linesObj; + + if (left.length !== right.length) { + if (left.length > right.length) { + const diff = left.length - right.length; + for (let i = 0; i < diff; i++) { + right.push({ lineType: 'emptyLine', richText: '' }); + } + } + else { + const diff = right.length - left.length; + for (let i = 0; i < diff; i++) { + left.push({ lineType: 'emptyLine', richText: '' }); + } + } + } + }, + + setPromptConfirmationState(file, state) { + file.promptDiscardConfirmation = state; + }, + + setFileResolveMode(file, mode) { + // Restore Interactive mode when switching to Edit mode + if (mode === EDIT_RESOLVE_MODE) { + file.resolutionData = {}; + + this.restoreFileLinesState(file); + } + + file.resolveMode = mode; + }, + + restoreFileLinesState(file) { + file.inlineLines.forEach((line) => { + if (line.hasConflict || line.isHeader) { + line.isSelected = false; + line.isUnselected = false; + } + }); + + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; + + if (isLeftMatch || isRightMatch) { + left.isSelected = false; + left.isUnselected = false; + right.isSelected = false; + right.isUnselected = false; + } + }); + }, + + isReadyToCommit() { + const files = this.state.conflictsData.files; + const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length; + let unresolved = 0; + + for (let i = 0, l = files.length; i < l; i++) { + let file = files[i]; + + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + let numberConflicts = 0; + let resolvedConflicts = Object.keys(file.resolutionData).length + + for (let j = 0, k = file.sections.length; j < k; j++) { + if (file.sections[j].conflict) { + numberConflicts++; + } + } + + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + // Unlikely to happen since switching to Edit mode saves content automatically. + // Checking anyway in case the save strategy changes in the future + if (!file.content) { + unresolved++; + continue; + } + } + } + + return !this.state.isSubmitting && hasCommitMessage && !unresolved; + }, + + getCommitButtonText() { + const initial = 'Commit conflict resolution'; + const inProgress = 'Committing...'; + + return this.state ? this.state.isSubmitting ? inProgress : initial : initial; + }, + + getCommitData() { + let commitData = {}; + + commitData = { + commit_message: this.state.conflictsData.commitMessage, + files: [] + }; + + this.state.conflictsData.files.forEach((file) => { + let addFile; + + addFile = { + old_path: file.old_path, + new_path: file.new_path + }; + + // Submit only one data for type of editing + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + addFile.sections = file.resolutionData; + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + addFile.content = file.content; + } + + commitData.files.push(addFile); + }); + + return commitData; + }, + + handleSelected(file, sectionId, selection) { + Vue.set(file.resolutionData, sectionId, selection); + + this.state.conflictsData.files.forEach((file) => { + file.inlineLines.forEach((line) => { + if (line.id === sectionId && (line.hasConflict || line.isHeader)) { + this.markLine(line, selection); + } + }); + + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const hasSameId = right.id === sectionId || left.id === sectionId; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; + + if (hasSameId && (isLeftMatch || isRightMatch)) { + this.markLine(left, selection); + this.markLine(right, selection); + } + }) + }); + }, + + markLine(line, selection) { + if (selection === 'head' && line.isHead) { + line.isSelected = true; + line.isUnselected = false; + } + else if (selection === 'origin' && line.isOrigin) { + line.isSelected = true; + line.isUnselected = false; + } + else { + line.isSelected = false; + line.isUnselected = true; + } + }, + + setSubmitState(state) { + this.state.isSubmitting = state; + } + }; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 new file mode 100644 index 00000000000..b5123e22f7a --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -0,0 +1,84 @@ +//= require vue +//= require ./merge_conflict_store +//= require ./merge_conflict_service +//= require ./components/diff_file_editor + +$(() => { + const INTERACTIVE_RESOLVE_MODE = 'interactive'; + const $conflicts = $(document.getElementById('conflicts')); + const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; + const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({ + conflictsPath: $conflicts.data('conflictsPath'), + resolveConflictsPath: $conflicts.data('resolveConflictsPath') + }); + + gl.MergeConflictsResolverApp = new Vue({ + el: '#conflicts', + data: mergeConflictsStore.state, + components: { + 'diff-file-editor': gl.mergeConflicts.diffFileEditor + }, + computed: { + conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, + readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, + commitButtonText() { return mergeConflictsStore.getCommitButtonText() } + }, + created() { + mergeConflictsService + .fetchConflictsData() + .done((data) => { + if (data.type === 'error') { + mergeConflictsStore.setFailedRequest(data.message); + } else { + mergeConflictsStore.setConflictsData(data); + } + }) + .error(() => { + mergeConflictsStore.setFailedRequest(); + }) + .always(() => { + mergeConflictsStore.setLoadingState(false); + + this.$nextTick(() => { + $conflicts.find('.js-syntax-highlight').syntaxHighlight(); + }); + }); + }, + methods: { + handleSelected(file, sectionId, selection) { + mergeConflictsStore.handleSelected(file, sectionId, selection); + }, + handleViewTypeChange(viewType) { + mergeConflictsStore.setViewType(viewType); + }, + onClickResolveModeButton(file, mode) { + if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { + mergeConflictsStore.setPromptConfirmationState(file, true); + return; + } + + mergeConflictsStore.setFileResolveMode(file, mode); + }, + acceptDiscardConfirmation(file) { + mergeConflictsStore.setPromptConfirmationState(file, false); + mergeConflictsStore.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); + }, + cancelDiscardConfirmation(file) { + mergeConflictsStore.setPromptConfirmationState(file, false); + }, + commit() { + mergeConflictsStore.setSubmitState(true); + + mergeConflictsService + .submitResolveConflicts(mergeConflictsStore.getCommitData()) + .done((data) => { + window.location.href = data.redirect_to; + }) + .error(() => { + mergeConflictsStore.setSubmitState(false); + new Flash('Failed to save merge conflicts resolutions. Please try again!'); + }); + } + } + }) +}); diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index ff641b90b86..997f40c0588 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -7,6 +7,7 @@ - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - content_for :page_specific_javascripts do + = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js') = page_specific_javascript_tag('lib/ace.js') = render "projects/merge_requests/show/mr_title" @@ -26,8 +27,8 @@ = render partial: "projects/merge_requests/conflicts/commit_stats" .files-wrapper{"v-if" => "!isLoading && !hasError"} - = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } + = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } = render partial: "projects/merge_requests/conflicts/submit_form" -# Components diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 457c467fba9..a3831d5a34e 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -13,8 +13,8 @@ .js-toggle-container .commit-stat-summary Showing - %strong.cred {{conflictsCount}} {{conflictsData.conflictsText}} + %strong.cred {{conflictsCountText}} between - %strong {{conflictsData.source_branch}} + %strong {{conflictsData.sourceBranch}} and - %strong {{conflictsData.target_branch}} + %strong {{conflictsData.targetBranch}} diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index fbb15c307c4..6ffaa9ad4d2 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -6,7 +6,6 @@ .commit-message-container .max-width-marker %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } - {{{conflictsData.commitMessage}}} .form-group .col-sm-offset-2.col-sm-10 .row diff --git a/config/application.rb b/config/application.rb index 962ffe0708d..8a9c539cb43 100644 --- a/config/application.rb +++ b/config/application.rb @@ -89,6 +89,7 @@ module Gitlab config.assets.precompile << "profile/profile_bundle.js" config.assets.precompile << "diff_notes/diff_notes_bundle.js" config.assets.precompile << "boards/boards_bundle.js" + config.assets.precompile << "merge_conflicts/merge_conflicts_bundle.js" config.assets.precompile << "boards/test_utils/simulate_drag.js" config.assets.precompile << "blob_edit/blob_edit_bundle.js" config.assets.precompile << "snippet/snippet_bundle.js" diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 721360b207e..f2ff000486b 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -42,13 +42,13 @@ feature 'Merge request conflict resolution', js: true, feature: true do within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");'); + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");') end within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");'); + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end click_button 'Commit conflict resolution' -- cgit v1.2.1 From 197ac5ebbfd8ca7fbcb79776fd25ca0e213376c4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 29 Sep 2016 20:26:01 -0500 Subject: Ability to resolve conflicts for files with `text-editor` as conflict type --- .../components/diff_file_editor.js.es6 | 43 ++-- .../merge_conflicts/merge_conflict_store.js.es6 | 240 +++++++++++---------- .../conflicts/_diff_file_editor.html.haml | 6 +- .../conflicts/_file_actions.html.haml | 2 +- .../conflicts/_inline_view.html.haml | 41 ++-- .../conflicts/_parallel_view.html.haml | 2 +- spec/features/merge_requests/conflicts_spec.rb | 38 +++- 7 files changed, 213 insertions(+), 159 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 49d30f6e9e8..46aad9fe33c 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -7,10 +7,10 @@ template: '#diff-file-editor', data() { return { - originalContent: '', saved: false, loading: false, - fileLoaded: false + fileLoaded: false, + originalContent: '', } }, computed: { @@ -23,43 +23,48 @@ } }, watch: { - loadFile(val) { - const self = this; - + ['file.showEditor'](val) { this.resetEditorContent(); if (!val || this.fileLoaded || this.loading) { - return + return; } + this.loadEditor(); + } + }, + ready() { + if (this.file.loadEditor) { + this.loadEditor(); + } + }, + methods: { + loadEditor() { this.loading = true; $.get(this.file.content_path) .done((file) => { - - let content = self.$el.querySelector('pre'); + let content = this.$el.querySelector('pre'); let fileContent = document.createTextNode(file.content); content.textContent = fileContent.textContent; - self.originalContent = file.content; - self.fileLoaded = true; - self.editor = ace.edit(content); - self.editor.$blockScrolling = Infinity; // Turn off annoying warning - self.editor.on('change', () => { - self.saveDiffResolution(); + this.originalContent = file.content; + this.fileLoaded = true; + this.editor = ace.edit(content); + this.editor.$blockScrolling = Infinity; // Turn off annoying warning + this.editor.on('change', () => { + this.saveDiffResolution(); }); - self.saveDiffResolution(); + this.saveDiffResolution(); }) .fail(() => { console.log('error'); }) .always(() => { - self.loading = false; + this.loading = false; }); - } - }, - methods: { + }, saveDiffResolution() { this.saved = true; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index d35e6d8aed6..e1f862797f5 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -13,6 +13,10 @@ INLINE: 'inline', PARALLEL: 'parallel' }; + const CONFLICT_TYPES = { + TEXT: 'text', + TEXT_EDITOR: 'text-editor' + }; global.mergeConflicts.mergeConflictsStore = { state: { @@ -26,8 +30,6 @@ setConflictsData(data) { this.decorateFiles(data.files); - this.setInlineLines(data.files); - this.setParallelLines(data.files); this.state.conflictsData = { files: data.files, @@ -45,90 +47,90 @@ file.resolutionData = {}; file.promptDiscardConfirmation = false; file.resolveMode = DEFAULT_RESOLVE_MODE; - }); - }, - - setInlineLines(files) { - files.forEach((file) => { + file.filePath = this.getFilePath(file); file.iconClass = `fa-${file.blob_icon}`; file.blobPath = file.blob_path; - file.filePath = this.getFilePath(file); - file.inlineLines = []; - file.sections.forEach((section) => { - let currentLineType = 'new'; - const { conflict, lines, id } = section; + if (file.type === CONFLICT_TYPES.TEXT) { + file.showEditor = false; + file.loadEditor = false; - if (conflict) { - file.inlineLines.push(this.getHeadHeaderLine(id)); - } + this.setInlineLine(file); + this.setParallelLine(file); + } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) { + file.showEditor = true; + file.loadEditor = true; + } + }); + }, - lines.forEach((line) => { - const { type } = line; + setInlineLine(file) { + file.inlineLines = []; - if ((type === 'new' || type === 'old') && currentLineType !== type) { - currentLineType = type; - file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); - } + file.sections.forEach((section) => { + let currentLineType = 'new'; + const { conflict, lines, id } = section; + + if (conflict) { + file.inlineLines.push(this.getHeadHeaderLine(id)); + } - this.decorateLineForInlineView(line, id, conflict); - file.inlineLines.push(line); - }) + lines.forEach((line) => { + const { type } = line; - if (conflict) { - file.inlineLines.push(this.getOriginHeaderLine(id)); + if ((type === 'new' || type === 'old') && currentLineType !== type) { + currentLineType = type; + file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); } - }); + + this.decorateLineForInlineView(line, id, conflict); + file.inlineLines.push(line); + }) + + if (conflict) { + file.inlineLines.push(this.getOriginHeaderLine(id)); + } }); }, - setParallelLines(files) { - files.forEach((file) => { - file.filePath = this.getFilePath(file); - file.iconClass = `fa-${file.blob_icon}`; - file.blobPath = file.blob_path; - file.parallelLines = []; - const linesObj = { left: [], right: [] }; - - file.sections.forEach((section) => { - const { conflict, lines, id } = section; + setParallelLine(file) { + file.parallelLines = []; + const linesObj = { left: [], right: [] }; - if (conflict) { - linesObj.left.push(this.getOriginHeaderLine(id)); - linesObj.right.push(this.getHeadHeaderLine(id)); - } + file.sections.forEach((section) => { + const { conflict, lines, id } = section; - lines.forEach((line) => { - const { type } = line; + if (conflict) { + linesObj.left.push(this.getOriginHeaderLine(id)); + linesObj.right.push(this.getHeadHeaderLine(id)); + } - if (conflict) { - if (type === 'old') { - linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); - } - else if (type === 'new') { - linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); - } - } - else { - const lineType = type || 'context'; + lines.forEach((line) => { + const { type } = line; - linesObj.left.push (this.getLineForParallelView(line, id, lineType)); - linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + if (conflict) { + if (type === 'old') { + linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); + } else if (type === 'new') { + linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); } - }); + } else { + const lineType = type || 'context'; - this.checkLineLengths(linesObj); + linesObj.left.push (this.getLineForParallelView(line, id, lineType)); + linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); + } }); - for (let i = 0, len = linesObj.left.length; i < len; i++) { - file.parallelLines.push([ - linesObj.right[i], - linesObj.left[i] - ]); - } - - return file; + this.checkLineLengths(linesObj); }); + + for (let i = 0, len = linesObj.left.length; i < len; i++) { + file.parallelLines.push([ + linesObj.right[i], + linesObj.left[i] + ]); + } }, setLoadingState(state) { @@ -140,13 +142,12 @@ }, setFailedRequest(message) { - console.log('setFailedRequest'); this.state.hasError = true; this.state.conflictsData.errorMessage = message; }, getConflictsCount() { - if (!this.state.conflictsData.files) { + if (!this.state.conflictsData.files.length) { return 0; } @@ -154,11 +155,15 @@ let count = 0; files.forEach((file) => { - file.sections.forEach((section) => { - if (section.conflict) { - count++; - } - }); + if (file.type === CONFLICT_TYPES.TEXT) { + file.sections.forEach((section) => { + if (section.conflict) { + count++; + } + }); + } else { + count++; + } }); return count; @@ -172,7 +177,7 @@ }, setViewType(viewType) { - this.state.diffView = viewType; + this.state.diffView = viewType; this.state.isParallel = viewType === VIEW_TYPES.PARALLEL; $.cookie('diff_view', viewType, { @@ -253,8 +258,7 @@ for (let i = 0; i < diff; i++) { right.push({ lineType: 'emptyLine', richText: '' }); } - } - else { + } else { const diff = right.length - left.length; for (let i = 0; i < diff; i++) { left.push({ lineType: 'emptyLine', richText: '' }); @@ -268,8 +272,12 @@ }, setFileResolveMode(file, mode) { - // Restore Interactive mode when switching to Edit mode - if (mode === EDIT_RESOLVE_MODE) { + if (mode === INTERACTIVE_RESOLVE_MODE) { + file.showEditor = false; + } else if (mode === EDIT_RESOLVE_MODE) { + // Restore Interactive mode when switching to Edit mode + file.showEditor = true; + file.loadEditor = true; file.resolutionData = {}; this.restoreFileLinesState(file); @@ -287,9 +295,9 @@ }); file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const isLeftMatch = left.hasConflict || left.isHeader; + const left = lines[0]; + const right = lines[1]; + const isLeftMatch = left.hasConflict || left.isHeader; const isRightMatch = right.hasConflict || right.isHeader; if (isLeftMatch || isRightMatch) { @@ -313,14 +321,17 @@ let numberConflicts = 0; let resolvedConflicts = Object.keys(file.resolutionData).length - for (let j = 0, k = file.sections.length; j < k; j++) { - if (file.sections[j].conflict) { - numberConflicts++; + // We only check if + if (file.type === CONFLICT_TYPES.TEXT) { + for (let j = 0, k = file.sections.length; j < k; j++) { + if (file.sections[j].conflict) { + numberConflicts++; + } } - } - if (resolvedConflicts !== numberConflicts) { - unresolved++; + if (resolvedConflicts !== numberConflicts) { + unresolved++; + } } } else if (file.resolveMode === EDIT_RESOLVE_MODE) { // Unlikely to happen since switching to Edit mode saves content automatically. @@ -358,10 +369,15 @@ new_path: file.new_path }; - // Submit only one data for type of editing - if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { - addFile.sections = file.resolutionData; - } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + if (file.type === CONFLICT_TYPES.TEXT) { + + // Submit only one data for type of editing + if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { + addFile.sections = file.resolutionData; + } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + addFile.content = file.content; + } + } else if (file.type === CONFLICT_TYPES.TEXT_EDITOR) { addFile.content = file.content; } @@ -374,39 +390,35 @@ handleSelected(file, sectionId, selection) { Vue.set(file.resolutionData, sectionId, selection); - this.state.conflictsData.files.forEach((file) => { - file.inlineLines.forEach((line) => { - if (line.id === sectionId && (line.hasConflict || line.isHeader)) { - this.markLine(line, selection); - } - }); + file.inlineLines.forEach((line) => { + if (line.id === sectionId && (line.hasConflict || line.isHeader)) { + this.markLine(line, selection); + } + }); - file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const hasSameId = right.id === sectionId || left.id === sectionId; - const isLeftMatch = left.hasConflict || left.isHeader; - const isRightMatch = right.hasConflict || right.isHeader; + file.parallelLines.forEach((lines) => { + const left = lines[0]; + const right = lines[1]; + const hasSameId = right.id === sectionId || left.id === sectionId; + const isLeftMatch = left.hasConflict || left.isHeader; + const isRightMatch = right.hasConflict || right.isHeader; - if (hasSameId && (isLeftMatch || isRightMatch)) { - this.markLine(left, selection); - this.markLine(right, selection); - } - }) + if (hasSameId && (isLeftMatch || isRightMatch)) { + this.markLine(left, selection); + this.markLine(right, selection); + } }); }, markLine(line, selection) { if (selection === 'head' && line.isHead) { - line.isSelected = true; + line.isSelected = true; line.isUnselected = false; - } - else if (selection === 'origin' && line.isOrigin) { - line.isSelected = true; + } else if (selection === 'origin' && line.isOrigin) { + line.isSelected = true; line.isUnselected = false; - } - else { - line.isSelected = false; + } else { + line.isSelected = false; line.isUnselected = true; } }, diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml index a335470de75..497db7e3f9b 100644 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml @@ -1,10 +1,8 @@ -- if_condition = local_assigns.fetch(:if_condition, '') - -.diff-editor-wrap{ "v-show" => if_condition } +.diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert Are you sure to discard your changes? .discard-actions %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel - %diff-file-editor{":file" => "file", ":load-file" => if_condition } + %diff-file-editor{":file" => "file"} diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml index 124dfde7b22..05af57acf03 100644 --- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -1,5 +1,5 @@ .file-actions - .btn-group + .btn-group{"v-if" => "file.type === 'text'"} %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", '@click' => "onClickResolveModeButton(file, 'interactive')", type: 'button' } diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml index 7120b6ff48d..60ac21d26c3 100644 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml @@ -4,23 +4,26 @@ %i.fa.fa-fw{":class" => "file.iconClass"} %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} + %template{"v-if" => "file.type === 'text'"} + .diff-content.diff-wrap-lines + .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.new_line{":class" => class_bindings} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => class_bindings} + %a {{line.old_line}} + %td.line_content{":class" => class_bindings} + {{{line.richText}}} - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && !isParallel" } + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor' + %template{"v-else" => true} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml index 18c830ddb17..7ed1485fc01 100644 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml @@ -22,4 +22,4 @@ {{line.lineNumber}} %td.line_content.parallel{":class" => class_bindings} {{{line.richText}}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.resolveMode === 'edit' && isParallel" } + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index f2ff000486b..0e5507a0210 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -37,7 +37,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do end end - context 'when in inline mode' do + context 'when in inline mode' do it 'resolves files manually' do within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do click_button 'Edit inline' @@ -66,6 +66,42 @@ feature 'Merge request conflict resolution', js: true, feature: true do end end + context 'when a merge request can be resolved in the UI' do + let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') } + + before do + project.team << [user, :developer] + login_as(user) + + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'a conflict contain markers' do + before { click_link('conflicts', href: /\/conflicts\Z/) } + + it 'resolves files manually' do + within find('.files-wrapper .diff-file.inline-view', text: 'files/markdown/ruby-style-guide.md') do + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') + end + + click_button 'Commit conflict resolution' + wait_for_ajax + + expect(page).to have_content('All merge conflicts were resolved') + + merge_request.reload_diff + + click_on 'Changes' + wait_for_ajax + find('.nothing-here-block', visible: true).click + wait_for_ajax + + expect(page).to have_content('Gregor Samsa woke from troubled dreams') + end + end + end + UNRESOLVABLE_CONFLICTS = { 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', -- cgit v1.2.1 From cebad0fb60c90ff64cf16b022d262381aff5bf4f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 30 Sep 2016 16:23:38 -0500 Subject: Do not show Diff view switcher if all files are can be only resolved with an editor --- .../javascripts/merge_conflicts/merge_conflict_store.js.es6 | 12 ++++++++++++ .../merge_conflicts/merge_conflicts_bundle.js.es6 | 3 ++- .../merge_requests/conflicts/_commit_stats.html.haml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index e1f862797f5..e31cac49d44 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -425,6 +425,18 @@ setSubmitState(state) { this.state.isSubmitting = state; + }, + + fileTextTypePresent() { + let found = false; + + this.state.conflictsData.files.find((f) => { + if (f.type === CONFLICT_TYPES.TEXT) { + return found = true; + } + }); + + return found; } }; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index b5123e22f7a..449b4d2209a 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -21,7 +21,8 @@ $(() => { computed: { conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, readyToCommit() { return mergeConflictsStore.isReadyToCommit() }, - commitButtonText() { return mergeConflictsStore.getCommitButtonText() } + commitButtonText() { return mergeConflictsStore.getCommitButtonText() }, + showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() } }, created() { mergeConflictsService diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index a3831d5a34e..7222b8ae4d3 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,5 +1,5 @@ .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} - .inline-parallel-buttons + .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} .btn-group %a.btn{ | ":class" => "{'active': !isParallel}", | -- cgit v1.2.1 From f947972dede2f8d68215555e4b4fd6e90a6bc437 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 30 Sep 2016 17:42:36 -0500 Subject: Improve diff view switching and components --- .../projects/merge_requests/conflicts.html.haml | 17 +++++++++++-- .../conflicts/_inline_view.html.haml | 29 ---------------------- .../conflicts/_parallel_view.html.haml | 25 ------------------- .../_resolve_mode_interactive_inline.html.haml | 17 +++++++++++++ .../_resolve_mode_interactive_parallel.html.haml | 17 +++++++++++++ spec/features/merge_requests/conflicts_spec.rb | 10 ++++---- 6 files changed, 54 insertions(+), 61 deletions(-) delete mode 100644 app/views/projects/merge_requests/conflicts/_inline_view.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_parallel_view.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 997f40c0588..5ff7befe0ca 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -27,9 +27,22 @@ = render partial: "projects/merge_requests/conflicts/commit_stats" .files-wrapper{"v-if" => "!isLoading && !hasError"} - = render partial: "projects/merge_requests/conflicts/inline_view", locals: { class_bindings: class_bindings } - = render partial: "projects/merge_requests/conflicts/parallel_view", locals: { class_bindings: class_bindings } + .files + .diff-file.file-holder.conflict{"v-for" => "file in conflictsData.files"} + .file-title + %i.fa.fa-fw{":class" => "file.iconClass"} + %strong {{file.filePath}} + = render partial: 'projects/merge_requests/conflicts/file_actions' + .diff-content.diff-wrap-lines + %div{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_inline", locals: { class_bindings: class_bindings } + %div{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_parallel", locals: { class_bindings: class_bindings } + %div{"v-show" => " file.resolveMode === 'edit' || file.type === 'text-editor'"} + = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } + = render partial: "projects/merge_requests/conflicts/submit_form" -# Components = render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' + \ No newline at end of file diff --git a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml b/app/views/projects/merge_requests/conflicts/_inline_view.html.haml deleted file mode 100644 index 60ac21d26c3..00000000000 --- a/app/views/projects/merge_requests/conflicts/_inline_view.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -.files{"v-show" => "!isParallel"} - .diff-file.file-holder.conflict.inline-view{"v-for" => "file in conflictsData.files"} - .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - %template{"v-if" => "file.type === 'text'"} - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor' - %template{"v-else" => true} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor' diff --git a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml b/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml deleted file mode 100644 index 7ed1485fc01..00000000000 --- a/app/views/projects/merge_requests/conflicts/_parallel_view.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -.files{"v-show" => "isParallel"} - .diff-file.file-holder.conflict.parallel-view{"v-for" => "file in conflictsData.files"} - .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} - %strong {{file.filePath}} - = render partial: 'projects/merge_requests/conflicts/file_actions' - .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ 'v-show' => "file.resolveMode === 'interactive'" } - %table - %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} - %template{"v-for" => "line in section"} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} - {{line.buttonTitle}} - - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.old_line{":class" => class_bindings} - {{line.lineNumber}} - %td.line_content.parallel{":class" => class_bindings} - {{{line.richText}}} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml new file mode 100644 index 00000000000..b1d7a90eb74 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml @@ -0,0 +1,17 @@ +.diff-wrap-lines.code.file-content.js-syntax-highlight + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.new_line{":class" => class_bindings} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => class_bindings} + %a {{line.old_line}} + %td.line_content{":class" => class_bindings} + {{{line.richText}}} + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml new file mode 100644 index 00000000000..f5af347f59b --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml @@ -0,0 +1,17 @@ +.diff-wrap-lines.code.file-content.js-syntax-highlight + %table + %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} + %template{"v-for" => "line in section"} + + %template{"v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => class_bindings} + %td.line_content.header{":class" => class_bindings} + %strong {{line.richText}} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} + {{line.buttonTitle}} + + %template{"v-if" => "!line.isHeader"} + %td.diff-line-num.old_line{":class" => class_bindings} + {{line.lineNumber}} + %td.line_content.parallel{":class" => class_bindings} + {{{line.richText}}} diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 0e5507a0210..4cecc22aa6c 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -39,16 +39,16 @@ feature 'Merge request conflict resolution', js: true, feature: true do context 'when in inline mode' do it 'resolves files manually' do - within find('.files-wrapper .diff-file.inline-view', text: 'files/ruby/popen.rb') do + within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("One morning");') + execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') end within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do click_button 'Edit inline' wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') + execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end click_button 'Commit conflict resolution' @@ -80,9 +80,9 @@ feature 'Merge request conflict resolution', js: true, feature: true do before { click_link('conflicts', href: /\/conflicts\Z/) } it 'resolves files manually' do - within find('.files-wrapper .diff-file.inline-view', text: 'files/markdown/ruby-style-guide.md') do + within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file.inline-view pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') + execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') end click_button 'Commit conflict resolution' -- cgit v1.2.1 From 4f76ff0ecd0d3b278f9722042b66d1767078ddd0 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 4 Oct 2016 03:24:44 -0500 Subject: Use .some instead of .find for phantomjs compatibility --- .../javascripts/merge_conflicts/merge_conflict_store.js.es6 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index e31cac49d44..bdd6753f66a 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -428,15 +428,7 @@ }, fileTextTypePresent() { - let found = false; - - this.state.conflictsData.files.find((f) => { - if (f.type === CONFLICT_TYPES.TEXT) { - return found = true; - } - }); - - return found; + return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT); } }; -- cgit v1.2.1 From c4142cf9c0c0b217034c60a0a973d2e96b17a428 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 07:57:22 -0500 Subject: Improve components for PhantomJs compatibility --- .../components/diff_file_editor.js.es6 | 14 ++++++++++--- .../components/inline_conflict_lines.js.es6 | 12 +++++++++++ .../components/parallel_conflict_line.js.es6 | 14 +++++++++++++ .../components/parallel_conflict_lines.js.es6 | 15 ++++++++++++++ .../merge_conflicts/merge_conflict_store.js.es6 | 6 +++--- .../merge_conflicts/merge_conflicts_bundle.js.es6 | 12 +++++++---- .../mixins/line_conflict_actions.js.es6 | 12 +++++++++++ .../mixins/line_conflict_utils.js.es6 | 18 ++++++++++++++++ .../projects/merge_requests/conflicts.html.haml | 24 ++++++++-------------- .../conflicts/_commit_stats.html.haml | 8 ++------ .../conflicts/_diff_file_editor.html.haml | 8 -------- .../_resolve_mode_interactive_inline.html.haml | 17 --------------- .../_resolve_mode_interactive_parallel.html.haml | 17 --------------- .../components/_diff_file_editor.html.haml | 10 +++++++-- .../components/_inline_conflict_lines.html.haml | 15 ++++++++++++++ .../components/_parallel_conflict_line.html.haml | 10 +++++++++ .../components/_parallel_conflict_lines.html.haml | 4 ++++ 17 files changed, 140 insertions(+), 76 deletions(-) create mode 100644 app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 create mode 100644 app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 delete mode 100644 app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml delete mode 100644 app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml create mode 100644 app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 46aad9fe33c..417095a8db7 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -3,8 +3,11 @@ global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts.diffFileEditor = Vue.extend({ - props: ['file', 'loadFile'], - template: '#diff-file-editor', + props: { + file: Object, + onCancelDiscardConfirmation: Function, + onAcceptDiscardConfirmation: Function + }, data() { return { saved: false, @@ -16,7 +19,6 @@ computed: { classObject() { return { - 'load-file': this.loadFile, 'saved': this.saved, 'is-loading': this.loading }; @@ -77,6 +79,12 @@ if (this.fileLoaded) { this.editor.setValue(this.originalContent, -1); } + }, + cancelDiscardConfirmation(file) { + this.onCancelDiscardConfirmation(file); + }, + acceptDiscardConfirmation(file) { + this.onAcceptDiscardConfirmation(file); } } }); diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 new file mode 100644 index 00000000000..b4be1c8988d --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 @@ -0,0 +1,12 @@ +((global) => { + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.inlineConflictLines = Vue.extend({ + props: { + file: Object + }, + mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions], + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 new file mode 100644 index 00000000000..8b0a8ab2073 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_line.js.es6 @@ -0,0 +1,14 @@ +((global) => { + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.parallelConflictLine = Vue.extend({ + props: { + file: Object, + line: Object + }, + mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions], + template: '#parallel-conflict-line' + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 new file mode 100644 index 00000000000..eb4cc6a9dac --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 @@ -0,0 +1,15 @@ +((global) => { + + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.parallelConflictLines = Vue.extend({ + props: { + file: Object + }, + mixins: [global.mergeConflicts.utils], + components: { + 'parallel-conflict-line': gl.mergeConflicts.parallelConflictLine + } + }); + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index bdd6753f66a..83bcc3f51aa 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -196,7 +196,7 @@ isHead: true, isSelected: false, isUnselected: false - } + }; }, decorateLineForInlineView(line, id, conflict) { @@ -227,7 +227,7 @@ richText: rich_text, isSelected: false, isUnselected: false - } + }; }, getOriginHeaderLine(id) { @@ -241,7 +241,7 @@ isOrigin: true, isSelected: false, isUnselected: false - } + }; }, getFilePath(file) { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 449b4d2209a..12688365f19 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -1,7 +1,12 @@ //= require vue //= require ./merge_conflict_store //= require ./merge_conflict_service +//= require ./mixins/line_conflict_utils +//= require ./mixins/line_conflict_actions //= require ./components/diff_file_editor +//= require ./components/inline_conflict_lines +//= require ./components/parallel_conflict_line +//= require ./components/parallel_conflict_lines $(() => { const INTERACTIVE_RESOLVE_MODE = 'interactive'; @@ -16,7 +21,9 @@ $(() => { el: '#conflicts', data: mergeConflictsStore.state, components: { - 'diff-file-editor': gl.mergeConflicts.diffFileEditor + 'diff-file-editor': gl.mergeConflicts.diffFileEditor, + 'inline-conflict-lines': gl.mergeConflicts.inlineConflictLines, + 'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines }, computed: { conflictsCountText() { return mergeConflictsStore.getConflictsCountText() }, @@ -46,9 +53,6 @@ $(() => { }); }, methods: { - handleSelected(file, sectionId, selection) { - mergeConflictsStore.handleSelected(file, sectionId, selection); - }, handleViewTypeChange(viewType) { mergeConflictsStore.setViewType(viewType); }, diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 new file mode 100644 index 00000000000..114a2c5b305 --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 @@ -0,0 +1,12 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.actions = { + methods: { + handleSelected(file, sectionId, selection) { + gl.mergeConflicts.mergeConflictsStore.handleSelected(file, sectionId, selection); + } + } + }; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 new file mode 100644 index 00000000000..b846a90ab2a --- /dev/null +++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 @@ -0,0 +1,18 @@ +((global) => { + global.mergeConflicts = global.mergeConflicts || {}; + + global.mergeConflicts.utils = { + methods: { + lineCssClass(line) { + return { + 'head': line.isHead, + 'origin': line.isOrigin, + 'match': line.hasMatch, + 'selected': line.isSelected, + 'unselected': line.isUnselected + }; + } + } + }; + +})(window.gl || (window.gl = {})); diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 5ff7befe0ca..d9f74d2cbfb 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -1,10 +1,3 @@ -- class_bindings = "{ | - 'head': line.isHead, | - 'origin': line.isOrigin, | - 'match': line.hasMatch, | - 'selected': line.isSelected, | - 'unselected': line.isUnselected }" - - page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" - content_for :page_specific_javascripts do = page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js') @@ -34,15 +27,14 @@ %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - %div{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_inline", locals: { class_bindings: class_bindings } - %div{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } - = render partial: "projects/merge_requests/conflicts/resolve_mode_interactive_parallel", locals: { class_bindings: class_bindings } - %div{"v-show" => " file.resolveMode === 'edit' || file.type === 'text-editor'"} - = render partial: 'projects/merge_requests/conflicts/diff_file_editor', locals: { if_condition: "file.loadFile && isParallel" } + .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" + .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + = render partial: "projects/merge_requests/conflicts/components/parallel_conflict_lines" + %div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"} + = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" = render partial: "projects/merge_requests/conflicts/submit_form" --# Components -= render partial: 'projects/merge_requests/conflicts/components/diff_file_editor' - \ No newline at end of file +-# Components += render partial: 'projects/merge_requests/conflicts/components/parallel_conflict_line' diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 7222b8ae4d3..5ab3cd96163 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,13 +1,9 @@ .content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} .btn-group - %a.btn{ | - ":class" => "{'active': !isParallel}", | - "@click" => "handleViewTypeChange('inline')"} + %button.btn{":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')"} Inline - %a.btn{ | - ":class" => "{'active': isParallel}", | - "@click" => "handleViewTypeChange('parallel')"} + %button.btn{":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')"} Side-by-side .js-toggle-container diff --git a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml deleted file mode 100644 index 497db7e3f9b..00000000000 --- a/app/views/projects/merge_requests/conflicts/_diff_file_editor.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -.diff-editor-wrap{ "v-show" => "file.showEditor" } - .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } - .discard-changes-alert - Are you sure to discard your changes? - .discard-actions - %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes - %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel - %diff-file-editor{":file" => "file"} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml deleted file mode 100644 index b1d7a90eb74..00000000000 --- a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_inline.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.diff-wrap-lines.code.file-content.js-syntax-highlight - %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.new_line{":class" => class_bindings} - %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => class_bindings} - %a {{line.old_line}} - %td.line_content{":class" => class_bindings} - {{{line.richText}}} - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{{line.richText}}} - %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } - {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml b/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml deleted file mode 100644 index f5af347f59b..00000000000 --- a/app/views/projects/merge_requests/conflicts/_resolve_mode_interactive_parallel.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -.diff-wrap-lines.code.file-content.js-syntax-highlight - %table - %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} - %template{"v-for" => "line in section"} - - %template{"v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => class_bindings} - %td.line_content.header{":class" => class_bindings} - %strong {{line.richText}} - %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} - {{line.buttonTitle}} - - %template{"v-if" => "!line.isHeader"} - %td.diff-line-num.old_line{":class" => class_bindings} - {{line.lineNumber}} - %td.line_content.parallel{":class" => class_bindings} - {{{line.richText}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 9965bdf9028..943ae6ee129 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,5 +1,11 @@ -%template{ id: "diff-file-editor" } - %div +%diff-file-editor{"inline-template" => "true", ":file" => "file", ":on-cancel-discard-confirmation" => "cancelDiscardConfirmation", ":on-accept-discard-confirmation" => "acceptDiscardConfirmation"} + .diff-editor-wrap{ "v-show" => "file.showEditor" } + .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } + .discard-changes-alert + Are you sure to discard your changes? + .discard-actions + %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes + %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel .editor-wrap{ ":class" => "classObject" } .loading %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml new file mode 100644 index 00000000000..f094df7fcaa --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml @@ -0,0 +1,15 @@ +%inline-conflict-lines{ "inline-template" => "true", ":file" => "file"} + %table + %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} + %td.diff-line-num.new_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %a {{line.new_line}} + %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %a {{line.old_line}} + %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{{line.richText}}} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %strong {{{line.richText}}} + %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } + {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml new file mode 100644 index 00000000000..5690bf7419c --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_line.html.haml @@ -0,0 +1,10 @@ +%script{"id" => 'parallel-conflict-line', "type" => "text/x-template"} + %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} + %strong {{line.richText}} + %button.btn{"@click" => "handleSelected(file, line.id, line.section)"} + {{line.buttonTitle}} + %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{line.lineNumber}} + %td.line_content.parallel{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + {{{line.richText}}} diff --git a/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml new file mode 100644 index 00000000000..a8ecdf59393 --- /dev/null +++ b/app/views/projects/merge_requests/conflicts/components/_parallel_conflict_lines.html.haml @@ -0,0 +1,4 @@ +%parallel-conflict-lines{"inline-template" => "true", ":file" => "file"} + %table + %tr.line_holder.parallel{"v-for" => "section in file.parallelLines"} + %td{"is"=>"parallel-conflict-line", "v-for" => "line in section", ":line" => "line", ":file" => "file"} -- cgit v1.2.1 From 54bfe70795e289b86485b2a57d72b6711e4994bd Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 07:57:57 -0500 Subject: Add more tests to check conflicts resolution --- .../merge_conflict_data_provider.js.es6 | 2 +- .../merge_conflicts/merge_conflict_store.js.es6 | 4 +- .../projects/merge_requests_controller_spec.rb | 1 + spec/features/merge_requests/conflicts_spec.rb | 145 ++++++++++++++------- 4 files changed, 101 insertions(+), 51 deletions(-) diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 index 6d1d3f36b33..5877d2f1896 100644 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6 @@ -290,7 +290,7 @@ class MergeConflictDataProvider { isReadyToCommit() { const vi = this.vueInstance; const files = this.vueInstance.conflictsData.files; - const hasCommitMessage = $.trim(this.vueInstance.conflictsData.commitMessage).length; + const hasCommitMessage = this.vueInstance.conflictsData.commitMessage.trim(); let unresolved = 0; for (let i = 0, l = files.length; i < l; i++) { diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 index 83bcc3f51aa..5c5c65f29d4 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 @@ -321,7 +321,8 @@ let numberConflicts = 0; let resolvedConflicts = Object.keys(file.resolutionData).length - // We only check if + // We only check for conflicts type 'text' + // since conflicts `text_editor` can´t be resolved in interactive mode if (file.type === CONFLICT_TYPES.TEXT) { for (let j = 0, k = file.sections.length; j < k; j++) { if (file.sections[j].conflict) { @@ -334,6 +335,7 @@ } } } else if (file.resolveMode === EDIT_RESOLVE_MODE) { + // Unlikely to happen since switching to Edit mode saves content automatically. // Checking anyway in case the save strategy changes in the future if (!file.content) { diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 06b37aa4997..31f43bdc89a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -659,6 +659,7 @@ describe Projects::MergeRequestsController do id: merge_request.iid expect(merge_request.reload.title).to eq(merge_request.wipless_title) + end end describe 'GET conflict_for_path' do diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 4cecc22aa6c..d258ff52bbb 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -12,74 +12,121 @@ feature 'Merge request conflict resolution', js: true, feature: true do end end - context 'when a merge request can be resolved in the UI' do - let(:merge_request) { create_merge_request('conflict-resolvable') } + shared_examples "conflicts are resolved in Interactive mode" do + it 'conflicts are resolved in Interactive mode' do + within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do + click_button 'Use ours' + end - before do - project.team << [user, :developer] - login_as(user) + within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do + all('button', text: 'Use ours').each do |button| + button.click + end + end - visit namespace_project_merge_request_path(project.namespace, project, merge_request) - end + click_button 'Commit conflict resolution' + wait_for_ajax - it 'shows a link to the conflict resolution page' do - expect(page).to have_link('conflicts', href: /\/conflicts\Z/) - end + expect(page).to have_content('All merge conflicts were resolved') + merge_request.reload_diff - context 'visiting the conflicts resolution page' do - before { click_link('conflicts', href: /\/conflicts\Z/) } + click_on 'Changes' + wait_for_ajax - it 'shows the conflicts' do - begin - expect(find('#conflicts')).to have_content('popen.rb') - rescue Capybara::Poltergeist::JavascriptError - retry - end + within find('.diff-file', text: 'files/ruby/popen.rb') do + expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }") + expect(page).to have_selector('.line_content.new', text: "options = { chdir: path }") end - context 'when in inline mode' do - it 'resolves files manually' do - within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do - click_button 'Edit inline' - wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') - end - - within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do - click_button 'Edit inline' - wait_for_ajax - execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') - end - - click_button 'Commit conflict resolution' - wait_for_ajax - expect(page).to have_content('All merge conflicts were resolved') - merge_request.reload_diff + within find('.diff-file', text: 'files/ruby/regex.rb') do + expect(page).to have_selector('.line_content.new', text: "def username_regexp") + expect(page).to have_selector('.line_content.new', text: "def project_name_regexp") + expect(page).to have_selector('.line_content.new', text: "def path_regexp") + expect(page).to have_selector('.line_content.new', text: "def archive_formats_regexp") + expect(page).to have_selector('.line_content.new', text: "def git_reference_regexp") + expect(page).to have_selector('.line_content.new', text: "def default_regexp") + end + end + end - click_on 'Changes' - wait_for_ajax + shared_examples "conflicts are resolved in Edit inline mode" do + it 'conflicts are resolved in Edit inline mode' do + expect(find('#conflicts')).to have_content('popen.rb') - expect(page).to have_content('One morning') - expect(page).to have_content('Gregor Samsa woke from troubled dreams') - end + within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");') end + + within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do + click_button 'Edit inline' + wait_for_ajax + execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') + end + + click_button 'Commit conflict resolution' + wait_for_ajax + expect(page).to have_content('All merge conflicts were resolved') + merge_request.reload_diff + + click_on 'Changes' + wait_for_ajax + + expect(page).to have_content('One morning') + expect(page).to have_content('Gregor Samsa woke from troubled dreams') end end - context 'when a merge request can be resolved in the UI' do - let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') } - + context 'can be resolved in the UI' do before do project.team << [user, :developer] login_as(user) + end - visit namespace_project_merge_request_path(project.namespace, project, merge_request) + context 'the conflicts are resolvable' do + let(:merge_request) { create_merge_request('conflict-resolvable') } + + before { visit namespace_project_merge_request_path(project.namespace, project, merge_request) } + + it 'shows a link to the conflict resolution page' do + expect(page).to have_link('conflicts', href: /\/conflicts\Z/) + end + + context 'in Inline view mode' do + before { click_link('conflicts', href: /\/conflicts\Z/) } + + include_examples "conflicts are resolved in Interactive mode" + include_examples "conflicts are resolved in Edit inline mode" + end + + context 'in Parallel view mode' do + before do + click_link('conflicts', href: /\/conflicts\Z/) + click_button 'Side-by-side' + end + + include_examples "conflicts are resolved in Interactive mode" + include_examples "conflicts are resolved in Edit inline mode" + end end - context 'a conflict contain markers' do - before { click_link('conflicts', href: /\/conflicts\Z/) } + context 'the conflict contain markers' do + let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') } + + before do + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + click_link('conflicts', href: /\/conflicts\Z/) + end + + it 'conflicts can not be resolved in Interactive mode' do + within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do + expect(page).not_to have_content 'Interactive mode' + expect(page).not_to have_content 'Edit inline' + end + end - it 'resolves files manually' do + it 'conflicts are resolved in Edit inline mode' do within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do wait_for_ajax execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') @@ -94,7 +141,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do click_on 'Changes' wait_for_ajax - find('.nothing-here-block', visible: true).click + find('.click-to-expand').click wait_for_ajax expect(page).to have_content('Gregor Samsa woke from troubled dreams') -- cgit v1.2.1 From cc874d9167b7ba5531aeade09f69ecdc1057a0ee Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 18:05:16 -0500 Subject: Fix column limit on mysql --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8708eae1b2a..2c2edd8e9e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ before_script: - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack - - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate' + - '[ "$SETUP_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate add_limits_mysql' stages: - prepare -- cgit v1.2.1 From 8fd7a5942286d69eb34406e06ba5263c3944012e Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 20:49:16 -0500 Subject: Remove unused files --- .../merge_conflict_data_provider.js.es6 | 438 --------------------- .../javascripts/merge_conflict_resolver.js.es6 | 112 ------ 2 files changed, 550 deletions(-) delete mode 100644 app/assets/javascripts/merge_conflict_data_provider.js.es6 delete mode 100644 app/assets/javascripts/merge_conflict_resolver.js.es6 diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6 deleted file mode 100644 index 5877d2f1896..00000000000 --- a/app/assets/javascripts/merge_conflict_data_provider.js.es6 +++ /dev/null @@ -1,438 +0,0 @@ -const HEAD_HEADER_TEXT = 'HEAD//our changes'; -const ORIGIN_HEADER_TEXT = 'origin//their changes'; -const HEAD_BUTTON_TITLE = 'Use ours'; -const ORIGIN_BUTTON_TITLE = 'Use theirs'; -const INTERACTIVE_RESOLVE_MODE = 'interactive'; -const EDIT_RESOLVE_MODE = 'edit'; -const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE; - -class MergeConflictDataProvider { - - getInitialData() { - // TODO: remove reliance on jQuery and DOM state introspection - const diffViewType = $.cookie('diff_view'); - const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited'); - - return { - isLoading : true, - hasError : false, - isParallel : diffViewType === 'parallel', - diffViewType : diffViewType, - fixedLayout : fixedLayout, - isSubmitting : false, - conflictsData : {} - } - } - - - decorateData(vueInstance, data) { - this.vueInstance = vueInstance; - - if (data.type === 'error') { - vueInstance.hasError = true; - data.errorMessage = data.message; - } - else { - data.shortCommitSha = data.commit_sha.slice(0, 7); - data.commitMessage = data.commit_message; - - this.decorateFiles(data); - this.setParallelLines(data); - this.setInlineLines(data); - } - - vueInstance.conflictsData = data; - vueInstance.isSubmitting = false; - - const conflictsText = this.getConflictsCount() > 1 ? 'conflicts' : 'conflict'; - vueInstance.conflictsData.conflictsText = conflictsText; - } - - decorateFiles(data) { - data.files.forEach((file) => { - file.content = ''; - file.resolutionData = {}; - file.promptDiscardConfirmation = false; - file.resolveMode = DEFAULT_RESOLVE_MODE; - }); - } - - - setParallelLines(data) { - data.files.forEach( (file) => { - file.filePath = this.getFilePath(file); - file.iconClass = `fa-${file.blob_icon}`; - file.blobPath = file.blob_path; - file.parallelLines = []; - const linesObj = { left: [], right: [] }; - - file.sections.forEach( (section) => { - const { conflict, lines, id } = section; - - if (conflict) { - linesObj.left.push(this.getOriginHeaderLine(id)); - linesObj.right.push(this.getHeadHeaderLine(id)); - } - - lines.forEach( (line) => { - const { type } = line; - - if (conflict) { - if (type === 'old') { - linesObj.left.push(this.getLineForParallelView(line, id, 'conflict')); - } - else if (type === 'new') { - linesObj.right.push(this.getLineForParallelView(line, id, 'conflict', true)); - } - } - else { - const lineType = type || 'context'; - - linesObj.left.push (this.getLineForParallelView(line, id, lineType)); - linesObj.right.push(this.getLineForParallelView(line, id, lineType, true)); - } - }); - - this.checkLineLengths(linesObj); - }); - - for (let i = 0, len = linesObj.left.length; i < len; i++) { - file.parallelLines.push([ - linesObj.right[i], - linesObj.left[i] - ]); - } - - }); - } - - - checkLineLengths(linesObj) { - let { left, right } = linesObj; - - if (left.length !== right.length) { - if (left.length > right.length) { - const diff = left.length - right.length; - for (let i = 0; i < diff; i++) { - right.push({ lineType: 'emptyLine', richText: '' }); - } - } - else { - const diff = right.length - left.length; - for (let i = 0; i < diff; i++) { - left.push({ lineType: 'emptyLine', richText: '' }); - } - } - } - } - - - setInlineLines(data) { - data.files.forEach( (file) => { - file.iconClass = `fa-${file.blob_icon}`; - file.blobPath = file.blob_path; - file.filePath = this.getFilePath(file); - file.inlineLines = [] - - file.sections.forEach( (section) => { - let currentLineType = 'new'; - const { conflict, lines, id } = section; - - if (conflict) { - file.inlineLines.push(this.getHeadHeaderLine(id)); - } - - lines.forEach( (line) => { - const { type } = line; - - if ((type === 'new' || type === 'old') && currentLineType !== type) { - currentLineType = type; - file.inlineLines.push({ lineType: 'emptyLine', richText: '' }); - } - - this.decorateLineForInlineView(line, id, conflict); - file.inlineLines.push(line); - }) - - if (conflict) { - file.inlineLines.push(this.getOriginHeaderLine(id)); - } - }); - }); - } - - - handleSelected(file, sectionId, selection) { - const vi = this.vueInstance; - let files = vi.conflictsData.files; - - vi.$set(`conflictsData.files[${files.indexOf(file)}].resolutionData['${sectionId}']`, selection); - - - files.forEach( (file) => { - file.inlineLines.forEach( (line) => { - if (line.id === sectionId && (line.hasConflict || line.isHeader)) { - this.markLine(line, selection); - } - }); - - file.parallelLines.forEach( (lines) => { - const left = lines[0]; - const right = lines[1]; - const hasSameId = right.id === sectionId || left.id === sectionId; - const isLeftMatch = left.hasConflict || left.isHeader; - const isRightMatch = right.hasConflict || right.isHeader; - - if (hasSameId && (isLeftMatch || isRightMatch)) { - this.markLine(left, selection); - this.markLine(right, selection); - } - }) - }); - } - - - updateViewType(newType) { - const vi = this.vueInstance; - - if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) { - return; - } - - vi.diffViewType = newType; - vi.isParallel = newType === 'parallel'; - $.cookie('diff_view', newType, { - path: (gon && gon.relative_url_root) || '/' - }); - $('.content-wrapper .container-fluid') - .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout); - } - - setFileResolveMode(file, mode) { - const vi = this.vueInstance; - - // Restore Interactive mode when switching to Edit mode - if (mode === EDIT_RESOLVE_MODE) { - file.resolutionData = {}; - - this.restoreFileLinesState(file); - } - - file.resolveMode = mode; - } - - - restoreFileLinesState(file) { - file.inlineLines.forEach((line) => { - if (line.hasConflict || line.isHeader) { - line.isSelected = false; - line.isUnselected = false; - } - }); - - file.parallelLines.forEach((lines) => { - const left = lines[0]; - const right = lines[1]; - const isLeftMatch = left.hasConflict || left.isHeader; - const isRightMatch = right.hasConflict || right.isHeader; - - if (isLeftMatch || isRightMatch) { - left.isSelected = false; - left.isUnselected = false; - right.isSelected = false; - right.isUnselected = false; - } - }); - } - - - setPromptConfirmationState(file, state) { - file.promptDiscardConfirmation = state; - } - - - markLine(line, selection) { - if (selection === 'head' && line.isHead) { - line.isSelected = true; - line.isUnselected = false; - } - else if (selection === 'origin' && line.isOrigin) { - line.isSelected = true; - line.isUnselected = false; - } - else { - line.isSelected = false; - line.isUnselected = true; - } - } - - - getConflictsCount() { - if (!this.vueInstance.conflictsData.files) { - return 0; - } - - const files = this.vueInstance.conflictsData.files; - let count = 0; - - files.forEach((file) => { - file.sections.forEach((section) => { - if (section.conflict) { - count++; - } - }); - }); - - return count; - } - - - isReadyToCommit() { - const vi = this.vueInstance; - const files = this.vueInstance.conflictsData.files; - const hasCommitMessage = this.vueInstance.conflictsData.commitMessage.trim(); - let unresolved = 0; - - for (let i = 0, l = files.length; i < l; i++) { - let file = files[i]; - - if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { - let numberConflicts = 0; - let resolvedConflicts = Object.keys(file.resolutionData).length - - for (let j = 0, k = file.sections.length; j < k; j++) { - if (file.sections[j].conflict) { - numberConflicts++; - } - } - - if (resolvedConflicts !== numberConflicts) { - unresolved++; - } - } else if (file.resolveMode === EDIT_RESOLVE_MODE) { - // Unlikely to happen since switching to Edit mode saves content automatically. - // Checking anyway in case the save strategy changes in the future - if (!file.content) { - unresolved++; - continue; - } - } - } - - return !vi.isSubmitting && hasCommitMessage && !unresolved; - } - - - getCommitButtonText() { - const initial = 'Commit conflict resolution'; - const inProgress = 'Committing...'; - const vue = this.vueInstance; - - return vue ? vue.isSubmitting ? inProgress : initial : initial; - } - - - decorateLineForInlineView(line, id, conflict) { - const { type } = line; - line.id = id; - line.hasConflict = conflict; - line.isHead = type === 'new'; - line.isOrigin = type === 'old'; - line.hasMatch = type === 'match'; - line.richText = line.rich_text; - line.isSelected = false; - line.isUnselected = false; - } - - getLineForParallelView(line, id, lineType, isHead) { - const { old_line, new_line, rich_text } = line; - const hasConflict = lineType === 'conflict'; - - return { - id, - lineType, - hasConflict, - isHead : hasConflict && isHead, - isOrigin : hasConflict && !isHead, - hasMatch : lineType === 'match', - lineNumber : isHead ? new_line : old_line, - section : isHead ? 'head' : 'origin', - richText : rich_text, - isSelected : false, - isUnselected : false - } - } - - - getHeadHeaderLine(id) { - return { - id : id, - richText : HEAD_HEADER_TEXT, - buttonTitle : HEAD_BUTTON_TITLE, - type : 'new', - section : 'head', - isHeader : true, - isHead : true, - isSelected : false, - isUnselected: false - } - } - - - getOriginHeaderLine(id) { - return { - id : id, - richText : ORIGIN_HEADER_TEXT, - buttonTitle : ORIGIN_BUTTON_TITLE, - type : 'old', - section : 'origin', - isHeader : true, - isOrigin : true, - isSelected : false, - isUnselected: false - } - } - - - handleFailedRequest(vueInstance, data) { - vueInstance.hasError = true; - vueInstance.conflictsData.errorMessage = 'Something went wrong!'; - } - - - getCommitData() { - let conflictsData = this.vueInstance.conflictsData; - let commitData = {}; - - commitData = { - commitMessage: conflictsData.commitMessage, - files: [] - }; - - conflictsData.files.forEach((file) => { - let addFile; - - addFile = { - old_path: file.old_path, - new_path: file.new_path - }; - - // Submit only one data for type of editing - if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) { - addFile.sections = file.resolutionData; - } else if (file.resolveMode === EDIT_RESOLVE_MODE) { - addFile.content = file.content; - } - - commitData.files.push(addFile); - }); - - return commitData; - } - - - getFilePath(file) { - const { old_path, new_path } = file; - return old_path === new_path ? new_path : `${old_path} → ${new_path}`; - } -} diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6 deleted file mode 100644 index c317a6521b5..00000000000 --- a/app/assets/javascripts/merge_conflict_resolver.js.es6 +++ /dev/null @@ -1,112 +0,0 @@ -//= require vue -//= require ./merge_conflicts/components/diff_file_editor - -const INTERACTIVE_RESOLVE_MODE = 'interactive'; -const EDIT_RESOLVE_MODE = 'edit'; - -class MergeConflictResolver { - - constructor() { - this.dataProvider = new MergeConflictDataProvider(); - this.initVue(); - } - - initVue() { - const that = this; - this.vue = new Vue({ - el : '#conflicts', - name : 'MergeConflictResolver', - data : this.dataProvider.getInitialData(), - created : this.fetchData(), - computed : this.setComputedProperties(), - methods : { - handleSelected(file, sectionId, selection) { - that.dataProvider.handleSelected(file, sectionId, selection); - }, - handleViewTypeChange(newType) { - that.dataProvider.updateViewType(newType); - }, - commit() { - that.commit(); - }, - onClickResolveModeButton(file, mode) { - that.toggleResolveMode(file, mode); - }, - acceptDiscardConfirmation(file) { - that.dataProvider.setPromptConfirmationState(file, false); - that.dataProvider.setFileResolveMode(file, INTERACTIVE_RESOLVE_MODE); - }, - cancelDiscardConfirmation(file) { - that.dataProvider.setPromptConfirmationState(file, false); - }, - }, - components: { - 'diff-file-editor': window.gl.diffFileEditor - } - }) - } - - - setComputedProperties() { - const dp = this.dataProvider; - - return { - conflictsCount() { return dp.getConflictsCount() }, - readyToCommit() { return dp.isReadyToCommit() }, - commitButtonText() { return dp.getCommitButtonText() } - } - } - - - fetchData() { - const dp = this.dataProvider; - - $.get($('#conflicts').data('conflictsPath')) - .done((data) => { - dp.decorateData(this.vue, data); - }) - .error((data) => { - dp.handleFailedRequest(this.vue, data); - }) - .always(() => { - this.vue.isLoading = false; - - this.vue.$nextTick(() => { - $('#conflicts .js-syntax-highlight').syntaxHighlight(); - }); - - $('.content-wrapper .container-fluid') - .toggleClass('container-limited', !this.vue.isParallel && this.vue.fixedLayout); - }) - } - - - commit() { - this.vue.isSubmitting = true; - - $.ajax({ - url: $('#conflicts').data('resolveConflictsPath'), - data: JSON.stringify(this.dataProvider.getCommitData()), - contentType: "application/json", - dataType: 'json', - method: 'POST' - }) - .done((data) => { - window.location.href = data.redirect_to; - }) - .error(() => { - this.vue.isSubmitting = false; - new Flash('Something went wrong!'); - }); - } - - - toggleResolveMode(file, mode) { - if (mode === INTERACTIVE_RESOLVE_MODE && file.resolveEditChanged) { - this.dataProvider.setPromptConfirmationState(file, true); - return; - } - - this.dataProvider.setFileResolveMode(file, mode); - } -} -- cgit v1.2.1 From 6141816c233d87888d1450d3bc48a28fc6044ec1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 5 Oct 2016 20:49:43 -0500 Subject: Use plain JS to get elements and data and values --- .../javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 index 12688365f19..7fd3749b3e2 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 +++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 @@ -10,14 +10,14 @@ $(() => { const INTERACTIVE_RESOLVE_MODE = 'interactive'; - const $conflicts = $(document.getElementById('conflicts')); + const conflictsEl = document.querySelector('#conflicts'); const mergeConflictsStore = gl.mergeConflicts.mergeConflictsStore; const mergeConflictsService = new gl.mergeConflicts.mergeConflictsService({ - conflictsPath: $conflicts.data('conflictsPath'), - resolveConflictsPath: $conflicts.data('resolveConflictsPath') + conflictsPath: conflictsEl.dataset.conflictsPath, + resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath }); - gl.MergeConflictsResolverApp = new Vue({ + gl.MergeConflictsResolverApp = new Vue({ el: '#conflicts', data: mergeConflictsStore.state, components: { @@ -48,7 +48,7 @@ $(() => { mergeConflictsStore.setLoadingState(false); this.$nextTick(() => { - $conflicts.find('.js-syntax-highlight').syntaxHighlight(); + $(conflictsEl.querySelectorAll('.js-syntax-highlight')).syntaxHighlight(); }); }); }, -- cgit v1.2.1 From edd30976bd8069a02e0674ee36d1455c06ea751f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 12 Oct 2016 18:53:29 -0500 Subject: Update CHANGELOG --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..f0b250f365c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ v 8.13.0 (unreleased) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.6 + - Ability to resolve merge request conflicts with editor !6374 + - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup -- cgit v1.2.1 From cdd0dd507d1d5a226f1f438c4bfa602a115caac3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 12 Oct 2016 22:52:44 -0500 Subject: Fix discard message --- .../merge_requests/conflicts/components/_diff_file_editor.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 943ae6ee129..3c927d362c2 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -2,7 +2,7 @@ .diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert - Are you sure to discard your changes? + Are you sure you want to discard your changes? .discard-actions %button.btn.btn-sm.btn-close{ "@click" => "acceptDiscardConfirmation(file)" } Discard changes %button.btn.btn-sm{ "@click" => "cancelDiscardConfirmation(file)" } Cancel -- cgit v1.2.1 From 3764fd4b4166bdf869cc04e4e82558d6b80b4ca5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 10:34:15 +0100 Subject: Add blob_ace_mode to conflict content response --- lib/gitlab/conflict/file.rb | 5 +++++ spec/controllers/projects/merge_requests_controller_spec.rb | 1 + spec/lib/gitlab/conflict/file_spec.rb | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 661e43d3fa9..c843315782d 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -25,6 +25,10 @@ module Gitlab merge_file_result[:data] end + def our_blob + @our_blob ||= repository.blob_at(merge_request.diff_refs.head_sha, our_path) + end + def type lines unless @type @@ -209,6 +213,7 @@ module Gitlab json_hash.tap do |json_hash| if opts[:full_content] json_hash[:content] = content + json_hash[:blob_ace_mode] = our_blob && our_blob.language.try(:ace_mode) else json_hash[:sections] = sections if type.text? json_hash[:type] = type diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 31f43bdc89a..3fe90375b92 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -712,6 +712,7 @@ describe Projects::MergeRequestsController do 'new_path' => path, 'blob_icon' => 'file-text-o', 'blob_path' => a_string_ending_with(path), + 'blob_ace_mode' => 'ruby', 'content' => content) end end diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index 60020487061..648d342ecf8 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -257,5 +257,16 @@ FILE it 'includes the blob icon for the file' do expect(conflict_file.as_json[:blob_icon]).to eq('file-text-o') end + + context 'with the full_content option passed' do + it 'includes the full content of the conflict' do + expect(conflict_file.as_json(full_content: true)).to have_key(:content) + end + + it 'includes the detected language of the conflict file' do + expect(conflict_file.as_json(full_content: true)[:blob_ace_mode]). + to eq('ruby') + end + end end end -- cgit v1.2.1 From f5de2ce3fd74af214556f677f54efa3f4da13ed3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 13 Oct 2016 12:32:35 -0500 Subject: Add syntax highlighting to files --- .../javascripts/merge_conflicts/components/diff_file_editor.js.es6 | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 417095a8db7..3379414343f 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -55,6 +55,7 @@ this.fileLoaded = true; this.editor = ace.edit(content); this.editor.$blockScrolling = Infinity; // Turn off annoying warning + this.editor.getSession().setMode(`ace/mode/${file.blob_ace_mode}`); this.editor.on('change', () => { this.saveDiffResolution(); }); -- cgit v1.2.1 From 26e327ea93140f6400b965b845958ff50461718c Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 13 Oct 2016 14:17:28 -0500 Subject: Update CHANGELOG --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f0b250f365c..e9f84503b2b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,7 +9,6 @@ v 8.13.0 (unreleased) - Add link from system note to compare with previous version - Use gitlab-shell v3.6.6 - Ability to resolve merge request conflicts with editor !6374 - - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup -- cgit v1.2.1 From d00f17c0108c73b521b533bad7d2d7706cb25a5c Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Thu, 13 Oct 2016 21:56:28 +0000 Subject: fix grafana_configuration.md move link --- doc/monitoring/performance/grafana_configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 93320b40174..0d4be02ff5f 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1 +1 @@ -This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). +This document was moved to [administration/monitoring/performance/grafana_configuration](../../administration/monitoring/performance/grafana_configuration.md). -- cgit v1.2.1 From 6c7d3a0ee4c4c2b4e0a38fe92cd364c888eda907 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 14 Oct 2016 09:40:40 +1100 Subject: Remove '/u' prefix form username from Account page --- CHANGELOG | 1 + app/views/profiles/accounts/show.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..f933c298b01 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) + - Remove '/u' prefix form username from Account page (blackst0ne) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c80f22457b4..8ee643f3bcc 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -86,11 +86,11 @@ = f.label :username, "Path", class: "label-light" .input-group .input-group-addon - = "#{root_url}u/" + = "#{root_url}" = f.text_field :username, required: true, class: 'form-control' .help-block Current path: - = "#{root_url}u/#{current_user.username}" + = "#{root_url}#{current_user.username}" .prepend-top-default = f.button class: "btn btn-warning", type: "submit" do = icon "spinner spin", class: "hidden loading-username" -- cgit v1.2.1 From 86d59657fc8ba0f96b8f957b1298a34144e060f9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 11:55:26 +0100 Subject: Increased performance of GL dropdown renderItem - Fixes an issue where `renderItem` is called several times even when not required - Increased performance when rendering dropdown items Closes #21110 --- app/assets/javascripts/gl_dropdown.js | 63 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e034ca68645..c0cb70cb427 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -37,12 +37,9 @@ e.preventDefault() } }) - .on('keyup', function(e) { + .on('input', function(e) { var keyCode; keyCode = e.which; - if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) { - return; - } if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -500,14 +497,17 @@ // Render the full menu GitLabDropdown.prototype.renderMenu = function(html) { - var menu_html; - menu_html = ""; if (this.options.renderMenu) { - menu_html = this.options.renderMenu(html); + return this.options.renderMenu(html); } else { - menu_html = $('
    ').append(html); + var ul = document.createElement('ul'); + + for (var i = 0; i < html.length; i++) { + ul.appendChild(html[i]); + } + + return ul; } - return menu_html; }; // Append the menu into the dropdown @@ -521,7 +521,7 @@ }; GitLabDropdown.prototype.renderItem = function(data, group, index) { - var cssClass, field, fieldName, groupAttrs, html, selected, text, url, value; + var field, fieldName, html, selected, text, url, value; if (group == null) { group = false; } @@ -529,18 +529,16 @@ // Render the row index = false; } - html = ""; - // Divider - if (data === "divider") { - return "
  • "; - } - // Separator is a full-width divider - if (data === "separator") { - return "
  • "; + html = document.createElement('li'); + if (data === 'divider' || data === 'separator') { + html.className = data; + return html; } // Header if (data.header != null) { - return _.template('')({ header: data.header }); + html.className = 'dropdown-header'; + html.innerHTML = data.header; + return html; } if (this.options.renderRow) { // Call the render function @@ -567,24 +565,25 @@ } else { text = data.text != null ? data.text : ''; } - cssClass = ""; - if (selected) { - cssClass = "is-active"; - } if (this.highlight) { text = this.highlightTextMatches(text, this.filterInput.val()); } + // Create the list item & the link + var link = document.createElement('a'); + + link.href = url; + link.innerHTML = text; + + if (selected) { + link.className = 'is-active'; + } + if (group) { - groupAttrs = 'data-group=' + group + ' data-index=' + index; - } else { - groupAttrs = ''; + link.dataset.group = group; + link.dataset.index = index; } - html = _.template('
  • class="<%- cssClass %>"><%= text %>
  • ')({ - url: url, - groupAttrs: groupAttrs, - cssClass: cssClass, - text: text - }); + + html.appendChild(link); } return html; }; -- cgit v1.2.1 From ef5a76f68a89b9bee9070800ceb19753d0c602fe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 17:31:08 +0100 Subject: Fixed rendering of HTML strings --- app/assets/javascripts/gl_dropdown.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index c0cb70cb427..06b31b9b24b 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -503,7 +503,13 @@ var ul = document.createElement('ul'); for (var i = 0; i < html.length; i++) { - ul.appendChild(html[i]); + var el = html[i]; + + if (typeof el === 'string') { + ul.innerHTML += el; + } else { + ul.appendChild(el); + } } return ul; -- cgit v1.2.1 From ba4e6376e1a6284286d2af46a9f5f1afa6382fd0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 09:29:37 +0100 Subject: Fixed appending jQuery elements --- app/assets/javascripts/gl_dropdown.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 06b31b9b24b..e94e7cd8be8 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -505,6 +505,10 @@ for (var i = 0; i < html.length; i++) { var el = html[i]; + if (el instanceof jQuery) { + el = el.get(0); + } + if (typeof el === 'string') { ul.innerHTML += el; } else { -- cgit v1.2.1 From e8901b20e9c07ae2654e267e5e88c0aa3476835d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 11:25:59 +0100 Subject: Changed trigger keyup to input --- app/assets/javascripts/gl_dropdown.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e94e7cd8be8..b7a70fab74e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -25,7 +25,7 @@ return function(e) { e.preventDefault(); e.stopPropagation(); - return _this.input.val('').trigger('keyup').focus(); + return _this.input.val('').trigger('input').focus(); }; })(this)); // Key events @@ -38,8 +38,6 @@ } }) .on('input', function(e) { - var keyCode; - keyCode = e.which; if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -252,7 +250,7 @@ _this.fullData = data; _this.parseData(_this.fullData); if (_this.options.filterable && _this.filter && _this.filter.input) { - return _this.filter.input.trigger('keyup'); + return _this.filter.input.trigger('input'); } }; // Remote data @@ -484,7 +482,7 @@ // Triggering 'keyup' will re-render the dropdown which is not always required // specially if we want to keep the state of the dropdown needed for bulk-assignment if (!this.options.persistWhenHide) { - $input.trigger("keyup"); + $input.trigger("input"); } if (this.dropdown.find(".dropdown-toggle-page").length) { $('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS); -- cgit v1.2.1 From 88996deba5a1d8b29f03856b295b33caa7290981 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 12:32:30 +0100 Subject: Fixed keycode undefined --- app/assets/javascripts/gl_dropdown.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index b7a70fab74e..fe798509420 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -37,7 +37,7 @@ e.preventDefault() } }) - .on('input', function(e) { + .on('input', function() { if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.addClass(HAS_VALUE_CLASS); } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { @@ -50,10 +50,6 @@ if (this.options.remote) { clearTimeout(timeout); return timeout = setTimeout(function() { - var blurField = this.shouldBlur(keyCode); - if (blurField && this.filterInputBlur) { - this.input.blur(); - } return this.options.query(this.input.val(), function(data) { return this.options.callback(data); }.bind(this)); -- cgit v1.2.1 From 8ebc0919ea7a0647b5b5a0951cd3ce2070d78bb2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 08:26:40 +0200 Subject: Link to review apps example from docs [ci skip] --- doc/ci/examples/README.md | 1 + doc/ci/yaml/README.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 08fbd9afa2f..ffc310ec8c7 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -13,6 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test a Scala application](test-scala-application.md) - [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) +- [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index cdf5ecc7a84..59399861a97 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -594,6 +594,8 @@ create the `review-apps/branch-name` environment. This environment should be accessible under `https://branch-name.review.example.com/`. +You can see a simple example at https://gitlab.com/gitlab-examples/review-apps-nginx/. + ### artifacts >**Notes:** -- cgit v1.2.1 From ab2a7a806a698d6638bb8b3f2ecc694f265fe65e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 13 Oct 2016 23:38:50 -0700 Subject: Add missing routes to make group edits work Closes #23306 --- config/routes/group.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/routes/group.rb b/config/routes/group.rb index 47a8a0a53d4..8bee2bb1eb4 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -3,6 +3,10 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do scope(path: ':id', as: :group, controller: :groups) do get '/', action: :show + post '/', action: :create + patch '/', action: :update + put '/', action: :update + delete '/', action: :destroy end end -- cgit v1.2.1 From 17e23b4c9c141bdbdcde4efec0bbcfdd544da8bd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 00:06:44 -0700 Subject: Add specs for group edit and deletion --- spec/features/groups_spec.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index c54ec2563ad..3f908294cbb 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -47,6 +47,32 @@ feature 'Group', feature: true do end end + describe 'Group Edit' do + let(:group) { create(:group) } + let(:path) { edit_group_path(group) } + + it 'saves new settings' do + expect(group.request_access_enabled).to be_truthy + visit path + + find('#group_request_access_enabled').set(false) + + click_button 'Save group' + + expect(page).to have_content 'successfully updated' + group.reload + expect(group.request_access_enabled).to be_falsey + end + + it 'removes group' do + visit path + + click_link 'Remove Group' + + expect(page).to have_content "scheduled for deletion" + end + end + describe 'description' do let(:group) { create(:group) } let(:path) { group_path(group) } -- cgit v1.2.1 From b927473c45e42e99adbbe69b71653f1b2981df01 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 09:16:55 +0200 Subject: Grapify todos API --- lib/api/todos.rb | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 19df13d8aac..832b04a3bb1 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -8,18 +8,19 @@ module API 'issues' => ->(id) { find_project_issue(id) } } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do ISSUABLE_TYPES.each do |type, finder| type_id_str = "#{type.singularize}_id".to_sym - # Create a todo on an issuable - # - # Parameters: - # id (required) - The ID of a project - # issuable_id (required) - The ID of an issuable - # Example Request: - # POST /projects/:id/issues/:issuable_id/todo - # POST /projects/:id/merge_requests/:issuable_id/todo + desc 'Create a todo on an issuable' do + success Entities::Todo + end + params do + requires type_id_str, type: Integer, desc: 'The ID of an issuable' + end post ":id/#{type}/:#{type_id_str}/todo" do issuable = instance_exec(params[type_id_str], &finder) todo = TodoService.new.mark_todo(issuable, current_user).first @@ -40,25 +41,21 @@ module API end end - # Get a todo list - # - # Example Request: - # GET /todos - # + desc 'Get a todo list' do + success Entities::Todo + end get do todos = find_todos present paginate(todos), with: Entities::Todo, current_user: current_user end - # Mark a todo as done - # - # Parameters: - # id: (required) - The ID of the todo being marked as done - # - # Example Request: - # DELETE /todos/:id - # + desc 'Mark a todo as done' do + success Entities::Todo + end + params do + requires :id, type: Integer, desc: 'The ID of the todo being marked as done' + end delete ':id' do todo = current_user.todos.find(params[:id]) TodoService.new.mark_todos_as_done([todo], current_user) @@ -66,11 +63,7 @@ module API present todo.reload, with: Entities::Todo, current_user: current_user end - # Mark all todos as done - # - # Example Request: - # DELETE /todos - # + desc 'Mark all todos as done' delete do todos = find_todos TodoService.new.mark_todos_as_done(todos, current_user) -- cgit v1.2.1 From c850651b9174a40376dc5b712ce696406adedeab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 10:07:36 +0200 Subject: Add link to update docs for source installations --- doc/user/project/new_ci_build_permissions_model.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 19385a99d64..8827b501901 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -162,7 +162,7 @@ As an administrator: - **500 errors**: You will need to update [GitLab Workhorse][workhorse] to at least 0.8.2. This is done automatically for Omnibus installations, you need to - check manually for installations from source. + [check manually][update-docs] for installations from source. - **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, Apache, etc.). It might be a good idea to let GitLab use the internal NGINX web server and not disable it completely. See [this comment][comment] for an @@ -307,4 +307,5 @@ test: [git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules [https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md +[update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update [workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -- cgit v1.2.1 From 3726dc4bb73647123ebef5c97f401f5537d3ae16 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 10:19:16 +0200 Subject: Check if project exists before creating deployment --- app/services/create_deployment_service.rb | 6 ++++++ app/workers/build_success_worker.rb | 2 -- spec/services/create_deployment_service_spec.rb | 17 ++++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index c6dc2148c11..ff9a8310a8c 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,6 +2,8 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) + return unless executable? + ActiveRecord::Base.transaction do @deployable = deployable @environment = prepare_environment @@ -14,6 +16,10 @@ class CreateDeploymentService < BaseService private + def executable? + project && name.present? + end + def deploy project.deployments.create( environment: @environment, diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index a9dc34166a1..500d357ce31 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -4,8 +4,6 @@ class BuildSuccessWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - return unless build.project - create_deployment(build) end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 343b4385bf2..5fe56e7725f 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -84,11 +84,22 @@ describe CreateDeploymentService, services: true do expect(subject).to be_persisted end end + + context 'when project was removed' do + let(:project) { nil } + + it 'does not create deployment or environment' do + expect { subject }.not_to raise_error + + expect(Environment.count).to be_zero + expect(Deployment.count).to be_zero + end + end end describe 'processing of builds' do let(:environment) { nil } - + shared_examples 'does not create environment and deployment' do it 'does not create a new environment' do expect { subject }.not_to change { Environment.count } @@ -133,12 +144,12 @@ describe CreateDeploymentService, services: true do context 'without environment specified' do let(:build) { create(:ci_build, project: project) } - + it_behaves_like 'does not create environment and deployment' do subject { build.success } end end - + context 'when environment is specified' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production', options: options) } -- cgit v1.2.1 From e8c9ccc2d8f85e22bb4969cb544f9de25f4c08f5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 10:25:11 +0200 Subject: Refactor merge requests revisions - A system note now appears on every push - Replace dashes with underscores in images [ci skip] --- .../merge_requests/img/versions-compare.png | Bin 68722 -> 0 bytes .../merge_requests/img/versions-dropdown.png | Bin 60587 -> 0 bytes .../merge_requests/img/versions_compare.png | Bin 0 -> 68722 bytes .../merge_requests/img/versions_dropdown.png | Bin 0 -> 60587 bytes .../merge_requests/img/versions_system_note.png | Bin 0 -> 18731 bytes doc/user/project/merge_requests/versions.md | 30 ++++++++++++++------- 6 files changed, 20 insertions(+), 10 deletions(-) delete mode 100644 doc/user/project/merge_requests/img/versions-compare.png delete mode 100644 doc/user/project/merge_requests/img/versions-dropdown.png create mode 100644 doc/user/project/merge_requests/img/versions_compare.png create mode 100644 doc/user/project/merge_requests/img/versions_dropdown.png create mode 100644 doc/user/project/merge_requests/img/versions_system_note.png diff --git a/doc/user/project/merge_requests/img/versions-compare.png b/doc/user/project/merge_requests/img/versions-compare.png deleted file mode 100644 index 890cae7768c..00000000000 Binary files a/doc/user/project/merge_requests/img/versions-compare.png and /dev/null differ diff --git a/doc/user/project/merge_requests/img/versions-dropdown.png b/doc/user/project/merge_requests/img/versions-dropdown.png deleted file mode 100644 index 9bab9304e14..00000000000 Binary files a/doc/user/project/merge_requests/img/versions-dropdown.png and /dev/null differ diff --git a/doc/user/project/merge_requests/img/versions_compare.png b/doc/user/project/merge_requests/img/versions_compare.png new file mode 100644 index 00000000000..890cae7768c Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_compare.png differ diff --git a/doc/user/project/merge_requests/img/versions_dropdown.png b/doc/user/project/merge_requests/img/versions_dropdown.png new file mode 100644 index 00000000000..9bab9304e14 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_dropdown.png differ diff --git a/doc/user/project/merge_requests/img/versions_system_note.png b/doc/user/project/merge_requests/img/versions_system_note.png new file mode 100644 index 00000000000..7c9d7715745 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_system_note.png differ diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 2805fdf635c..77eab7ba5e3 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -7,26 +7,36 @@ of merge request diff is created. When you visit a merge request that contains more than one pushes, you can select and compare the versions of those merge request diffs. -![Merge Request Versions](img/versions.png) +![Merge request versions](img/versions.png) + +--- By default, the latest version of changes is shown. However, you can select an older one from version dropdown. -![Merge Request Versions](img/versions-dropdown.png) +![Merge request versions dropdown](img/versions_dropdown.png) + +--- -You can also compare the merge request version with older one to see what is +You can also compare the merge request version with an older one to see what has changed since then. -![Merge Request Versions](img/versions-compare.png) +![Merge request versions compare](img/versions_compare.png) + +--- + +Every time you push new changes to the branch, a link to compare the last +changes appears as a system note. -Please note that comments are disabled while viewing outdated merge versions -or comparing to versions other than base. +![Merge request versions system note](img/versions_system_note.png) --- ->**Note:** -Merge request versions are based on push not on commit. So, if you pushed 5 -commits in a single push, it will be a single option in the dropdown. If you -pushed 5 times, that will count for 5 options. +>**Notes:** +- Comments are disabled while viewing outdated merge versions or comparing to + versions other than base. +- Merge request versions are based on push not on commit. So, if you pushed 5 + commits in a single push, it will be a single option in the dropdown. If you + pushed 5 times, that will count for 5 options. [ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467 -- cgit v1.2.1 From ebddb5f329778ebf96d78c31e2ac93707a455864 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 09:34:53 +0100 Subject: Fixed undefined keycode build error --- app/assets/javascripts/gl_dropdown.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index fe798509420..53762f2965c 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -43,9 +43,6 @@ } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) { $inputContainer.removeClass(HAS_VALUE_CLASS); } - if (keyCode === 13 && !options.elIsInput) { - return false; - } // Only filter asynchronously only if option remote is set if (this.options.remote) { clearTimeout(timeout); -- cgit v1.2.1 From 04147f8773992b50c372160ed73ce8e7674d77d9 Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 09:56:47 +0100 Subject: Fixed missing links --- doc/university/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/university/README.md b/doc/university/README.md index 8b3538d5616..d5bb7299ecf 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -67,18 +67,18 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project #### 1.7 Community and Support -1. [Getting Help](/getting-help/) +1. [Getting Help](https://about.gitlab.com/getting-help/) - Proposing Features and Reporting and Tracking bugs for GitLab - The GitLab IRC channel, Gitter Chat Room, Community Forum and Mailing List - Getting Technical Support - Being part of our Great Community and Contributing to GitLab 1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) 1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) -1. [GitLab Training Workshops](/training) +1. [GitLab Training Workshops](https://about.gitlab.com/getting-help/training) #### 1.8 GitLab Training Material -1. [Git and GitLab Terminology](/glossary/) +1. [Git and GitLab Terminology](glossary/README.md) 1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web) 1. [Git and GitLab Revision](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/end-user) @@ -209,7 +209,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project *Some content can only be accessed by GitLab team members* -1. [Support Path](/support/) +1. [Support Path](support/README.md) 1. [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/) 1. [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md) 1. [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing) -- cgit v1.2.1 From 75e12ca9880e900c8d0b29c973bdef40c6757c6f Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 10:00:02 +0100 Subject: Fixed missing links --- doc/university/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/university/README.md b/doc/university/README.md index d5bb7299ecf..e71e49c33c8 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -74,7 +74,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project - Being part of our Great Community and Contributing to GitLab 1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) 1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) -1. [GitLab Training Workshops](https://about.gitlab.com/getting-help/training) +1. [GitLab Training Workshops](https://about.gitlab.com/training) #### 1.8 GitLab Training Material -- cgit v1.2.1 From 6a4f71008390752e6b5574a9e9bdf277732d852d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 4 Oct 2016 16:03:13 +0200 Subject: Show what time ago a MR was deployed --- CHANGELOG | 1 + app/assets/javascripts/merge_request_widget.js | 193 ----------------- app/assets/javascripts/merge_request_widget.js.es6 | 240 +++++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 4 + .../projects/merge_requests_controller.rb | 31 ++- app/models/environment.rb | 8 + app/models/merge_request.rb | 9 + app/models/repository.rb | 8 + .../merge_requests/widget/_heading.html.haml | 16 +- .../projects/merge_requests/widget/_show.html.haml | 2 +- .../merge_requests/widget_deployments_spec.rb | 26 +++ spec/javascripts/merge_request_widget_spec.js | 27 ++- spec/models/environment_spec.rb | 17 ++ spec/models/repository_spec.rb | 23 ++ .../merge_requests/_heading.html.haml_spec.rb | 28 --- 15 files changed, 391 insertions(+), 242 deletions(-) delete mode 100644 app/assets/javascripts/merge_request_widget.js create mode 100644 app/assets/javascripts/merge_request_widget.js.es6 create mode 100644 spec/features/merge_requests/widget_deployments_spec.rb delete mode 100644 spec/views/projects/merge_requests/_heading.html.haml_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..348ac41a534 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,7 @@ v 8.13.0 (unreleased) - Add new issue button to each list on Issues Board - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) + - Show the time ago a merge request was deployed to an environment - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js deleted file mode 100644 index 7bbcdf59838..00000000000 --- a/app/assets/javascripts/merge_request_widget.js +++ /dev/null @@ -1,193 +0,0 @@ -(function() { - var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; - - this.MergeRequestWidget = (function() { - function MergeRequestWidget(opts) { - // Initialize MergeRequestWidget behavior - // - // check_enable - Boolean, whether to check automerge status - // merge_check_url - String, URL to use to check automerge status - // ci_status_url - String, URL to use to check CI status - // - this.opts = opts; - $('#modal_merge_info').modal({ - show: false - }); - this.firstCICheck = true; - this.readyForCICheck = false; - this.cancel = false; - clearInterval(this.fetchBuildStatusInterval); - this.clearEventListeners(); - this.addEventListeners(); - this.getCIStatus(false); - this.pollCIStatus(); - notifyPermissions(); - } - - MergeRequestWidget.prototype.clearEventListeners = function() { - return $(document).off('page:change.merge_request'); - }; - - MergeRequestWidget.prototype.cancelPolling = function() { - return this.cancel = true; - }; - - MergeRequestWidget.prototype.addEventListeners = function() { - var allowedPages; - allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; - return $(document).on('page:change.merge_request', (function(_this) { - return function() { - var page; - page = $('body').data('page').split(':').last(); - if (allowedPages.indexOf(page) < 0) { - clearInterval(_this.fetchBuildStatusInterval); - _this.cancelPolling(); - return _this.clearEventListeners(); - } - }; - })(this)); - }; - - MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { - if (deleteSourceBranch == null) { - deleteSourceBranch = false; - } - return $.ajax({ - type: 'GET', - url: $('.merge-request').data('url'), - success: (function(_this) { - return function(data) { - var callback, urlSuffix; - if (data.state === "merged") { - urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; - return window.location.href = window.location.pathname + urlSuffix; - } else if (data.merge_error) { - return $('.mr-widget-body').html("

    " + data.merge_error + "

    "); - } else { - callback = function() { - return merge_request_widget.mergeInProgress(deleteSourceBranch); - }; - return setTimeout(callback, 2000); - } - }; - })(this), - dataType: 'json' - }); - }; - - MergeRequestWidget.prototype.getMergeStatus = function() { - return $.get(this.opts.merge_check_url, function(data) { - return $('.mr-state-widget').replaceWith(data); - }); - }; - - MergeRequestWidget.prototype.ciLabelForStatus = function(status) { - switch (status) { - case 'success': - return 'passed'; - case 'success_with_warnings': - return 'passed with warnings'; - default: - return status; - } - }; - - MergeRequestWidget.prototype.pollCIStatus = function() { - return this.fetchBuildStatusInterval = setInterval(((function(_this) { - return function() { - if (!_this.readyForCICheck) { - return; - } - _this.getCIStatus(true); - return _this.readyForCICheck = false; - }; - })(this)), 10000); - }; - - MergeRequestWidget.prototype.getCIStatus = function(showNotification) { - var _this; - _this = this; - $('.ci-widget-fetching').show(); - return $.getJSON(this.opts.ci_status_url, (function(_this) { - return function(data) { - var message, status, title; - if (_this.cancel) { - return; - } - _this.readyForCICheck = true; - if (data.status === '') { - return; - } - if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { - _this.opts.ci_status = data.status; - _this.showCIStatus(data.status); - if (data.coverage) { - _this.showCICoverage(data.coverage); - } - // The first check should only update the UI, a notification - // should only be displayed on status changes - if (showNotification && !_this.firstCICheck) { - status = _this.ciLabelForStatus(data.status); - if (status === "preparing") { - title = _this.opts.ci_title.preparing; - status = status.charAt(0).toUpperCase() + status.slice(1); - message = _this.opts.ci_message.preparing.replace('{{status}}', status); - } else { - title = _this.opts.ci_title.normal; - message = _this.opts.ci_message.normal.replace('{{status}}', status); - } - title = title.replace('{{status}}', status); - message = message.replace('{{sha}}', data.sha); - message = message.replace('{{title}}', data.title); - notify(title, message, _this.opts.gitlab_icon, function() { - this.close(); - return Turbolinks.visit(_this.opts.builds_path); - }); - } - return _this.firstCICheck = false; - } - }; - })(this)); - }; - - MergeRequestWidget.prototype.showCIStatus = function(state) { - var allowed_states; - if (state == null) { - return; - } - $('.ci_widget').hide(); - allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; - if (indexOf.call(allowed_states, state) >= 0) { - $('.ci_widget.ci-' + state).show(); - switch (state) { - case "failed": - case "canceled": - case "not_found": - return this.setMergeButtonClass('btn-danger'); - case "running": - return this.setMergeButtonClass('btn-warning'); - case "success": - case "success_with_warnings": - return this.setMergeButtonClass('btn-create'); - } - } else { - $('.ci_widget.ci-error').show(); - return this.setMergeButtonClass('btn-danger'); - } - }; - - MergeRequestWidget.prototype.showCICoverage = function(coverage) { - var text; - text = 'Coverage ' + coverage + '%'; - return $('.ci_widget:visible .ci-coverage').text(text); - }; - - MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { - return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); - }; - - return MergeRequestWidget; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 new file mode 100644 index 00000000000..7c727cf214f --- /dev/null +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -0,0 +1,240 @@ + ((global) => { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + const DEPLOYMENT_TEMPLATE = `
    +
    + <%= ci_success_icon %> + + Deployed to + + <%- name %> + + + <%- deployed_at %> + + + + View on <%- external_url_formatted %> + + +
    +
    `; + + global.MergeRequestWidget = (function() { + function MergeRequestWidget(opts) { + // Initialize MergeRequestWidget behavior + // + // check_enable - Boolean, whether to check automerge status + // merge_check_url - String, URL to use to check automerge status + // ci_status_url - String, URL to use to check CI status + // + this.opts = opts; + this.$widgetBody = $('.mr-widget-body'); + $('#modal_merge_info').modal({ + show: false + }); + this.firstCICheck = true; + this.readyForCICheck = false; + this.cancel = false; + clearInterval(this.fetchBuildStatusInterval); + this.clearEventListeners(); + this.addEventListeners(); + this.getCIStatus(false); + this.retrieveSuccessIcon(); + this.pollCIStatus(); + notifyPermissions(); + } + + MergeRequestWidget.prototype.clearEventListeners = function() { + return $(document).off('page:change.merge_request'); + }; + + MergeRequestWidget.prototype.cancelPolling = function() { + return this.cancel = true; + }; + + MergeRequestWidget.prototype.addEventListeners = function() { + var allowedPages; + allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; + return $(document).on('page:change.merge_request', (function(_this) { + return function() { + var page; + page = $('body').data('page').split(':').last(); + if (allowedPages.indexOf(page) < 0) { + clearInterval(_this.fetchBuildStatusInterval); + _this.cancelPolling(); + return _this.clearEventListeners(); + } + }; + })(this)); + }; + + MergeRequestWidget.prototype.retrieveSuccessIcon = function() { + const $ciSuccessIcon = $('.js-success-icon'); + this.$ciSuccessIcon = $ciSuccessIcon.html(); + $ciSuccessIcon.remove(); + } + + MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { + if (deleteSourceBranch == null) { + deleteSourceBranch = false; + } + return $.ajax({ + type: 'GET', + url: $('.merge-request').data('url'), + success: (function(_this) { + return function(data) { + var callback, urlSuffix; + if (data.state === "merged") { + urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; + return window.location.href = window.location.pathname + urlSuffix; + } else if (data.merge_error) { + return this.$widgetBody.html("

    " + data.merge_error + "

    "); + } else { + callback = function() { + return merge_request_widget.mergeInProgress(deleteSourceBranch); + }; + return setTimeout(callback, 2000); + } + }; + })(this), + dataType: 'json' + }); + }; + + MergeRequestWidget.prototype.getMergeStatus = function() { + return $.get(this.opts.merge_check_url, function(data) { + return $('.mr-state-widget').replaceWith(data); + }); + }; + + MergeRequestWidget.prototype.ciLabelForStatus = function(status) { + switch (status) { + case 'success': + return 'passed'; + case 'success_with_warnings': + return 'passed with warnings'; + default: + return status; + } + }; + + MergeRequestWidget.prototype.pollCIStatus = function() { + return this.fetchBuildStatusInterval = setInterval(((function(_this) { + return function() { + if (!_this.readyForCICheck) { + return; + } + _this.getCIStatus(true); + return _this.readyForCICheck = false; + }; + })(this)), 10000); + }; + + MergeRequestWidget.prototype.getCIStatus = function(showNotification) { + var _this; + _this = this; + $('.ci-widget-fetching').show(); + return $.getJSON(this.opts.ci_status_url, (function(_this) { + return function(data) { + var message, status, title; + if (_this.cancel) { + return; + } + _this.readyForCICheck = true; + if (data.status === '') { + return; + } + if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); + if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { + _this.opts.ci_status = data.status; + _this.showCIStatus(data.status); + if (data.coverage) { + _this.showCICoverage(data.coverage); + } + // The first check should only update the UI, a notification + // should only be displayed on status changes + if (showNotification && !_this.firstCICheck) { + status = _this.ciLabelForStatus(data.status); + if (status === "preparing") { + title = _this.opts.ci_title.preparing; + status = status.charAt(0).toUpperCase() + status.slice(1); + message = _this.opts.ci_message.preparing.replace('{{status}}', status); + } else { + title = _this.opts.ci_title.normal; + message = _this.opts.ci_message.normal.replace('{{status}}', status); + } + title = title.replace('{{status}}', status); + message = message.replace('{{sha}}', data.sha); + message = message.replace('{{title}}', data.title); + notify(title, message, _this.opts.gitlab_icon, function() { + this.close(); + return Turbolinks.visit(_this.opts.builds_path); + }); + } + return _this.firstCICheck = false; + } + }; + })(this)); + }; + + MergeRequestWidget.prototype.renderEnvironments = function(environments) { + for (let i = 0; i < environments.length; i++) { + const environment = environments[i]; + if ($(`.mr-state-widget #${ environment.id }`).length) return; + const $template = $(DEPLOYMENT_TEMPLATE); + if (!environment.external_url) $('.js-environment-link', $template).remove(); + if (environment.deployed_at) { + environment.deployed_at = $.timeago(environment.deployed_at) + '.'; + } else { + $('.js-environment-timeago', $template).remove(); + environment.name += '.'; + } + environment.ci_success_icon = this.$ciSuccessIcon; + const templateString = _.unescape($template[0].outerHTML); + const template = _.template(templateString)(environment) + this.$widgetBody.before(template); + } + }; + + MergeRequestWidget.prototype.showCIStatus = function(state) { + var allowed_states; + if (state == null) { + return; + } + $('.ci_widget').hide(); + allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; + if (indexOf.call(allowed_states, state) >= 0) { + $('.ci_widget.ci-' + state).show(); + switch (state) { + case "failed": + case "canceled": + case "not_found": + return this.setMergeButtonClass('btn-danger'); + case "running": + return this.setMergeButtonClass('btn-warning'); + case "success": + case "success_with_warnings": + return this.setMergeButtonClass('btn-create'); + } + } else { + $('.ci_widget.ci-error').show(); + return this.setMergeButtonClass('btn-danger'); + } + }; + + MergeRequestWidget.prototype.showCICoverage = function(coverage) { + var text; + text = 'Coverage ' + coverage + '%'; + return $('.ci_widget:visible .ci-coverage').text(text); + }; + + MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { + return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); + }; + + return MergeRequestWidget; + + })(); + + })(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7cf69c56d15..96d5547154d 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -121,6 +121,10 @@ color: #5c5d5e; } + .js-deployment-link { + display: inline-block; + } + .mr-widget-body { h4 { font-weight: 600; diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 869d96b86f4..28225fbb762 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -393,11 +393,40 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + environments = @merge_request.environments + deployments = @merge_request.deployments + + if environments.present? + environments = environments.select { |e| can?(current_user, :read_environment, e) }.map do |environment| + project = environment.project + deployment = deployments.find { |d| d.environment == environment } + + environment = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(project.namespace, project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } + + if environment[:external_url] + environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + end + + if environment[:deployed_at] + environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) + end + + environment + end + end + response = { title: merge_request.title, sha: merge_request.diff_head_commit.short_id, status: status, - coverage: coverage + coverage: coverage, + environments: environments } render json: response diff --git a/app/models/environment.rb b/app/models/environment.rb index f0f3ee23223..1c7d06906f3 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,6 +48,14 @@ class Environment < ActiveRecord::Base self.name == "production" end + def deployment_id_for(commit) + ref = project.repository.ref_name_for_sha(ref_path, commit.sha) + + return nil unless ref + + ref.split('/').last.to_i + end + def ref_path "refs/environments/#{Shellwords.shellescape(name)}" end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..ec8c09f83db 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -685,6 +685,15 @@ class MergeRequest < ActiveRecord::Base !pipeline || pipeline.success? end + def deployments + deployment_ids = + environments.map do |environment| + environment.deployment_id_for(diff_head_commit) + end.compact + + Deployments.find(deployment_ids) + end + def environments return [] unless diff_head_commit diff --git a/app/models/repository.rb b/app/models/repository.rb index 608c99eed46..37833cf004f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -719,6 +719,14 @@ class Repository end end + def ref_name_for_sha(environment_ref_path, sha) + args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{environment_ref_path} --contains #{sha}) + + # Not found -> ["", 0] + # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] + Gitlab::Popen.popen(args, path_to_repo).first.split.last + end + def refs_contains_sha(ref_type, sha) args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) names = Gitlab::Popen.popen(args, path_to_repo).first diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 5b7f83c344f..cda8f0b7de6 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -44,17 +44,5 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. -- @merge_request.environments.sort_by(&:name).each do |environment| - - if can?(current_user, :read_environment, environment) - .mr-widget-heading - .ci_widget.ci-success - = ci_icon_for_status("success") - %span - Deployed to - = succeed '.' do - = link_to environment.name, environment_path(environment), class: 'environment' - - external_url = environment.external_url - - if external_url - = link_to external_url, target: '_blank' do - %span.hidden-xs View on #{external_url.gsub(/\A.*?:\/\//, '')} - = icon('external-link', right: true) + .js-success-icon.hidden + = ci_icon_for_status('success') diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index ea618263a4a..856ec1e0bee 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -33,4 +33,4 @@ merge_request_widget.clearEventListeners(); } - merge_request_widget = new MergeRequestWidget(opts); + merge_request_widget = new window.gl.MergeRequestWidget(opts); diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb new file mode 100644 index 00000000000..8e23ec50d4a --- /dev/null +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +feature 'Widget Deployments Header', feature: true, js: true do + include WaitForAjax + + describe 'when deployed to an environment' do + let(:project) { merge_request.target_project } + let(:merge_request) { create(:merge_request, :merged) } + let(:environment) { create(:environment, project: project) } + let!(:deployment) do + create(:deployment, environment: environment, sha: project.commit('master').id) + end + + before do + login_as :admin + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'displays that the environment is deployed' do + wait_for_ajax + + expect(page).to have_content("Deployed to #{environment.name}") + expect(find('.ci_widget > span > span')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) + end + end +end diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 17b32914ec3..75ef10939de 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,5 +1,5 @@ - /*= require merge_request_widget */ +/*= require lib/utils/jquery.timeago.js */ (function() { describe('MergeRequestWidget', function() { @@ -20,7 +20,7 @@ gitlab_icon: "gitlab_logo.png", builds_path: "http://sampledomain.local/sampleBuildsPath" }; - this["class"] = new MergeRequestWidget(this.opts); + this["class"] = new window.gl.MergeRequestWidget(this.opts); return this.ciStatusData = { "title": "Sample MR title", "sha": "12a34bc5", @@ -30,7 +30,7 @@ }); return describe('getCIStatus', function() { beforeEach(function() { - return spyOn(jQuery, 'getJSON').and.callFake((function(_this) { + spyOn(jQuery, 'getJSON').and.callFake((function(_this) { return function(req, cb) { return cb(_this.ciStatusData); }; @@ -61,13 +61,30 @@ this["class"].getCIStatus(false); return expect(spy).not.toHaveBeenCalled(); }); - return it('should not display a notification on the first check after the widget has been created', function() { + it('should not display a notification on the first check after the widget has been created', function() { var spy; spy = spyOn(window, 'notify'); - this["class"] = new MergeRequestWidget(this.opts); + this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); + it('should call renderEnvironments when the environments property is set', function() { + this.ciStatusData.environments = [{ + created_at: '2016-09-12T13:38:30.636Z', + environment_id: 1, + environment_name: 'env1', + external_url: 'https://test-url.com', + external_url_formatted: 'test-url.com' + }]; + var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); + this['class'].getCIStatus(false); + expect(spy).toHaveBeenCalledWith(this.ciStatusData.environments); + }); + it('should not call renderEnvironments when the environments property is not set', function() { + var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); + this['class'].getCIStatus(false); + expect(spy).not.toHaveBeenCalled(); + }); }); }); diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 6b1867a44e1..fb9629ac47a 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -64,6 +64,23 @@ describe Environment, models: true do end end + describe '#deployment_id_for' do + let(:project) { create(:project) } + let!(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } + let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } + let(:head_commit) { project.commit } + let(:commit) { project.commit.parent } + + it 'returns deployment id for the environment' do + expect(environment.deployment_id_for(commit)).to eq deployment1.id + end + + it 'return nil when no deployment is found' do + expect(environment.deployment_id_for(head_commit)).to eq nil + end + end + describe '#environment_type' do subject { environment.environment_type } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4b80efbe12b..f977cf73673 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -7,15 +7,18 @@ describe Repository, models: true do let(:project) { create(:project) } let(:repository) { project.repository } let(:user) { create(:user) } + let(:commit_options) do author = repository.user_to_committer(user) { message: 'Test message', committer: author, author: author } end + let(:merge_commit) do merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) merge_commit_id = repository.merge(user, merge_request, commit_options) repository.commit(merge_commit_id) end + let(:author_email) { FFaker::Internet.email } # I have to remove periods from the end of the name @@ -90,6 +93,26 @@ describe Repository, models: true do end end + describe '#ref_name_for_sha' do + context 'ref found' do + it 'returns the ref' do + allow_any_instance_of(Gitlab::Popen).to receive(:popen). + and_return(["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]) + + expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq 'refs/environments/production/77' + end + end + + context 'ref not found' do + it 'returns nil' do + allow_any_instance_of(Gitlab::Popen).to receive(:popen). + and_return(["", 0]) + + expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq nil + end + end + end + describe '#last_commit_for_path' do subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb deleted file mode 100644 index 86980f59cd8..00000000000 --- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe 'projects/merge_requests/widget/_heading' do - include Devise::Test::ControllerHelpers - - context 'when released to an environment' do - let(:project) { merge_request.target_project } - let(:merge_request) { create(:merge_request, :merged) } - let(:environment) { create(:environment, project: project) } - let!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end - - before do - assign(:merge_request, merge_request) - assign(:project, project) - - allow(view).to receive(:can?).and_return(true) - - render - end - - it 'displays that the environment is deployed' do - expect(rendered).to match("Deployed to") - expect(rendered).to match("#{environment.name}") - end - end -end -- cgit v1.2.1 From 8a1064d76252e16b64b6e4f03d819b6dc20e1d6d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 4 Oct 2016 18:50:25 +0200 Subject: Fix indenting error in HAML --- app/models/merge_request.rb | 2 +- app/views/projects/merge_requests/widget/_heading.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ec8c09f83db..0e427378bac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -691,7 +691,7 @@ class MergeRequest < ActiveRecord::Base environment.deployment_id_for(diff_head_commit) end.compact - Deployments.find(deployment_ids) + Deployment.find(deployment_ids) end def environments diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index cda8f0b7de6..a82c846baa7 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -44,5 +44,5 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. - .js-success-icon.hidden - = ci_icon_for_status('success') +.js-success-icon.hidden + = ci_icon_for_status('success') -- cgit v1.2.1 From fa58068b2894b900d4b2519825411e0710557fc6 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 5 Oct 2016 13:52:15 +0200 Subject: Refactor ci_status on MergeRequestController --- app/assets/stylesheets/pages/merge_requests.scss | 4 +- .../projects/merge_requests_controller.rb | 45 ++++++++++------------ app/models/environment.rb | 5 ++- app/models/merge_request.rb | 22 ++++------- app/models/repository.rb | 4 +- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 96d5547154d..6a0fae8a3f9 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -122,8 +122,8 @@ } .js-deployment-link { - display: inline-block; - } + display: inline-block; + } .mr-widget-body { h4 { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 28225fbb762..00043c5a4c0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -393,33 +393,28 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - environments = @merge_request.environments - deployments = @merge_request.deployments - - if environments.present? - environments = environments.select { |e| can?(current_user, :read_environment, e) }.map do |environment| - project = environment.project - deployment = deployments.find { |d| d.environment == environment } - - environment = { - name: environment.name, - id: environment.id, - url: namespace_project_environment_path(project.namespace, project, environment), - external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil - } - - if environment[:external_url] - environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') - end - - if environment[:deployed_at] - environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) - end + environments = @merge_request.environments.map do |environment| + next unless can?(current_user, :read_environment, environment) + + deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + environment = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(@project.namespace, @project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } + + if environment[:external_url] + environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + end - environment + if environment[:deployed_at] + environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) end - end + + environment + end.compact response = { title: merge_request.title, diff --git a/app/models/environment.rb b/app/models/environment.rb index 1c7d06906f3..c6cae81ce6a 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,12 +48,13 @@ class Environment < ActiveRecord::Base self.name == "production" end - def deployment_id_for(commit) + def first_deployment_for(commit) ref = project.repository.ref_name_for_sha(ref_path, commit.sha) return nil unless ref - ref.split('/').last.to_i + deployment_id = ref.split('/').last.to_i + deployments.find(deployment_id) end def ref_path diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0e427378bac..5ccfe11a2a2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -685,24 +685,18 @@ class MergeRequest < ActiveRecord::Base !pipeline || pipeline.success? end - def deployments - deployment_ids = - environments.map do |environment| - environment.deployment_id_for(diff_head_commit) - end.compact - - Deployment.find(deployment_ids) - end - def environments return [] unless diff_head_commit - environments = source_project.environments_for( - source_branch, diff_head_commit) - environments += target_project.environments_for( - target_branch, diff_head_commit, with_tags: true) + @environments ||= + begin + environments = source_project.environments_for( + source_branch, diff_head_commit) + environments += target_project.environments_for( + target_branch, diff_head_commit, with_tags: true) - environments.uniq + environments.uniq + end end def state_human_name diff --git a/app/models/repository.rb b/app/models/repository.rb index 37833cf004f..72e473871fa 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -719,8 +719,8 @@ class Repository end end - def ref_name_for_sha(environment_ref_path, sha) - args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{environment_ref_path} --contains #{sha}) + def ref_name_for_sha(ref_path, sha) + args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha}) # Not found -> ["", 0] # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] -- cgit v1.2.1 From 58368fbc53bfe7c2a9b425626819eae576afff09 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 23:10:06 +0100 Subject: Moved ci_status environments logic to new action ci_envrionments_status and set up frontend polling --- app/assets/javascripts/merge_request_widget.js.es6 | 53 +++++++++++++------ .../projects/merge_requests_controller.rb | 38 +++++++------- .../projects/merge_requests/widget/_show.html.haml | 1 + config/routes.rb | 5 -- config/routes/project.rb | 1 + spec/javascripts/merge_request_widget_spec.js | 60 +++++++++++++--------- 6 files changed, 96 insertions(+), 62 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 7c727cf214f..8354f7ab8a4 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -35,13 +35,17 @@ }); this.firstCICheck = true; this.readyForCICheck = false; + this.readyForCIEnvironmentCheck = false; this.cancel = false; clearInterval(this.fetchBuildStatusInterval); + clearInterval(this.fetchBuildEnvironmentStatusInterval); this.clearEventListeners(); this.addEventListeners(); this.getCIStatus(false); + this.getCIEnvironmentsStatus(); this.retrieveSuccessIcon(); this.pollCIStatus(); + this.pollCIEnvironmentsStatus(); notifyPermissions(); } @@ -62,6 +66,7 @@ page = $('body').data('page').split(':').last(); if (allowedPages.indexOf(page) < 0) { clearInterval(_this.fetchBuildStatusInterval); + clearInterval(_this.fetchBuildEnvironmentStatusInterval); _this.cancelPolling(); return _this.clearEventListeners(); } @@ -178,23 +183,39 @@ })(this)); }; + MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() { + this.fetchBuildEnvironmentStatusInterval = setInterval(() => { + if (!this.readyForCIEnvironmentCheck) return; + this.getCIEnvironmentsStatus(); + this.readyForCIEnvironmentCheck = false; + }, 300000); + }; + + MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { + $.getJSON(this.opts.ci_environments_status_url, (environments) => { + if (this.cancel) return; + this.readyForCIEnvironmentCheck = true; + if (environments && environments.length) this.renderEnvironments(environments); + }); + }; + MergeRequestWidget.prototype.renderEnvironments = function(environments) { - for (let i = 0; i < environments.length; i++) { - const environment = environments[i]; - if ($(`.mr-state-widget #${ environment.id }`).length) return; - const $template = $(DEPLOYMENT_TEMPLATE); - if (!environment.external_url) $('.js-environment-link', $template).remove(); - if (environment.deployed_at) { - environment.deployed_at = $.timeago(environment.deployed_at) + '.'; - } else { - $('.js-environment-timeago', $template).remove(); - environment.name += '.'; - } - environment.ci_success_icon = this.$ciSuccessIcon; - const templateString = _.unescape($template[0].outerHTML); - const template = _.template(templateString)(environment) - this.$widgetBody.before(template); - } + for (let i = 0; i < environments.length; i++) { + const environment = environments[i]; + if ($(`.mr-state-widget #${ environment.id }`).length) return; + const $template = $(DEPLOYMENT_TEMPLATE); + if (!environment.external_url) $('.js-environment-link', $template).remove(); + if (environment.deployed_at) { + environment.deployed_at = $.timeago(environment.deployed_at) + '.'; + } else { + $('.js-environment-timeago', $template).remove(); + environment.name += '.'; + } + environment.ci_success_icon = this.$ciSuccessIcon; + const templateString = _.unescape($template[0].outerHTML); + const template = _.template(templateString)(environment) + this.$widgetBody.before(template); + } }; MergeRequestWidget.prototype.showCIStatus = function(state) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 00043c5a4c0..e17d560138f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues + :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -393,11 +393,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - environments = @merge_request.environments.map do |environment| + response = { + title: merge_request.title, + sha: merge_request.diff_head_commit.short_id, + status: status, + coverage: coverage + } + + render json: response + end + + def ci_environments_status + render json: @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) deployment = environment.first_deployment_for(@merge_request.diff_head_commit) - environment = { + + environment_data = { name: environment.name, id: environment.id, url: namespace_project_environment_path(@project.namespace, @project, environment), @@ -405,26 +417,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController deployed_at: deployment ? deployment.created_at : nil } - if environment[:external_url] - environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + if environment_data[:external_url] + environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') end - if environment[:deployed_at] - environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) + if environment_data[:deployed_at] + environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) end - environment + environment_data end.compact - - response = { - title: merge_request.title, - sha: merge_request.diff_head_commit.short_id, - status: status, - coverage: coverage, - environments: environments - } - - render json: response end protected diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 856ec1e0bee..608fdf1c5f5 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -12,6 +12,7 @@ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", + ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", ci_message: { diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..68dc84d9c9e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,9 +83,4 @@ Rails.application.routes.draw do draw :group draw :user draw :project - - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - - root to: "root#index" end diff --git a/config/routes/project.rb b/config/routes/project.rb index f9d58f5d5b2..200922b74db 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -273,6 +273,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :merge post :cancel_merge_when_build_succeeds get :ci_status + get :ci_environments_status post :toggle_subscription post :remove_wip get :diff_for_path diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 75ef10939de..7b20572742c 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -8,6 +8,7 @@ window.notify = function() {}; this.opts = { ci_status_url: "http://sampledomain.local/ci/getstatus", + ci_environments_status_url: "http://sampledomain.local/ci/getenvironmentsstatus", ci_status: "", ci_message: { normal: "Build {{status}} for \"{{title}}\"", @@ -21,15 +22,45 @@ builds_path: "http://sampledomain.local/sampleBuildsPath" }; this["class"] = new window.gl.MergeRequestWidget(this.opts); - return this.ciStatusData = { - "title": "Sample MR title", - "sha": "12a34bc5", - "status": "success", - "coverage": 98 - }; }); + + describe('getCIEnvironmentsStatus', function() { + beforeEach(function() { + this.ciEnvironmentsStatusData = { + created_at: '2016-09-12T13:38:30.636Z', + environment_id: 1, + environment_name: 'env1', + external_url: 'https://test-url.com', + external_url_formatted: 'test-url.com' + }; + + spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { + cb(this.ciEnvironmentsStatusData); + }); + }); + + it('should call renderEnvironments when the environments property is set', function() { + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData); + }); + + it('should not call renderEnvironments when the environments property is not set', function() { + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).not.toHaveBeenCalled(); + }); + }); + return describe('getCIStatus', function() { beforeEach(function() { + this.ciStatusData = { + "title": "Sample MR title", + "sha": "12a34bc5", + "status": "success", + "coverage": 98 + }; + spyOn(jQuery, 'getJSON').and.callFake((function(_this) { return function(req, cb) { return cb(_this.ciStatusData); @@ -68,23 +99,6 @@ this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); - it('should call renderEnvironments when the environments property is set', function() { - this.ciStatusData.environments = [{ - created_at: '2016-09-12T13:38:30.636Z', - environment_id: 1, - environment_name: 'env1', - external_url: 'https://test-url.com', - external_url_formatted: 'test-url.com' - }]; - var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); - this['class'].getCIStatus(false); - expect(spy).toHaveBeenCalledWith(this.ciStatusData.environments); - }); - it('should not call renderEnvironments when the environments property is not set', function() { - var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); - this['class'].getCIStatus(false); - expect(spy).not.toHaveBeenCalled(); - }); }); }); -- cgit v1.2.1 From 88b03bb542a8480d61c260a9dc3769ab791995e5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 10 Oct 2016 20:38:48 +0200 Subject: Rename method in test --- .../projects/merge_requests_controller.rb | 41 ++++++++++++---------- config/routes.rb | 5 +++ spec/models/environment_spec.rb | 6 ++-- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e17d560138f..5f7f46cf566 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -404,29 +404,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_environments_status - render json: @merge_request.environments.map do |environment| - next unless can?(current_user, :read_environment, environment) - - deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + environments = + begin + @merge_request.environments.map do |environment| + next unless can?(current_user, :read_environment, environment) + + deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + + environment_data = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(@project.namespace, @project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } - environment_data = { - name: environment.name, - id: environment.id, - url: namespace_project_environment_path(@project.namespace, @project, environment), - external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil - } + if environment_data[:external_url] + environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') + end - if environment_data[:external_url] - environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') - end + if environment_data[:deployed_at] + environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) + end - if environment_data[:deployed_at] - environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) + environment_data + end.compact end - environment_data - end.compact + render json: environments end protected diff --git a/config/routes.rb b/config/routes.rb index 68dc84d9c9e..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,4 +83,9 @@ Rails.application.routes.draw do draw :group draw :user draw :project + + # Get all keys of user + get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + + root to: "root#index" end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index fb9629ac47a..e172ee8e590 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -64,7 +64,7 @@ describe Environment, models: true do end end - describe '#deployment_id_for' do + describe '#first_deployment_for' do let(:project) { create(:project) } let!(:environment) { create(:environment, project: project) } let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } @@ -73,11 +73,11 @@ describe Environment, models: true do let(:commit) { project.commit.parent } it 'returns deployment id for the environment' do - expect(environment.deployment_id_for(commit)).to eq deployment1.id + expect(environment.first_deployment_for(commit)).to eq deployment1 end it 'return nil when no deployment is found' do - expect(environment.deployment_id_for(head_commit)).to eq nil + expect(environment.first_deployment_for(head_commit)).to eq nil end end -- cgit v1.2.1 From 4b40027b50a2be4bad76b0a4a6a5e92c0de14255 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 11 Oct 2016 18:48:43 +0100 Subject: Fixed conflict and corrected teaspoon test --- spec/javascripts/merge_request_widget_spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 7b20572742c..c9175e2b704 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -26,13 +26,13 @@ describe('getCIEnvironmentsStatus', function() { beforeEach(function() { - this.ciEnvironmentsStatusData = { + this.ciEnvironmentsStatusData = [{ created_at: '2016-09-12T13:38:30.636Z', environment_id: 1, environment_name: 'env1', external_url: 'https://test-url.com', external_url_formatted: 'test-url.com' - }; + }]; spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { cb(this.ciEnvironmentsStatusData); @@ -46,6 +46,7 @@ }); it('should not call renderEnvironments when the environments property is not set', function() { + this.ciEnvironmentsStatusData = null; const spy = spyOn(this.class, 'renderEnvironments').and.stub(); this.class.getCIEnvironmentsStatus(); expect(spy).not.toHaveBeenCalled(); -- cgit v1.2.1 From 35e2315a66e34f290230720cfa74240fd5532970 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 12 Oct 2016 15:21:01 +0200 Subject: Minor style improvement --- .../projects/merge_requests_controller.rb | 20 ++++++-------------- app/models/deployment.rb | 4 ++++ app/models/environment.rb | 8 +++++++- db/schema.rb | 12 +++++++++++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5f7f46cf566..5c802798028 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -404,30 +404,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_environments_status - environments = + environments = begin @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) deployment = environment.first_deployment_for(@merge_request.diff_head_commit) - environment_data = { - name: environment.name, + { id: environment.id, + name: environment.name, url: namespace_project_environment_path(@project.namespace, @project, environment), external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil + external_url_formatted: environment.formatted_external_url, + deployed_at: deployment.try(:created_at), + deployed_at_formatted: deployment.try(:formatted_deployment_time) } - - if environment_data[:external_url] - environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') - end - - if environment_data[:deployed_at] - environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) - end - - environment_data end.compact end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f63cc179b9e..3d9902d496e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -84,6 +84,10 @@ class Deployment < ActiveRecord::Base take end + def formatted_deployment_time + created_at.to_time.in_time_zone.to_s(:medium) + end + private def ref_path diff --git a/app/models/environment.rb b/app/models/environment.rb index c6cae81ce6a..d970bc0a005 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -53,11 +53,17 @@ class Environment < ActiveRecord::Base return nil unless ref - deployment_id = ref.split('/').last.to_i + deployment_id = ref.split('/').last deployments.find(deployment_id) end def ref_path "refs/environments/#{Shellwords.shellescape(name)}" end + + def formatted_external_url + return nil unless external_url + + external_url.gsub(/\A.*?:\/\//, '') + end end diff --git a/db/schema.rb b/db/schema.rb index a362fd8f228..0bb03dfe4bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -446,6 +446,17 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree + create_table "integrations", force: :cascade do |t| + t.integer "project_id" + t.string "name" + t.string "external_token" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "integrations", ["external_token"], name: "index_integrations_on_external_token", unique: true, using: :btree + add_index "integrations", ["project_id"], name: "index_integrations_on_project_id", using: :btree + create_table "issue_metrics", force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -613,7 +624,6 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.datetime "updated_at", null: false end - add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| -- cgit v1.2.1 From 6e509ae3b60eb0fbe4bddb7dbb42beaf47817f3b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Oct 2016 13:00:06 +0100 Subject: Added safety check for formatted values --- app/assets/javascripts/merge_request_widget.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 8354f7ab8a4..fcadc4bc515 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -204,8 +204,8 @@ const environment = environments[i]; if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); - if (!environment.external_url) $('.js-environment-link', $template).remove(); - if (environment.deployed_at) { + if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = $.timeago(environment.deployed_at) + '.'; } else { $('.js-environment-timeago', $template).remove(); -- cgit v1.2.1 From b8003aa012843fac1745c94788b993c36570fbd9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 11:47:54 +0200 Subject: Calculate build coverage asynchronously --- app/models/ci/build.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ff70f3deb52..23119c5ed61 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -82,9 +82,8 @@ module Ci end after_transition any => [:success, :failed, :canceled] do |build| - build.update_coverage - build.run_after_commit do + BuildCoverageWorker.perform_async(id) BuildHooksWorker.perform_async(id) end end -- cgit v1.2.1 From d1a7b35b167b747a4794110dc1354200fd6dc592 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 10:49:23 +0100 Subject: Updated issuable dropdown titles Closes #23337 --- app/views/shared/issuable/_form.html.haml | 6 +++--- app/views/shared/issuable/_label_dropdown.html.haml | 3 ++- app/views/shared/issuable/_milestone_dropdown.html.haml | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c3f4e10c954..a75653d842f 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -85,20 +85,20 @@ .issuable-form-select-holder - if issuable.assignee_id = f.hidden_field :assignee_id - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 6d307611640..22b5a6aa11b 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -8,6 +8,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - selected_toggle = local_assigns.fetch(:selected_toggle, nil) +- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") - dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options @@ -23,7 +24,7 @@ = multi_label_name(selected, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } + = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } - if show_create && project && can?(current_user, :admin_label, project) = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index ab3cc33d18f..b85e2c11ff0 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,9 +2,10 @@ - extra_class = extra_class || '' - show_menu_above = show_menu_above || false - selected_text = selected.try(:title) +- dropdown_title = dropdown_title || "Filter by milestone" - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) -= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list -- cgit v1.2.1 From 270434f7372dcb4d412f3f62068f69d8eac212e8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 13 Oct 2016 08:34:35 +0100 Subject: Loads GFM once for per page #22827 --- CHANGELOG | 1 + app/views/layouts/application.html.haml | 1 + app/views/projects/_zen.html.haml | 4 +--- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..7c8989b2072 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -116,6 +116,7 @@ v 8.13.0 (unreleased) v 8.12.7 - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + - Fix GFM autocomplete setup being called several times v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 15a94ac23c5..6c2285fa2b6 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,3 +11,4 @@ = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body + = render "layouts/init_auto_complete" if @gfm_form diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index cb97181b9e1..0c8241053e7 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,3 +1,4 @@ +- @gfm_form = true - supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false) .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' @@ -7,6 +8,3 @@ = text_area_tag attr, nil, class: classes, placeholder: placeholder %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') - -- content_for :scripts_body do - = render "layouts/init_auto_complete" if current_user && (@target_project || @project) -- cgit v1.2.1 From 8710cdf57d4160685071a44dc225cb02865634e2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 11:15:44 +0100 Subject: Use local assigns to get the dropdown title --- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index b85e2c11ff0..f27a9002ec2 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,7 +2,7 @@ - extra_class = extra_class || '' - show_menu_above = show_menu_above || false - selected_text = selected.try(:title) -- dropdown_title = dropdown_title || "Filter by milestone" +- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone") - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", -- cgit v1.2.1 From eb4a42e0cef5e89b1d01cfa2a401908ffd09874c Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Fri, 14 Oct 2016 21:26:43 +1100 Subject: Remove changelog item and interpolation --- CHANGELOG | 1 - app/views/profiles/accounts/show.html.haml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f933c298b01..99bbd99726d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - - Remove '/u' prefix form username from Account page (blackst0ne) - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 8ee643f3bcc..e2e974ba072 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -86,7 +86,7 @@ = f.label :username, "Path", class: "label-light" .input-group .input-group-addon - = "#{root_url}" + = root_url = f.text_field :username, required: true, class: 'form-control' .help-block Current path: -- cgit v1.2.1 From cf15af31353b141028717456aa6967c9c11697af Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 13 Oct 2016 14:23:18 +0200 Subject: Add test, fix merge error --- .../projects/merge_requests_controller.rb | 3 ++- db/schema.rb | 12 +-------- .../projects/merge_requests_controller_spec.rb | 30 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5c802798028..9207c954335 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -409,12 +409,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) + project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_commit) { id: environment.id, name: environment.name, - url: namespace_project_environment_path(@project.namespace, @project, environment), + url: namespace_project_environment_path(project.namespace, project, environment), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/db/schema.rb b/db/schema.rb index 0bb03dfe4bc..a362fd8f228 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -446,17 +446,6 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree - create_table "integrations", force: :cascade do |t| - t.integer "project_id" - t.string "name" - t.string "external_token" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "integrations", ["external_token"], name: "index_integrations_on_external_token", unique: true, using: :btree - add_index "integrations", ["project_id"], name: "index_integrations_on_project_id", using: :btree - create_table "issue_metrics", force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -624,6 +613,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.datetime "updated_at", null: false end + add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 84298f8bef4..d509f0f2b96 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -756,4 +756,34 @@ describe Projects::MergeRequestsController do post_assign_issues end end + + describe 'GET ci_environments_status' do + context 'when the environment is from a forked project' do + let!(:forked) { create(:project) } + let!(:environment) { create(:environment, project: forked) } + let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } + let(:json_response) { JSON.parse(response.body) } + let(:admin) { create(:admin) } + + let(:merge_request) do + create(:forked_project_link, forked_to_project: forked, + forked_from_project: project) + + create(:merge_request, source_project: forked, target_project: project) + end + + before do + forked.team << [user, :master] + + get :ci_environments_status, + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project.to_param, + id: merge_request.iid, format: 'json' + end + + it 'links to the environment on that project' do + expect(json_response.first['url']).to match /#{forked.path_with_namespace}/ + end + end + end end -- cgit v1.2.1 From 4c46c9a9738cfa90dd450e70ccf85e470be1d789 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 09:38:20 +0200 Subject: Grapify boards API --- lib/api/boards.rb | 76 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 9b71d335128..b14dd4f6e83 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -3,19 +3,28 @@ module API class Boards < Grape::API before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Get the project board + desc 'Get all project boards' do + detail 'This feature was introduced in 8.13' + success Entities::Board + end get ':id/boards' do authorize!(:read_board, user_project) present user_project.boards, with: Entities::Board end + params do + requires :board_id, type: Integer, desc: 'The ID of a board' + end segment ':id/boards/:board_id' do helpers do def project_board board = user_project.boards.first - if params[:board_id].to_i == board.id + if params[:board_id] == board.id board else not_found!('Board') @@ -27,29 +36,35 @@ module API end end - # Get the lists of a project board - # Does not include `backlog` and `done` lists + desc 'Get the lists of a project board' do + detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13' + success Entities::List + end get '/lists' do authorize!(:read_board, user_project) present board_lists, with: Entities::List end - # Get a list of a project board + desc 'Get a list of a project board' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a list' + end get '/lists/:list_id' do authorize!(:read_board, user_project) present board_lists.find(params[:list_id]), with: Entities::List end - # Create a new board list - # - # Parameters: - # id (required) - The ID of a project - # label_id (required) - The ID of an existing label - # Example Request: - # POST /projects/:id/boards/:board_id/lists + desc 'Create a new board list' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :label_id, type: Integer, desc: 'The ID of an existing label' + end post '/lists' do - required_attributes! [:label_id] - unless user_project.labels.exists?(params[:label_id]) render_api_error!({ error: "Label not found!" }, 400) end @@ -68,21 +83,21 @@ module API end end - # Moves a board list to a new position - # - # Parameters: - # id (required) - The ID of a project - # board_id (required) - The ID of a board - # position (required) - The position of the list - # Example Request: - # PUT /projects/:id/boards/:board_id/lists/:list_id + desc 'Moves a board list to a new position' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a list' + requires :position, type: Integer, desc: 'The position of the list' + end put '/lists/:list_id' do list = project_board.lists.movable.find(params[:list_id]) authorize!(:admin_list, user_project) service = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position].to_i }) + { position: params[:position] }) if service.execute(list) present list, with: Entities::List @@ -91,14 +106,13 @@ module API end end - # Delete a board list - # - # Parameters: - # id (required) - The ID of a project - # board_id (required) - The ID of a board - # list_id (required) - The ID of a board list - # Example Request: - # DELETE /projects/:id/boards/:board_id/lists/:list_id + desc 'Delete a board list' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a board list' + end delete "/lists/:list_id" do authorize!(:admin_list, user_project) -- cgit v1.2.1 From d7324f0ed5181f0eb765871a35eaee587bf25fa9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Oct 2016 14:51:59 +0300 Subject: Move edit group scenario to rspec and refactor groups_spec Signed-off-by: Dmitriy Zaporozhets --- config/routes/group.rb | 1 - features/groups.feature | 5 --- features/steps/groups.rb | 12 ------- spec/features/groups_spec.rb | 78 ++++++++++++++++++++++++-------------------- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/config/routes/group.rb b/config/routes/group.rb index 8bee2bb1eb4..33143f0dfa2 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -3,7 +3,6 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do scope(path: ':id', as: :group, controller: :groups) do get '/', action: :show - post '/', action: :create patch '/', action: :update put '/', action: :update delete '/', action: :destroy diff --git a/features/groups.feature b/features/groups.feature index 49e939807b5..4044bd9be79 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -39,11 +39,6 @@ Feature: Groups When I visit group "Owned" merge requests page Then I should not see merge requests from the archived project - Scenario: I should see edit group "Owned" page - When I visit group "Owned" settings page - And I change group "Owned" name to "new-name" - Then I should see new group "Owned" name - Scenario: I edit group "Owned" avatar When I visit group "Owned" settings page And I change group "Owned" avatar diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 4fa7d7c6567..0e81e99120b 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -73,18 +73,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps author: current_user end - step 'I change group "Owned" name to "new-name"' do - fill_in 'group_name', with: 'new-name' - fill_in 'group_path', with: 'new-name' - click_button "Save group" - end - - step 'I should see new group "Owned" name' do - page.within ".navbar-gitlab" do - expect(page).to have_content "new-name" - end - end - step 'I change group "Owned" avatar' do attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) click_button "Save group" diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 3f908294cbb..13bfe90302c 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -11,93 +11,99 @@ feature 'Group', feature: true do end end - describe 'creating a group with space in group path' do - it 'renders new group form with validation errors' do - visit new_group_path - fill_in 'Group path', with: 'space group' + describe 'create a group' do + before { visit new_group_path } - click_button 'Create group' + describe 'with space in group path' do + it 'renders new group form with validation errors' do + fill_in 'Group path', with: 'space group' + click_button 'Create group' - expect(current_path).to eq(groups_path) - expect(page).to have_namespace_error_message + expect(current_path).to eq(groups_path) + expect(page).to have_namespace_error_message + end end - end - - describe 'creating a group with .atom at end of group path' do - it 'renders new group form with validation errors' do - visit new_group_path - fill_in 'Group path', with: 'atom_group.atom' - click_button 'Create group' + describe 'with .atom at end of group path' do + it 'renders new group form with validation errors' do + fill_in 'Group path', with: 'atom_group.atom' + click_button 'Create group' - expect(current_path).to eq(groups_path) - expect(page).to have_namespace_error_message + expect(current_path).to eq(groups_path) + expect(page).to have_namespace_error_message + end end - end - - describe 'creating a group with .git at end of group path' do - it 'renders new group form with validation errors' do - visit new_group_path - fill_in 'Group path', with: 'git_group.git' - click_button 'Create group' + describe 'with .git at end of group path' do + it 'renders new group form with validation errors' do + fill_in 'Group path', with: 'git_group.git' + click_button 'Create group' - expect(current_path).to eq(groups_path) - expect(page).to have_namespace_error_message + expect(current_path).to eq(groups_path) + expect(page).to have_namespace_error_message + end end end - describe 'Group Edit' do + describe 'group edit' do let(:group) { create(:group) } let(:path) { edit_group_path(group) } + let(:new_name) { 'new-name' } - it 'saves new settings' do - expect(group.request_access_enabled).to be_truthy - visit path - - find('#group_request_access_enabled').set(false) + before { visit path } + it 'saves new settings' do + fill_in 'group_name', with: new_name click_button 'Save group' expect(page).to have_content 'successfully updated' - group.reload - expect(group.request_access_enabled).to be_falsey + expect(find('#group_name').value).to eq(new_name) + + page.within ".navbar-gitlab" do + expect(page).to have_content new_name + end end it 'removes group' do - visit path - click_link 'Remove Group' expect(page).to have_content "scheduled for deletion" end end - describe 'description' do + describe 'group page with markdown description' do let(:group) { create(:group) } let(:path) { group_path(group) } it 'parses Markdown' do group.update_attribute(:description, 'This is **my** group') + visit path + expect(page).to have_css('.description > p > strong') end it 'passes through html-pipeline' do group.update_attribute(:description, 'This group is the :poop:') + visit path + expect(page).to have_css('.description > p > img') end it 'sanitizes unwanted tags' do group.update_attribute(:description, '# Group Description') + visit path + expect(page).not_to have_css('.description h1') end it 'permits `rel` attribute on links' do group.update_attribute(:description, 'https://google.com/') + visit path + expect(page).to have_css('.description a[rel]') end end -- cgit v1.2.1 From fd7fb993667265967633a1ac51f0ed17d852ae64 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 5 Oct 2016 18:24:39 -0600 Subject: Remove spinach retry. --- .gitlab-ci.yml | 2 +- Gemfile | 1 - Gemfile.lock | 3 --- features/support/db_cleaner.rb | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3b864f16cf..30471286026 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' + - knapsack spinach artifacts: expire_in: 31d paths: diff --git a/Gemfile b/Gemfile index 5f754c1b66f..563851c25f5 100644 --- a/Gemfile +++ b/Gemfile @@ -278,7 +278,6 @@ group :development, :test do gem 'rspec-rails', '~> 3.5.0' gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' - gem 'spinach-rerun-reporter', '~> 0.0.2' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index a9892d1c130..60d434a1b69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -700,8 +700,6 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spinach-rerun-reporter (0.0.2) - spinach (~> 0.8) spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -964,7 +962,6 @@ DEPENDENCIES simplecov (= 0.12.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) - spinach-rerun-reporter (~> 0.0.2) spring (~> 1.7.0) spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.1.0) diff --git a/features/support/db_cleaner.rb b/features/support/db_cleaner.rb index 1ab308cfa55..8294bb1445f 100644 --- a/features/support/db_cleaner.rb +++ b/features/support/db_cleaner.rb @@ -1,6 +1,6 @@ require 'database_cleaner' -DatabaseCleaner.strategy = :truncation +DatabaseCleaner[:active_record].strategy = :truncation Spinach.hooks.before_scenario do DatabaseCleaner.start -- cgit v1.2.1 From 06528eebb2255a77904d751e9f3934210cd5945b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 13:09:18 +0200 Subject: Re-run specs if failed --- .gitlab-ci.yml | 2 +- Gemfile | 1 + Gemfile.lock | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 30471286026..48015d5ec2a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - knapsack spinach + - if ! knapsack spinach "-r rerun"; then bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt); fi artifacts: expire_in: 31d paths: diff --git a/Gemfile b/Gemfile index 563851c25f5..5f754c1b66f 100644 --- a/Gemfile +++ b/Gemfile @@ -278,6 +278,7 @@ group :development, :test do gem 'rspec-rails', '~> 3.5.0' gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' + gem 'spinach-rerun-reporter', '~> 0.0.2' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index 60d434a1b69..a9892d1c130 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -700,6 +700,8 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) + spinach-rerun-reporter (0.0.2) + spinach (~> 0.8) spring (1.7.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -962,6 +964,7 @@ DEPENDENCIES simplecov (= 0.12.0) slack-notifier (~> 1.2.0) spinach-rails (~> 0.2.1) + spinach-rerun-reporter (~> 0.0.2) spring (~> 1.7.0) spring-commands-rspec (~> 1.0.4) spring-commands-spinach (~> 1.1.0) -- cgit v1.2.1 From caa141eaa719f52b126065ff42c68fdc36b52437 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 14:12:30 +0200 Subject: Fix retries --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 48015d5ec2a..ace0de5cad3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,7 +99,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - - if ! knapsack spinach "-r rerun"; then bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt); fi + - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: expire_in: 31d paths: -- cgit v1.2.1 From eaa22eb447b8dcd690f084221db26043274f343b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 14:32:47 +0200 Subject: Try to fix re-run --- features/support/rerun.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/support/rerun.rb b/features/support/rerun.rb index 8b176c5be89..60b78f9d050 100644 --- a/features/support/rerun.rb +++ b/features/support/rerun.rb @@ -1,5 +1,7 @@ # The spinach-rerun-reporter doesn't define the on_undefined_step # See it here: https://github.com/javierav/spinach-rerun-reporter/blob/master/lib/spinach/reporter/rerun.rb +require 'spinach-rerun-reporter' + module Spinach class Reporter class Rerun -- cgit v1.2.1 From 5904793ad8ba88d3dfc9c973bcffd1d426db5a33 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 14 Oct 2016 12:53:51 +0200 Subject: Add build finished worker that creates a workflow --- app/models/ci/build.rb | 3 +-- app/workers/build_finished_worker.rb | 12 ++++++++++++ spec/workers/build_finished_worker_spec.rb | 30 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 app/workers/build_finished_worker.rb create mode 100644 spec/workers/build_finished_worker_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 23119c5ed61..87475119b23 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -83,8 +83,7 @@ module Ci after_transition any => [:success, :failed, :canceled] do |build| build.run_after_commit do - BuildCoverageWorker.perform_async(id) - BuildHooksWorker.perform_async(id) + BuildFinishedWorker.perform_async(id) end end diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb new file mode 100644 index 00000000000..f1267ab92fc --- /dev/null +++ b/app/workers/build_finished_worker.rb @@ -0,0 +1,12 @@ +class BuildFinishedWorker + include Sidekiq::Worker + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + build.with_lock do + BuildCoverageWorker.new.perform(build.id) + BuildHooksWorker.new.perform(build.id) + end + end + end +end diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb new file mode 100644 index 00000000000..2868167c7d4 --- /dev/null +++ b/spec/workers/build_finished_worker_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe BuildFinishedWorker do + describe '#perform' do + context 'when build exists' do + let(:build) { create(:ci_build) } + + it 'calculates coverage and calls hooks' do + expect(BuildCoverageWorker) + .to receive(:new).ordered.and_call_original + expect(BuildHooksWorker) + .to receive(:new).ordered.and_call_original + + expect_any_instance_of(BuildCoverageWorker) + .to receive(:perform) + expect_any_instance_of(BuildHooksWorker) + .to receive(:perform) + + described_class.new.perform(build.id) + end + end + + context 'when build does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From f24966174701024ece1e728315f5e847b684ee65 Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 13:53:35 +0100 Subject: Added book club to university --- doc/university/README.md | 1 + doc/university/bookclub/booklist.md | 113 ++++++++++++++++++++++++++++++++++++ doc/university/bookclub/index.md | 19 ++++++ 3 files changed, 133 insertions(+) create mode 100644 doc/university/bookclub/booklist.md create mode 100644 doc/university/bookclub/index.md diff --git a/doc/university/README.md b/doc/university/README.md index e71e49c33c8..f5a0dab39fe 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -64,6 +64,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project 1. [Making GitLab Great for Everyone - Video](https://www.youtube.com/watch?v=GGC40y4vMx0) - Response to "Dear GitHub" letter 1. [Using Innersourcing to Improve Collaboration](https://about.gitlab.com/2014/09/05/innersourcing-using-the-open-source-workflow-to-improve-collaboration-within-an-organization/) 1. [The Software Development Market and GitLab - Video](https://www.youtube.com/watch?v=sXlhgPK1NTY&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e&index=6) - [Slides](https://docs.google.com/presentation/d/1vCU-NbZWz8NTNK8Vu3y4zGMAHb5DpC8PE5mHtw1PWfI/edit) +1. [The GitLab Book Club](bookclub/index.md) #### 1.7 Community and Support diff --git a/doc/university/bookclub/booklist.md b/doc/university/bookclub/booklist.md new file mode 100644 index 00000000000..c4229832e9f --- /dev/null +++ b/doc/university/bookclub/booklist.md @@ -0,0 +1,113 @@ +# Books + +List of books and resources, that may be worth reading. + +## Papers + +1. **The Humble Programmer** + + Edsger W. Dijkstra, 1972 ([paper](http://dl.acm.org/citation.cfm?id=361591)) + +## Programming + +1. **Design Patterns: Elements of Reusable Object-Oriented Software** + + Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 1994 ([amazon](http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)) + +1. **Clean Code: A Handbook of Agile Software Craftsmanship** + + Robert C. "Uncle Bob" Martin, 2008 ([amazon](http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)) + +1. **Code Complete: A Practical Handbook of Software Construction**, 2nd Edition + + Steve McConnell, 2004 ([amazon](http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670)) + +1. **The Pragmatic Programmer: From Journeyman to Master** + + Andrew Hunt, David Thomas, 1999 ([amazon](http://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X)) + +1. **Working Effectively with Legacy Code** + + Michael Feathers, 2004 ([amazon](http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052)) + +1. **Eloquent Ruby** + + Russ Olsen, 2011 ([amazon](http://www.amazon.com/Eloquent-Ruby-Addison-Wesley-Professional/dp/0321584104)) + +1. **Domain-Driven Design: Tackling Complexity in the Heart of Software** + + Eric Evans, 2003 ([amazon](http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215)) + +1. **How to Solve It: A New Aspect of Mathematical Method** + + Polya G. 1957 ([amazon](http://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069116407X)) + +1. **Software Creativity 2.0** + + Robert L. Glass, 2006 ([amazon](http://www.amazon.com/Software-Creativity-2-0-Robert-Glass/dp/0977213315)) + +1. **Object-Oriented Software Construction** + + Bertrand Meyer, 1997 ([amazon](http://www.amazon.com/Object-Oriented-Software-Construction-Book-CD-ROM/dp/0136291554)) + +1. **Refactoring: Improving the Design of Existing Code** + + Martin Fowler, Kent Beck, 1999 ([amazon](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672)) + +1. **Test Driven Development: By Example** + + Kent Beck, 2002 ([amazon](http://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530)) + +1. **Algorithms in C++: Fundamentals, Data Structure, Sorting, Searching** + + Robert Sedgewick, 1990 ([amazon](http://www.amazon.com/Algorithms-Parts-1-4-Fundamentals-Structure/dp/0201350882)) + +1. **Effective C++** + + Scott Mayers, 1996 ([amazon](http://www.amazon.com/Effective-Specific-Improve-Programs-Designs/dp/0321334876)) + +1. **Extreme Programming Explained: Embrace Change** + + Kent Beck, 1999 ([amazon](http://www.amazon.com/Extreme-Programming-Explained-Embrace-Change/dp/0321278658)) + +1. **The Art of Computer Programming** + + Donald E. Knuth, 1997 ([amazon](http://www.amazon.com/Computer-Programming-Volumes-1-4A-Boxed/dp/0321751043)) + +1. **Writing Efficient Programs** + + Jon Louis Bentley, 1982 ([amazon](http://www.amazon.com/Writing-Efficient-Programs-Prentice-Hall-Software/dp/013970244X)) + +1. **The Mythical Man-Month: Essays on Software Engineering** + + Frederick Phillips Brooks, 1975 ([amazon](http://www.amazon.com/Mythical-Man-Month-Essays-Software-Engineering/dp/0201006502)) + +1. **Peopleware: Productive Projects and Teams** 3rd Edition + + Tom DeMarco, Tim Lister, 2013 ([amazon](http://www.amazon.com/Peopleware-Productive-Projects-Teams-3rd/dp/0321934113)) + +1. **Principles Of Software Engineering Management** + + Tom Gilb, 1988 ([amazon](http://www.amazon.com/Principles-Software-Engineering-Management-Gilb/dp/0201192462)) + +## Other + +1. **Thinking, Fast and Slow** + + Daniel Kahneman, 2013 ([amazon](http://www.amazon.com/Thinking-Fast-Slow-Daniel-Kahneman/dp/0374533555)) + +1. **The Social Animal** 11th Edition + + Elliot Aronson, 2011 ([amazon](http://www.amazon.com/Social-Animal-Elliot-Aronson/dp/1429233419)) + +1. **Influence: Science and Practice** 5th Edition + + Robert B. Cialdini, 2008 ([amazon](http://www.amazon.com/Influence-Practice-Robert-B-Cialdini/dp/0205609996)) + +1. **Getting to Yes: Negotiating Agreement Without Giving In** + + Roger Fisher, William L. Ury, Bruce Patton, 2011 ([amazon](http://www.amazon.com/Getting-Yes-Negotiating-Agreement-Without/dp/0143118757)) + +1. **How to Win Friends & Influence People** + + Dale Carnegie, 1981 ([amazon](http://www.amazon.com/How-Win-Friends-Influence-People/dp/0671027034)) diff --git a/doc/university/bookclub/index.md b/doc/university/bookclub/index.md new file mode 100644 index 00000000000..022a61f4429 --- /dev/null +++ b/doc/university/bookclub/index.md @@ -0,0 +1,19 @@ +# The GitLab Book Club + +The Book Club is a casual meet-up to read and discuss books we like. +We'll find a time that suits most, if not all. + +See the [book list](booklist.md) for additional recommendations. + +## Currently reading : Books about remote work + +1. **Remote: Office not required** + + David Heinemeier Hansson and Jason Fried, 2013 + ([amazon](http://www.amazon.co.uk/Remote-Required-David-Heinemeier-Hansson/dp/0091954673)) + +1. **The Year Without Pants** + + Scott Berkun, 2013 ([ScottBerkun.com](http://scottberkun.com/yearwithoutpants/)) + +Any other books you'd like to suggest? Edit this page and add them to the queue. -- cgit v1.2.1 From aef739605987dc70424b83c56ba393462520af52 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 30 Sep 2016 11:38:56 +0200 Subject: Cache gems in CI on tags --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62e06683124..7d19f55aca3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -339,3 +339,16 @@ pages: - public only: - master + +# Insurance in case a gem needed by one of our releases gets yanked from +# rubygems.org in the future. +cache gems: + only: + - tags + variables: + SETUP_DB: "false" + script: + - bundle package --all --all-platforms + artifacts: + paths: + - vendor/cache -- cgit v1.2.1 From c736ffdfb043df34a89273639297cfc86d2d5f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 14 Oct 2016 16:04:23 +0300 Subject: Validate user id for users select autcomplete Single user autcomplete should be used only for existing users with digital ID provided. Now js code puts any input into generating user URL which can lead to 500 error because routing like this does not exists: GET "/autocomplete/users/whatever@example.com.json". Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/users_select.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 6aa0e1cd2b6..3020b7cc239 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -325,6 +325,10 @@ }; UsersSelect.prototype.user = function(user_id, callback) { + if(!/^\d+$/.test(user_id)) { + return false; + } + var url; url = this.buildUrl(this.userPath); url = url.replace(':id', user_id); -- cgit v1.2.1 From 9ec3c32f98f9548b4aa756c8fba74159b12492a7 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Oct 2016 13:39:20 +0100 Subject: Improved specifity of toggleable file headers --- .gitlab-ci.yml | 13 ------------- app/assets/stylesheets/framework/files.scss | 9 --------- app/assets/stylesheets/pages/diff.scss | 13 +++++++++++++ app/assets/stylesheets/pages/notes.scss | 8 ++++++++ 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..62e06683124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -339,16 +339,3 @@ pages: - public only: - master - -# Insurance in case a gem needed by one of our releases gets yanked from -# rubygems.org in the future. -cache gems: - only: - - tags - variables: - SETUP_DB: "false" - script: - - bundle package --all --all-platforms - artifacts: - paths: - - vendor/cache diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 81520500594..76a3c083697 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -26,15 +26,6 @@ padding: 10px $gl-padding; word-wrap: break-word; border-radius: 3px 3px 0 0; - cursor: pointer; - - &:hover { - background-color: $dark-background-color; - } - - .diff-toggle-caret { - padding-right: 6px; - } &.file-title-clear { padding-left: 0; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index b8ef76cc74e..84094b0cb1b 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -33,6 +33,19 @@ font-size: smaller; } } + + .file-title { + cursor: pointer; + + &:hover { + background-color: $dark-background-color; + } + + .diff-toggle-caret { + padding-right: 6px; + } + } + .diff-content { overflow: auto; overflow-y: hidden; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index d399f84a2ff..efeea96373f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -147,6 +147,14 @@ ul.notes { // Diff code in discussion view .discussion-body .diff-file { + .file-title { + cursor: default; + + &:hover { + background-color: $gray-light; + } + } + .diff-header > span { margin-right: 10px; } -- cgit v1.2.1 From 5bcf56401e502e9bb6094d37e76ea736d68cddfc Mon Sep 17 00:00:00 2001 From: De Wet Blomerus Date: Fri, 14 Oct 2016 15:16:18 +0200 Subject: remove a period that can be annoying --- .gitlab/merge_request_templates/Documentation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md index d2a1eb56423..9b541aadad1 100644 --- a/.gitlab/merge_request_templates/Documentation.md +++ b/.gitlab/merge_request_templates/Documentation.md @@ -1,4 +1,4 @@ -See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html. +See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html ## What does this MR do? -- cgit v1.2.1 From 513f061b5b521a1a428a05ebc467784f672ae07b Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 14 Oct 2016 14:47:13 +0100 Subject: Use gitlab-workhorse 0.8.5 --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.12-to-8.13.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index b60d71966ae..7ada0d303f3 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.8.4 +0.8.5 diff --git a/doc/install/installation.md b/doc/install/installation.md index 1fa8678223a..c9acc9cdfb0 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.8.4 + sudo -u git -H git checkout v0.8.5 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index 00d63c1b3c6..8940d14559b 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -84,7 +84,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.8.4 +sudo -u git -H git checkout v0.8.5 sudo -u git -H make ``` -- cgit v1.2.1 From c5bf8e362ce10e52428bda873eb14b41e15a1bd0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 14 Oct 2016 14:34:31 +0200 Subject: Use module_function in Banzai::Renderer Using `extend self` prevents GitLab Performance Monitoring from being able to track class methods. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/23347 --- CHANGELOG | 1 + lib/banzai/renderer.rb | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4f2f74a7595..d2f1501bd2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - Updating verbiage on git basics to be more intuitive - Clarify documentation for Runners API (Gennady Trafimenkov) + - The instrumentation for Banzai::Renderer has been restored - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 - Added documentation for .gitattributes files diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 6924a293da8..ce048a36fa0 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,6 +1,6 @@ module Banzai module Renderer - extend self + module_function # Convert a Markdown String into an HTML-safe String of HTML # @@ -141,8 +141,6 @@ module Banzai end.html_safe end - private - def cacheless_render(text, context = {}) Gitlab::Metrics.measure(:banzai_cacheless_render) do result = render_result(text, context) -- cgit v1.2.1 From 3f8fa3576509e2c90fbf9c18450e3daede32deac Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 14 Oct 2016 15:53:06 +0100 Subject: Document restrictions on cache and artifact paths See https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/issues/1792 --- doc/ci/yaml/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 59399861a97..84ea59ab687 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -159,7 +159,8 @@ Variables can be also defined on [job level](#job-variables). > Introduced in GitLab Runner v0.7.0. `cache` is used to specify a list of files and directories which should be -cached between builds. +cached between builds. You can only use paths that are within the project +workspace. **By default the caching is enabled per-job and per-branch.** @@ -606,8 +607,8 @@ You can see a simple example at https://gitlab.com/gitlab-examples/review-apps-n > - Build artifacts are only collected for successful builds by default. `artifacts` is used to specify a list of files and directories which should be -attached to the build after success. To pass artifacts between different builds, -see [dependencies](#dependencies). +attached to the build after success. You can only use paths that are within the +project workspace. To pass artifacts between different builds, see [dependencies](#dependencies). Below are some examples. -- cgit v1.2.1 From 308769f82b19a142f443fb101a01a60c2b19757f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 17:48:28 +0200 Subject: Remove unecessary lock --- app/workers/build_finished_worker.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index f1267ab92fc..e7286b77ac5 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -3,10 +3,8 @@ class BuildFinishedWorker def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - build.with_lock do - BuildCoverageWorker.new.perform(build.id) - BuildHooksWorker.new.perform(build.id) - end + BuildCoverageWorker.new.perform(build.id) + BuildHooksWorker.new.perform(build.id) end end end -- cgit v1.2.1 From b9f351e8798529f09316c0d8ab77a01c6a18f7bf Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 14 Oct 2016 17:12:50 +0000 Subject: Rename pipeline.js.es6 to pipelines.js.es6 --- app/assets/javascripts/pipeline.js.es6 | 40 --------------------------------- app/assets/javascripts/pipelines.js.es6 | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 app/assets/javascripts/pipeline.js.es6 create mode 100644 app/assets/javascripts/pipelines.js.es6 diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 deleted file mode 100644 index 6bf63ee6979..00000000000 --- a/app/assets/javascripts/pipeline.js.es6 +++ /dev/null @@ -1,40 +0,0 @@ -((global) => { - - class Pipelines { - constructor() { - $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); - this.addMarginToBuildColumns(); - } - - toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - - - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') - } - - addMarginToBuildColumns() { - const $secondChildBuildNode = $('.build:nth-child(2)'); - if ($secondChildBuildNode.length) { - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); - const $previousColumn = $multiBuildColumn.prev('.stage-column'); - $multiBuildColumn.addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); - $previousColumn.each(function() { - $this = $(this); - if ($('.build', $this).length === 1) $this.addClass('no-margin'); - }); - } - $('.pipeline-graph').removeClass('hidden'); - } - } - - global.Pipelines = Pipelines; - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 new file mode 100644 index 00000000000..6bf63ee6979 --- /dev/null +++ b/app/assets/javascripts/pipelines.js.es6 @@ -0,0 +1,40 @@ +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + this.addMarginToBuildColumns(); + } + + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); + + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + + + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph').removeClass('hidden'); + } + } + + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From c180221a5b0e58c42632412d2084efc3bd4cda99 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 14 Oct 2016 19:49:36 +0200 Subject: Add docs for request profiling Closes #23239 --- CHANGELOG | 1 + doc/README.md | 1 + .../performance/img/request_profile_result.png | Bin 0 -> 9720 bytes .../performance/img/request_profiling_token.png | Bin 0 -> 30076 bytes .../monitoring/performance/request_profiling.md | 16 ++++++++++++++++ doc/development/performance.md | 3 ++- 6 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 doc/administration/monitoring/performance/img/request_profile_result.png create mode 100644 doc/administration/monitoring/performance/img/request_profiling_token.png create mode 100644 doc/administration/monitoring/performance/request_profiling.md diff --git a/CHANGELOG b/CHANGELOG index d2f1501bd2a..9def97ca342 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -115,6 +115,7 @@ v 8.13.0 (unreleased) - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found + - Add docs for request profiling - Make guests unable to view MRs on private projects v 8.12.7 diff --git a/doc/README.md b/doc/README.md index 7e3d9b00900..c30bf328003 100644 --- a/doc/README.md +++ b/doc/README.md @@ -49,6 +49,7 @@ - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. - [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Request Profiling](administration/monitoring/performance/request_profiling.md) Get a detailed profile on slow requests. - [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. diff --git a/doc/administration/monitoring/performance/img/request_profile_result.png b/doc/administration/monitoring/performance/img/request_profile_result.png new file mode 100644 index 00000000000..73e2fdcab67 Binary files /dev/null and b/doc/administration/monitoring/performance/img/request_profile_result.png differ diff --git a/doc/administration/monitoring/performance/img/request_profiling_token.png b/doc/administration/monitoring/performance/img/request_profiling_token.png new file mode 100644 index 00000000000..04d87567816 Binary files /dev/null and b/doc/administration/monitoring/performance/img/request_profiling_token.png differ diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md new file mode 100644 index 00000000000..c358dfbead2 --- /dev/null +++ b/doc/administration/monitoring/performance/request_profiling.md @@ -0,0 +1,16 @@ +# Request Profiling + +## Procedure +1. Grab the profiling token from `Monitoring > Requests Profiles` admin page +(highlighted in a blue in the image below). +![Profile token](img/request_profiling_token.png) +1. Pass the header `X-Profile-Token: ` to the request you want to profile. You can use any of these tools + * [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) Chrome extension + * [Modify Headers](https://addons.mozilla.org/en-US/firefox/addon/modify-headers/) Firefox extension + * `curl --header 'X-Profile-Token: ' https://gitlab.example.com/group/project` +1. Once request is finished (which will take a little longer than usual), you can +view the profiling output from `Monitoring > Requests Profiles` admin page. +![Profiling output](img/request_profile_result.png) + +## Cleaning up +Profiling output will be cleared out every day via a Sidekiq worker. diff --git a/doc/development/performance.md b/doc/development/performance.md index 7ff603e2c4a..65d34829025 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -34,10 +34,11 @@ graphs/dashboards. ## Tooling -GitLab provides two built-in tools to aid the process of improving performance: +GitLab provides built-in tools to aid the process of improving performance: * [Sherlock](profiling.md#sherlock) * [GitLab Performance Monitoring](../monitoring/performance/monitoring.md) +* [Request Profiling](../administration/monitoring/performance/request_profiling.md) GitLab employees can use GitLab.com's performance monitoring systems located at , this requires you to log in using your -- cgit v1.2.1 From e88aa25f42c394785d4c176046f10c815250c8b1 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 14 Oct 2016 15:39:08 -0500 Subject: Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint --- .scss-lint.yml | 4 +- CHANGELOG | 1 + app/assets/stylesheets/behaviors.scss | 1 + app/assets/stylesheets/framework/blocks.scss | 2 + app/assets/stylesheets/framework/buttons.scss | 6 +- app/assets/stylesheets/framework/callout.scss | 5 + app/assets/stylesheets/framework/common.scss | 39 +-- app/assets/stylesheets/framework/dropdowns.scss | 2 + app/assets/stylesheets/framework/files.scss | 9 + app/assets/stylesheets/framework/forms.scss | 4 +- app/assets/stylesheets/framework/gitlab-theme.scss | 2 +- app/assets/stylesheets/framework/header.scss | 1 + app/assets/stylesheets/framework/lists.scss | 1 + app/assets/stylesheets/framework/logo.scss | 7 + app/assets/stylesheets/framework/mixins.scss | 1 + app/assets/stylesheets/framework/nav.scss | 1 + app/assets/stylesheets/framework/selects.scss | 4 + app/assets/stylesheets/framework/timeline.scss | 1 + app/assets/stylesheets/framework/tw_bootstrap.scss | 10 + app/assets/stylesheets/framework/typography.scss | 1 + app/assets/stylesheets/highlight/dark.scss | 128 ++++----- app/assets/stylesheets/highlight/monokai.scss | 118 ++++----- .../stylesheets/highlight/solarized_dark.scss | 136 +++++----- .../stylesheets/highlight/solarized_light.scss | 136 +++++----- app/assets/stylesheets/highlight/white.scss | 2 +- .../stylesheets/mailers/repository_push_email.scss | 2 +- app/assets/stylesheets/notify.scss | 10 +- app/assets/stylesheets/pages/admin.scss | 12 +- app/assets/stylesheets/pages/builds.scss | 2 + app/assets/stylesheets/pages/commit.scss | 6 + app/assets/stylesheets/pages/commits.scss | 1 + app/assets/stylesheets/pages/dashboard.scss | 2 + app/assets/stylesheets/pages/diff.scss | 24 ++ app/assets/stylesheets/pages/editor.scss | 5 + app/assets/stylesheets/pages/events.scss | 8 +- app/assets/stylesheets/pages/groups.scss | 1 + app/assets/stylesheets/pages/help.scss | 8 +- app/assets/stylesheets/pages/issuable.scss | 8 +- app/assets/stylesheets/pages/issues.scss | 1 + app/assets/stylesheets/pages/labels.scss | 1 + app/assets/stylesheets/pages/lint.scss | 1 + app/assets/stylesheets/pages/login.scss | 2 +- app/assets/stylesheets/pages/merge_conflicts.scss | 2 + app/assets/stylesheets/pages/merge_requests.scss | 4 + app/assets/stylesheets/pages/milestone.scss | 1 + app/assets/stylesheets/pages/note_form.scss | 2 + app/assets/stylesheets/pages/notes.scss | 2 + app/assets/stylesheets/pages/pipelines.scss | 4 +- app/assets/stylesheets/pages/profile.scss | 1 + app/assets/stylesheets/pages/projects.scss | 18 ++ app/assets/stylesheets/pages/runners.scss | 1 + app/assets/stylesheets/pages/status.scss | 2 + app/assets/stylesheets/pages/tree.scss | 1 + app/assets/stylesheets/pages/xterm.scss | 290 ++++++++++++++++++++- 54 files changed, 746 insertions(+), 298 deletions(-) diff --git a/.scss-lint.yml b/.scss-lint.yml index 71df6be6a15..5093702519b 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -61,7 +61,7 @@ linters: # Separate rule, function, and mixin declarations with empty lines. EmptyLineBetweenBlocks: - enabled: false + enabled: true # Reports when you have an empty rule set. EmptyRule: @@ -219,7 +219,7 @@ linters: # Property values, @extend, @include, and @import directives, and variable # declarations should always end with a semicolon. TrailingSemicolon: - enabled: false + enabled: true # Reports lines containing trailing whitespace. TrailingWhitespace: diff --git a/CHANGELOG b/CHANGELOG index 3d6347c5395..194ee18b74c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.13.0 (unreleased) - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Truncate long labels with ellipsis in labels page - Improve tabbing usability for sign in page (ClemMakesApps) + - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index 897bc49e7df..e3ca7f6373a 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -5,6 +5,7 @@ display: none; &.hide { display: block; } } + &.open .content { display: block; &.hide { display: none; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 8002e56724b..df2e2ea8d2c 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -19,6 +19,7 @@ &.diff-collapsed { padding: 5px; + .click-to-expand { cursor: pointer; } @@ -203,6 +204,7 @@ } } } + &.user-cover-block { padding: 24px 0 0; } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index a7c8d782e9b..7c0ed72dbc5 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -25,7 +25,7 @@ &:focus { background-color: $hover-background; color: $hover-text; - border-color: $hover-border;; + border-color: $hover-border; } } @@ -240,6 +240,7 @@ width: 100%; margin: 0; margin-bottom: 15px; + &.btn { padding: 6px 0; } @@ -321,6 +322,7 @@ .btn-build { margin-left: 10px; + i { color: $gl-icon-color; } @@ -328,6 +330,7 @@ .clone-dropdown-btn a { color: $dropdown-link-color; + &:hover { text-decoration: none; } @@ -337,6 +340,7 @@ background-color: $background-color !important; border: 1px solid lightgrey; cursor: default; + &:active { -moz-box-shadow: inset 0 0 0 white; -webkit-box-shadow: inset 0 0 0 white; diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index da7bab74a32..f3b6ad88ad6 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -13,10 +13,12 @@ color: $text-color; background: $background-color; } + .bs-callout h4 { margin-top: 0; margin-bottom: 5px; } + .bs-callout p:last-child { margin-bottom: 0; } @@ -27,16 +29,19 @@ border-color: #eed3d7; color: #b94a48; } + .bs-callout-warning { background-color: #faf8f0; border-color: #faebcc; color: #8a6d3b; } + .bs-callout-info { background-color: #f4f8fa; border-color: #bce8f1; color: #34789a; } + .bs-callout-success { background-color: #dff0d8; border-color: #5ca64d; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 5957dce89bc..81e4e264560 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,31 +1,31 @@ /** COLORS **/ .cgray { color: $gl-gray; } -.clgray { color: #bbb } +.clgray { color: #bbb; } .cred { color: $gl-text-red; } .cgreen { color: $gl-text-green; } -.cdark { color: #444 } +.cdark { color: #444; } /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } .prepend-top-5 { margin-top: 5px; } -.prepend-top-10 { margin-top: 10px } +.prepend-top-10 { margin-top: 10px; } .prepend-top-default { margin-top: $gl-padding !important; } -.prepend-top-20 { margin-top: 20px } -.prepend-left-5 { margin-left: 5px } -.prepend-left-10 { margin-left: 10px } +.prepend-top-20 { margin-top: 20px; } +.prepend-left-5 { margin-left: 5px; } +.prepend-left-10 { margin-left: 10px; } .prepend-left-default { margin-left: $gl-padding; } -.prepend-left-20 { margin-left: 20px } -.append-right-5 { margin-right: 5px } -.append-right-10 { margin-right: 10px } +.prepend-left-20 { margin-left: 20px; } +.append-right-5 { margin-right: 5px; } +.append-right-10 { margin-right: 10px; } .append-right-default { margin-right: $gl-padding; } -.append-right-20 { margin-right: 20px } -.append-bottom-0 { margin-bottom: 0 } -.append-bottom-10 { margin-bottom: 10px } -.append-bottom-15 { margin-bottom: 15px } -.append-bottom-20 { margin-bottom: 20px } +.append-right-20 { margin-right: 20px; } +.append-bottom-0 { margin-bottom: 0; } +.append-bottom-10 { margin-bottom: 10px; } +.append-bottom-15 { margin-bottom: 15px; } +.append-bottom-20 { margin-bottom: 20px; } .append-bottom-default { margin-bottom: $gl-padding; } -.inline { display: inline-block } -.center { text-align: center } +.inline { display: inline-block; } +.center { text-align: center; } .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } @@ -97,6 +97,7 @@ span.update-author { color: #999; font-weight: normal; font-style: italic; + strong { font-weight: bold; font-style: normal; @@ -128,7 +129,7 @@ p.time { // Fix issue with notes & lists creating a bunch of bottom borders. li.note { - img { max-width: 100% } + img { max-width: 100%; } .note-title { li { border-bottom: none !important; @@ -172,6 +173,7 @@ li.note { @extend .col-md-6; text-align: left; margin-top: 40px; + pre { background: white; border: none; @@ -197,6 +199,7 @@ li.note { background: #c67; color: #fff; font-weight: bold; + a { color: #fff; text-decoration: underline; @@ -227,6 +230,7 @@ li.note { &.milestone-closed { background: $gray-light; } + .progress { margin-bottom: 0; margin-top: 4px; @@ -286,6 +290,7 @@ table { .footer-links { margin-bottom: 20px; + a { margin-right: 15px; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index baa95711329..a839371a6f2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -12,6 +12,7 @@ .dropdown-menu, .dropdown-menu-nav { display: block; + @media (max-width: $screen-xs-max) { width: 100%; } @@ -48,6 +49,7 @@ margin-top: -6px; color: $dropdown-toggle-icon-color; font-size: 10px; + &.fa-spinner { font-size: 16px; margin-top: -8px; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 76a3c083697..13c1bbf0359 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -57,6 +57,7 @@ margin-top: -3px; } } + .file-content { background: #fff; @@ -96,22 +97,27 @@ border: none; margin: 0; } + tr { border-bottom: 1px solid #eee; } + td { &:first-child { border-left: none; } + &:last-child { border-right: none; } } + td.blame-commit { padding: 0 10px; min-width: 400px; background: $gray-light; } + td.line-numbers { float: none; border-left: 1px solid #ddd; @@ -121,6 +127,7 @@ margin-right: 0; } } + td.lines { padding: 0; } @@ -137,8 +144,10 @@ border-left: 1px solid $border-color; margin-bottom: 0; background: white; + li { color: #888; + p { margin: 0; color: #333; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 3d01179f074..311e3fa1a35 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -9,7 +9,7 @@ input { input[type='text'].danger { background: #f2dede!important; border-color: #d66; - text-shadow: 0 1px 1px #fff + text-shadow: 0 1px 1px #fff; } .datetime-controls { @@ -117,9 +117,11 @@ label { display: table-cell; width: 200px !important; } + .input-group-addon { background-color: #f7f8fa; } + .input-group-addon:not(:first-child):not(:last-child) { border-left: 0; border-right: 0; diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 3673b81f183..fe834f4e2f6 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -62,7 +62,7 @@ } i { - color: $white-light + color: $white-light; } path, diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 9823abdde1f..3a4fdd0da22 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -168,6 +168,7 @@ header { a { color: $gl-text-color; + &:hover { text-decoration: underline; } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 9114425cfdd..4b2627c1b87 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -60,6 +60,7 @@ padding-top: 1px; margin: 0; color: $gray-dark; + img { position: relative; top: 3px; diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index c214eabcad7..a90e45bb5f4 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -37,6 +37,7 @@ 0%, 10%, 100% { fill: lighten($tanuki-yellow, 25%); } + 90% { fill: $tanuki-yellow; } @@ -48,6 +49,7 @@ 10%, 80% { fill: $tanuki-orange; } + 20%, 90% { fill: lighten($tanuki-orange, 25%); } @@ -59,6 +61,7 @@ 10%, 80% { fill: $tanuki-red; } + 20%, 90% { fill: lighten($tanuki-red, 25%); } @@ -70,6 +73,7 @@ 20%, 70% { fill: $tanuki-red; } + 30%, 80% { fill: lighten($tanuki-red, 25%); } @@ -81,6 +85,7 @@ 30%, 60% { fill: $tanuki-orange; } + 40%, 70% { fill: lighten($tanuki-orange, 25%); } @@ -92,6 +97,7 @@ 30%, 60% { fill: $tanuki-red; } + 40%, 70% { fill: lighten($tanuki-red, 25%); } @@ -103,6 +109,7 @@ 40% { fill: $tanuki-yellow; } + 60% { fill: lighten($tanuki-yellow, 25%); } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 7fabf27a558..f84ca36d10f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -34,6 +34,7 @@ &.active { background: $gray-light; + a { font-weight: 600; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ea43f4afc37..899db045b74 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -210,6 +210,7 @@ @media (max-width: $screen-xs-max) { padding-bottom: 0; width: 100%; + .btn, form, .dropdown, .dropdown-menu-toggle, .form-control { margin: 0 0 10px; display: block; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index bf9208f83f3..e0708c65695 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -137,6 +137,7 @@ .select2-results { max-height: 350px; + .select2-highlighted { background: $gl-primary; } @@ -212,9 +213,11 @@ .group-image { float: left; } + .group-name { font-weight: bold; } + .group-path { color: #999; } @@ -239,6 +242,7 @@ color: #aaa; font-weight: normal; } + .namespace-path { margin-left: 10px; font-weight: bolder; diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 0b0bd80c326..eb63a9f214b 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -48,6 +48,7 @@ &:before { background: none; } + .timeline-entry .timeline-entry-inner { .timeline-icon { display: none; diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index e3154657c54..f4106641269 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -48,31 +48,40 @@ .clearfix { @include clearfix(); } + .center-block { @include center-block(); } + .pull-right { float: right !important; } + .pull-left { float: left !important; } + .hide { display: none; } + .show { display: block !important; } + .invisible { visibility: hidden; } + .text-hide { @include text-hide(); } + .hidden { display: none !important; visibility: hidden !important; } + .affix { position: fixed; } @@ -146,6 +155,7 @@ padding: 6px 15px; font-size: 13px; font-weight: normal; + a { color: #777; } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index d099a884f54..8df0067fac1 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -106,6 +106,7 @@ @extend .table-bordered; margin: 12px 0; color: #5c5d5e; + th { background: #f8fafc; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 16ffbe57a99..a3acee299e3 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -55,68 +55,68 @@ color: #000 !important; } - .hll { background-color: #373b41 } - .c { color: #969896 } /* Comment */ - .err { color: #c66 } /* Error */ - .k { color: #b294bb } /* Keyword */ - .l { color: #de935f } /* Literal */ - .n { color: #c5c8c6 } /* Name */ - .o { color: #8abeb7 } /* Operator */ - .p { color: #c5c8c6 } /* Punctuation */ - .cm { color: #969896 } /* Comment.Multiline */ - .cp { color: #969896 } /* Comment.Preproc */ - .c1 { color: #969896 } /* Comment.Single */ - .cs { color: #969896 } /* Comment.Special */ - .gd { color: #c66 } /* Generic.Deleted */ - .ge { font-style: italic } /* Generic.Emph */ - .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */ - .gi { color: #b5bd68 } /* Generic.Inserted */ - .gp { color: #969896; font-weight: bold } /* Generic.Prompt */ - .gs { font-weight: bold } /* Generic.Strong */ - .gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */ - .kc { color: #b294bb } /* Keyword.Constant */ - .kd { color: #b294bb } /* Keyword.Declaration */ - .kn { color: #8abeb7 } /* Keyword.Namespace */ - .kp { color: #b294bb } /* Keyword.Pseudo */ - .kr { color: #b294bb } /* Keyword.Reserved */ - .kt { color: #f0c674 } /* Keyword.Type */ - .ld { color: #b5bd68 } /* Literal.Date */ - .m { color: #de935f } /* Literal.Number */ - .s { color: #b5bd68 } /* Literal.String */ - .na { color: #81a2be } /* Name.Attribute */ - .nb { color: #c5c8c6 } /* Name.Builtin */ - .nc { color: #f0c674 } /* Name.Class */ - .no { color: #c66 } /* Name.Constant */ - .nd { color: #8abeb7 } /* Name.Decorator */ - .ni { color: #c5c8c6 } /* Name.Entity */ - .ne { color: #c66 } /* Name.Exception */ - .nf { color: #81a2be } /* Name.Function */ - .nl { color: #c5c8c6 } /* Name.Label */ - .nn { color: #f0c674 } /* Name.Namespace */ - .nx { color: #81a2be } /* Name.Other */ - .py { color: #c5c8c6 } /* Name.Property */ - .nt { color: #8abeb7 } /* Name.Tag */ - .nv { color: #c66 } /* Name.Variable */ - .ow { color: #8abeb7 } /* Operator.Word */ - .w { color: #c5c8c6 } /* Text.Whitespace */ - .mf { color: #de935f } /* Literal.Number.Float */ - .mh { color: #de935f } /* Literal.Number.Hex */ - .mi { color: #de935f } /* Literal.Number.Integer */ - .mo { color: #de935f } /* Literal.Number.Oct */ - .sb { color: #b5bd68 } /* Literal.String.Backtick */ - .sc { color: #c5c8c6 } /* Literal.String.Char */ - .sd { color: #969896 } /* Literal.String.Doc */ - .s2 { color: #b5bd68 } /* Literal.String.Double */ - .se { color: #de935f } /* Literal.String.Escape */ - .sh { color: #b5bd68 } /* Literal.String.Heredoc */ - .si { color: #de935f } /* Literal.String.Interpol */ - .sx { color: #b5bd68 } /* Literal.String.Other */ - .sr { color: #b5bd68 } /* Literal.String.Regex */ - .s1 { color: #b5bd68 } /* Literal.String.Single */ - .ss { color: #b5bd68 } /* Literal.String.Symbol */ - .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */ - .vc { color: #c66 } /* Name.Variable.Class */ - .vg { color: #c66 } /* Name.Variable.Global */ - .vi { color: #c66 } /* Name.Variable.Instance */ - .il { color: #de935f } /* Literal.Number.Integer.Long */ + .hll { background-color: #373b41; } + .c { color: #969896; } /* Comment */ + .err { color: #c66; } /* Error */ + .k { color: #b294bb; } /* Keyword */ + .l { color: #de935f; } /* Literal */ + .n { color: #c5c8c6; } /* Name */ + .o { color: #8abeb7; } /* Operator */ + .p { color: #c5c8c6; } /* Punctuation */ + .cm { color: #969896; } /* Comment.Multiline */ + .cp { color: #969896; } /* Comment.Preproc */ + .c1 { color: #969896; } /* Comment.Single */ + .cs { color: #969896; } /* Comment.Special */ + .gd { color: #c66; } /* Generic.Deleted */ + .ge { font-style: italic; } /* Generic.Emph */ + .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */ + .gi { color: #b5bd68; } /* Generic.Inserted */ + .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */ + .gs { font-weight: bold; } /* Generic.Strong */ + .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */ + .kc { color: #b294bb; } /* Keyword.Constant */ + .kd { color: #b294bb; } /* Keyword.Declaration */ + .kn { color: #8abeb7; } /* Keyword.Namespace */ + .kp { color: #b294bb; } /* Keyword.Pseudo */ + .kr { color: #b294bb; } /* Keyword.Reserved */ + .kt { color: #f0c674; } /* Keyword.Type */ + .ld { color: #b5bd68; } /* Literal.Date */ + .m { color: #de935f; } /* Literal.Number */ + .s { color: #b5bd68; } /* Literal.String */ + .na { color: #81a2be; } /* Name.Attribute */ + .nb { color: #c5c8c6; } /* Name.Builtin */ + .nc { color: #f0c674; } /* Name.Class */ + .no { color: #c66; } /* Name.Constant */ + .nd { color: #8abeb7; } /* Name.Decorator */ + .ni { color: #c5c8c6; } /* Name.Entity */ + .ne { color: #c66; } /* Name.Exception */ + .nf { color: #81a2be; } /* Name.Function */ + .nl { color: #c5c8c6; } /* Name.Label */ + .nn { color: #f0c674; } /* Name.Namespace */ + .nx { color: #81a2be; } /* Name.Other */ + .py { color: #c5c8c6; } /* Name.Property */ + .nt { color: #8abeb7; } /* Name.Tag */ + .nv { color: #c66; } /* Name.Variable */ + .ow { color: #8abeb7; } /* Operator.Word */ + .w { color: #c5c8c6; } /* Text.Whitespace */ + .mf { color: #de935f; } /* Literal.Number.Float */ + .mh { color: #de935f; } /* Literal.Number.Hex */ + .mi { color: #de935f; } /* Literal.Number.Integer */ + .mo { color: #de935f; } /* Literal.Number.Oct */ + .sb { color: #b5bd68; } /* Literal.String.Backtick */ + .sc { color: #c5c8c6; } /* Literal.String.Char */ + .sd { color: #969896; } /* Literal.String.Doc */ + .s2 { color: #b5bd68; } /* Literal.String.Double */ + .se { color: #de935f; } /* Literal.String.Escape */ + .sh { color: #b5bd68; } /* Literal.String.Heredoc */ + .si { color: #de935f; } /* Literal.String.Interpol */ + .sx { color: #b5bd68; } /* Literal.String.Other */ + .sr { color: #b5bd68; } /* Literal.String.Regex */ + .s1 { color: #b5bd68; } /* Literal.String.Single */ + .ss { color: #b5bd68; } /* Literal.String.Symbol */ + .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */ + .vc { color: #c66; } /* Name.Variable.Class */ + .vg { color: #c66; } /* Name.Variable.Global */ + .vi { color: #c66; } /* Name.Variable.Instance */ + .il { color: #de935f; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 7de920e074b..e9228c94db9 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -55,65 +55,65 @@ color: #000 !important; } - .hll { background-color: #49483e } - .c { color: #75715e } /* Comment */ - .err { color: #960050; background-color: #1e0010 } /* Error */ - .k { color: #66d9ef } /* Keyword */ - .l { color: #ae81ff } /* Literal */ - .n { color: #f8f8f2 } /* Name */ - .o { color: #f92672 } /* Operator */ - .p { color: #f8f8f2 } /* Punctuation */ - .cm { color: #75715e } /* Comment.Multiline */ - .cp { color: #75715e } /* Comment.Preproc */ - .c1 { color: #75715e } /* Comment.Single */ - .cs { color: #75715e } /* Comment.Special */ - .ge { font-style: italic } /* Generic.Emph */ - .gs { font-weight: bold } /* Generic.Strong */ - .kc { color: #66d9ef } /* Keyword.Constant */ - .kd { color: #66d9ef } /* Keyword.Declaration */ - .kn { color: #f92672 } /* Keyword.Namespace */ - .kp { color: #66d9ef } /* Keyword.Pseudo */ - .kr { color: #66d9ef } /* Keyword.Reserved */ - .kt { color: #66d9ef } /* Keyword.Type */ - .ld { color: #e6db74 } /* Literal.Date */ - .m { color: #ae81ff } /* Literal.Number */ - .s { color: #e6db74 } /* Literal.String */ - .na { color: #a6e22e } /* Name.Attribute */ - .nb { color: #f8f8f2 } /* Name.Builtin */ - .nc { color: #a6e22e } /* Name.Class */ - .no { color: #66d9ef } /* Name.Constant */ - .nd { color: #a6e22e } /* Name.Decorator */ - .ni { color: #f8f8f2 } /* Name.Entity */ - .ne { color: #a6e22e } /* Name.Exception */ - .nf { color: #a6e22e } /* Name.Function */ - .nl { color: #f8f8f2 } /* Name.Label */ - .nn { color: #f8f8f2 } /* Name.Namespace */ - .nx { color: #a6e22e } /* Name.Other */ - .py { color: #f8f8f2 } /* Name.Property */ - .nt { color: #f92672 } /* Name.Tag */ - .nv { color: #f8f8f2 } /* Name.Variable */ - .ow { color: #f92672 } /* Operator.Word */ - .w { color: #f8f8f2 } /* Text.Whitespace */ - .mf { color: #ae81ff } /* Literal.Number.Float */ - .mh { color: #ae81ff } /* Literal.Number.Hex */ - .mi { color: #ae81ff } /* Literal.Number.Integer */ - .mo { color: #ae81ff } /* Literal.Number.Oct */ - .sb { color: #e6db74 } /* Literal.String.Backtick */ - .sc { color: #e6db74 } /* Literal.String.Char */ - .sd { color: #e6db74 } /* Literal.String.Doc */ - .s2 { color: #e6db74 } /* Literal.String.Double */ - .se { color: #ae81ff } /* Literal.String.Escape */ - .sh { color: #e6db74 } /* Literal.String.Heredoc */ - .si { color: #e6db74 } /* Literal.String.Interpol */ - .sx { color: #e6db74 } /* Literal.String.Other */ - .sr { color: #e6db74 } /* Literal.String.Regex */ - .s1 { color: #e6db74 } /* Literal.String.Single */ - .ss { color: #e6db74 } /* Literal.String.Symbol */ - .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ - .vc { color: #f8f8f2 } /* Name.Variable.Class */ - .vg { color: #f8f8f2 } /* Name.Variable.Global */ - .vi { color: #f8f8f2 } /* Name.Variable.Instance */ - .il { color: #ae81ff } /* Literal.Number.Integer.Long */ + .hll { background-color: #49483e; } + .c { color: #75715e; } /* Comment */ + .err { color: #960050; background-color: #1e0010; } /* Error */ + .k { color: #66d9ef; } /* Keyword */ + .l { color: #ae81ff; } /* Literal */ + .n { color: #f8f8f2; } /* Name */ + .o { color: #f92672; } /* Operator */ + .p { color: #f8f8f2; } /* Punctuation */ + .cm { color: #75715e; } /* Comment.Multiline */ + .cp { color: #75715e; } /* Comment.Preproc */ + .c1 { color: #75715e; } /* Comment.Single */ + .cs { color: #75715e; } /* Comment.Special */ + .ge { font-style: italic; } /* Generic.Emph */ + .gs { font-weight: bold; } /* Generic.Strong */ + .kc { color: #66d9ef; } /* Keyword.Constant */ + .kd { color: #66d9ef; } /* Keyword.Declaration */ + .kn { color: #f92672; } /* Keyword.Namespace */ + .kp { color: #66d9ef; } /* Keyword.Pseudo */ + .kr { color: #66d9ef; } /* Keyword.Reserved */ + .kt { color: #66d9ef; } /* Keyword.Type */ + .ld { color: #e6db74; } /* Literal.Date */ + .m { color: #ae81ff; } /* Literal.Number */ + .s { color: #e6db74; } /* Literal.String */ + .na { color: #a6e22e; } /* Name.Attribute */ + .nb { color: #f8f8f2; } /* Name.Builtin */ + .nc { color: #a6e22e; } /* Name.Class */ + .no { color: #66d9ef; } /* Name.Constant */ + .nd { color: #a6e22e; } /* Name.Decorator */ + .ni { color: #f8f8f2; } /* Name.Entity */ + .ne { color: #a6e22e; } /* Name.Exception */ + .nf { color: #a6e22e; } /* Name.Function */ + .nl { color: #f8f8f2; } /* Name.Label */ + .nn { color: #f8f8f2; } /* Name.Namespace */ + .nx { color: #a6e22e; } /* Name.Other */ + .py { color: #f8f8f2; } /* Name.Property */ + .nt { color: #f92672; } /* Name.Tag */ + .nv { color: #f8f8f2; } /* Name.Variable */ + .ow { color: #f92672; } /* Operator.Word */ + .w { color: #f8f8f2; } /* Text.Whitespace */ + .mf { color: #ae81ff; } /* Literal.Number.Float */ + .mh { color: #ae81ff; } /* Literal.Number.Hex */ + .mi { color: #ae81ff; } /* Literal.Number.Integer */ + .mo { color: #ae81ff; } /* Literal.Number.Oct */ + .sb { color: #e6db74; } /* Literal.String.Backtick */ + .sc { color: #e6db74; } /* Literal.String.Char */ + .sd { color: #e6db74; } /* Literal.String.Doc */ + .s2 { color: #e6db74; } /* Literal.String.Double */ + .se { color: #ae81ff; } /* Literal.String.Escape */ + .sh { color: #e6db74; } /* Literal.String.Heredoc */ + .si { color: #e6db74; } /* Literal.String.Interpol */ + .sx { color: #e6db74; } /* Literal.String.Other */ + .sr { color: #e6db74; } /* Literal.String.Regex */ + .s1 { color: #e6db74; } /* Literal.String.Single */ + .ss { color: #e6db74; } /* Literal.String.Symbol */ + .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */ + .vc { color: #f8f8f2; } /* Name.Variable.Class */ + .vg { color: #f8f8f2; } /* Name.Variable.Global */ + .vi { color: #f8f8f2; } /* Name.Variable.Instance */ + .il { color: #ae81ff; } /* Literal.Number.Integer.Long */ .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index b11499c71ee..c3c7773b9e2 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -72,72 +72,72 @@ green #859900 operators, other keywords */ - .c { color: #586e75 } /* Comment */ - .err { color: #93a1a1 } /* Error */ - .g { color: #93a1a1 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #93a1a1 } /* Literal */ - .n { color: #93a1a1 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #cb4b16 } /* Other */ - .p { color: #93a1a1 } /* Punctuation */ - .cm { color: #586e75 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #586e75 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2aa198 } /* Generic.Deleted */ - .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ - .gr { color: #dc322f } /* Generic.Error */ - .gh { color: #cb4b16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #93a1a1 } /* Generic.Output */ - .gp { color: #93a1a1 } /* Generic.Prompt */ - .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ - .gu { color: #cb4b16 } /* Generic.Subheading */ - .gt { color: #93a1a1 } /* Generic.Traceback */ - .kc { color: #cb4b16 } /* Keyword.Constant */ - .kd { color: #268bd2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268bd2 } /* Keyword.Reserved */ - .kt { color: #dc322f } /* Keyword.Type */ - .ld { color: #93a1a1 } /* Literal.Date */ - .m { color: #2aa198 } /* Literal.Number */ - .s { color: #2aa198 } /* Literal.String */ - .na { color: #93a1a1 } /* Name.Attribute */ - .nb { color: #b58900 } /* Name.Builtin */ - .nc { color: #268bd2 } /* Name.Class */ - .no { color: #cb4b16 } /* Name.Constant */ - .nd { color: #268bd2 } /* Name.Decorator */ - .ni { color: #cb4b16 } /* Name.Entity */ - .ne { color: #cb4b16 } /* Name.Exception */ - .nf { color: #268bd2 } /* Name.Function */ - .nl { color: #93a1a1 } /* Name.Label */ - .nn { color: #93a1a1 } /* Name.Namespace */ - .nx { color: #93a1a1 } /* Name.Other */ - .py { color: #93a1a1 } /* Name.Property */ - .nt { color: #268bd2 } /* Name.Tag */ - .nv { color: #268bd2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #93a1a1 } /* Text.Whitespace */ - .mf { color: #2aa198 } /* Literal.Number.Float */ - .mh { color: #2aa198 } /* Literal.Number.Hex */ - .mi { color: #2aa198 } /* Literal.Number.Integer */ - .mo { color: #2aa198 } /* Literal.Number.Oct */ - .sb { color: #586e75 } /* Literal.String.Backtick */ - .sc { color: #2aa198 } /* Literal.String.Char */ - .sd { color: #93a1a1 } /* Literal.String.Doc */ - .s2 { color: #2aa198 } /* Literal.String.Double */ - .se { color: #cb4b16 } /* Literal.String.Escape */ - .sh { color: #93a1a1 } /* Literal.String.Heredoc */ - .si { color: #2aa198 } /* Literal.String.Interpol */ - .sx { color: #2aa198 } /* Literal.String.Other */ - .sr { color: #dc322f } /* Literal.String.Regex */ - .s1 { color: #2aa198 } /* Literal.String.Single */ - .ss { color: #2aa198 } /* Literal.String.Symbol */ - .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2 } /* Name.Variable.Class */ - .vg { color: #268bd2 } /* Name.Variable.Global */ - .vi { color: #268bd2 } /* Name.Variable.Instance */ - .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .c { color: #586e75; } /* Comment */ + .err { color: #93a1a1; } /* Error */ + .g { color: #93a1a1; } /* Generic */ + .k { color: #859900; } /* Keyword */ + .l { color: #93a1a1; } /* Literal */ + .n { color: #93a1a1; } /* Name */ + .o { color: #859900; } /* Operator */ + .x { color: #cb4b16; } /* Other */ + .p { color: #93a1a1; } /* Punctuation */ + .cm { color: #586e75; } /* Comment.Multiline */ + .cp { color: #859900; } /* Comment.Preproc */ + .c1 { color: #586e75; } /* Comment.Single */ + .cs { color: #859900; } /* Comment.Special */ + .gd { color: #2aa198; } /* Generic.Deleted */ + .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */ + .gr { color: #dc322f; } /* Generic.Error */ + .gh { color: #cb4b16; } /* Generic.Heading */ + .gi { color: #859900; } /* Generic.Inserted */ + .go { color: #93a1a1; } /* Generic.Output */ + .gp { color: #93a1a1; } /* Generic.Prompt */ + .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */ + .gu { color: #cb4b16; } /* Generic.Subheading */ + .gt { color: #93a1a1; } /* Generic.Traceback */ + .kc { color: #cb4b16; } /* Keyword.Constant */ + .kd { color: #268bd2; } /* Keyword.Declaration */ + .kn { color: #859900; } /* Keyword.Namespace */ + .kp { color: #859900; } /* Keyword.Pseudo */ + .kr { color: #268bd2; } /* Keyword.Reserved */ + .kt { color: #dc322f; } /* Keyword.Type */ + .ld { color: #93a1a1; } /* Literal.Date */ + .m { color: #2aa198; } /* Literal.Number */ + .s { color: #2aa198; } /* Literal.String */ + .na { color: #93a1a1; } /* Name.Attribute */ + .nb { color: #b58900; } /* Name.Builtin */ + .nc { color: #268bd2; } /* Name.Class */ + .no { color: #cb4b16; } /* Name.Constant */ + .nd { color: #268bd2; } /* Name.Decorator */ + .ni { color: #cb4b16; } /* Name.Entity */ + .ne { color: #cb4b16; } /* Name.Exception */ + .nf { color: #268bd2; } /* Name.Function */ + .nl { color: #93a1a1; } /* Name.Label */ + .nn { color: #93a1a1; } /* Name.Namespace */ + .nx { color: #93a1a1; } /* Name.Other */ + .py { color: #93a1a1; } /* Name.Property */ + .nt { color: #268bd2; } /* Name.Tag */ + .nv { color: #268bd2; } /* Name.Variable */ + .ow { color: #859900; } /* Operator.Word */ + .w { color: #93a1a1; } /* Text.Whitespace */ + .mf { color: #2aa198; } /* Literal.Number.Float */ + .mh { color: #2aa198; } /* Literal.Number.Hex */ + .mi { color: #2aa198; } /* Literal.Number.Integer */ + .mo { color: #2aa198; } /* Literal.Number.Oct */ + .sb { color: #586e75; } /* Literal.String.Backtick */ + .sc { color: #2aa198; } /* Literal.String.Char */ + .sd { color: #93a1a1; } /* Literal.String.Doc */ + .s2 { color: #2aa198; } /* Literal.String.Double */ + .se { color: #cb4b16; } /* Literal.String.Escape */ + .sh { color: #93a1a1; } /* Literal.String.Heredoc */ + .si { color: #2aa198; } /* Literal.String.Interpol */ + .sx { color: #2aa198; } /* Literal.String.Other */ + .sr { color: #dc322f; } /* Literal.String.Regex */ + .s1 { color: #2aa198; } /* Literal.String.Single */ + .ss { color: #2aa198; } /* Literal.String.Symbol */ + .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2; } /* Name.Variable.Class */ + .vg { color: #268bd2; } /* Name.Variable.Global */ + .vi { color: #268bd2; } /* Name.Variable.Instance */ + .il { color: #2aa198; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 657bb5e3cd9..5956a28cafe 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -78,72 +78,72 @@ green #859900 operators, other keywords */ - .c { color: #93a1a1 } /* Comment */ - .err { color: #586e75 } /* Error */ - .g { color: #586e75 } /* Generic */ - .k { color: #859900 } /* Keyword */ - .l { color: #586e75 } /* Literal */ - .n { color: #586e75 } /* Name */ - .o { color: #859900 } /* Operator */ - .x { color: #cb4b16 } /* Other */ - .p { color: #586e75 } /* Punctuation */ - .cm { color: #93a1a1 } /* Comment.Multiline */ - .cp { color: #859900 } /* Comment.Preproc */ - .c1 { color: #93a1a1 } /* Comment.Single */ - .cs { color: #859900 } /* Comment.Special */ - .gd { color: #2aa198 } /* Generic.Deleted */ - .ge { color: #586e75; font-style: italic } /* Generic.Emph */ - .gr { color: #dc322f } /* Generic.Error */ - .gh { color: #cb4b16 } /* Generic.Heading */ - .gi { color: #859900 } /* Generic.Inserted */ - .go { color: #586e75 } /* Generic.Output */ - .gp { color: #586e75 } /* Generic.Prompt */ - .gs { color: #586e75; font-weight: bold } /* Generic.Strong */ - .gu { color: #cb4b16 } /* Generic.Subheading */ - .gt { color: #586e75 } /* Generic.Traceback */ - .kc { color: #cb4b16 } /* Keyword.Constant */ - .kd { color: #268bd2 } /* Keyword.Declaration */ - .kn { color: #859900 } /* Keyword.Namespace */ - .kp { color: #859900 } /* Keyword.Pseudo */ - .kr { color: #268bd2 } /* Keyword.Reserved */ - .kt { color: #dc322f } /* Keyword.Type */ - .ld { color: #586e75 } /* Literal.Date */ - .m { color: #2aa198 } /* Literal.Number */ - .s { color: #2aa198 } /* Literal.String */ - .na { color: #586e75 } /* Name.Attribute */ - .nb { color: #b58900 } /* Name.Builtin */ - .nc { color: #268bd2 } /* Name.Class */ - .no { color: #cb4b16 } /* Name.Constant */ - .nd { color: #268bd2 } /* Name.Decorator */ - .ni { color: #cb4b16 } /* Name.Entity */ - .ne { color: #cb4b16 } /* Name.Exception */ - .nf { color: #268bd2 } /* Name.Function */ - .nl { color: #586e75 } /* Name.Label */ - .nn { color: #586e75 } /* Name.Namespace */ - .nx { color: #586e75 } /* Name.Other */ - .py { color: #586e75 } /* Name.Property */ - .nt { color: #268bd2 } /* Name.Tag */ - .nv { color: #268bd2 } /* Name.Variable */ - .ow { color: #859900 } /* Operator.Word */ - .w { color: #586e75 } /* Text.Whitespace */ - .mf { color: #2aa198 } /* Literal.Number.Float */ - .mh { color: #2aa198 } /* Literal.Number.Hex */ - .mi { color: #2aa198 } /* Literal.Number.Integer */ - .mo { color: #2aa198 } /* Literal.Number.Oct */ - .sb { color: #93a1a1 } /* Literal.String.Backtick */ - .sc { color: #2aa198 } /* Literal.String.Char */ - .sd { color: #586e75 } /* Literal.String.Doc */ - .s2 { color: #2aa198 } /* Literal.String.Double */ - .se { color: #cb4b16 } /* Literal.String.Escape */ - .sh { color: #586e75 } /* Literal.String.Heredoc */ - .si { color: #2aa198 } /* Literal.String.Interpol */ - .sx { color: #2aa198 } /* Literal.String.Other */ - .sr { color: #dc322f } /* Literal.String.Regex */ - .s1 { color: #2aa198 } /* Literal.String.Single */ - .ss { color: #2aa198 } /* Literal.String.Symbol */ - .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2 } /* Name.Variable.Class */ - .vg { color: #268bd2 } /* Name.Variable.Global */ - .vi { color: #268bd2 } /* Name.Variable.Instance */ - .il { color: #2aa198 } /* Literal.Number.Integer.Long */ + .c { color: #93a1a1; } /* Comment */ + .err { color: #586e75; } /* Error */ + .g { color: #586e75; } /* Generic */ + .k { color: #859900; } /* Keyword */ + .l { color: #586e75; } /* Literal */ + .n { color: #586e75; } /* Name */ + .o { color: #859900; } /* Operator */ + .x { color: #cb4b16; } /* Other */ + .p { color: #586e75; } /* Punctuation */ + .cm { color: #93a1a1; } /* Comment.Multiline */ + .cp { color: #859900; } /* Comment.Preproc */ + .c1 { color: #93a1a1; } /* Comment.Single */ + .cs { color: #859900; } /* Comment.Special */ + .gd { color: #2aa198; } /* Generic.Deleted */ + .ge { color: #586e75; font-style: italic; } /* Generic.Emph */ + .gr { color: #dc322f; } /* Generic.Error */ + .gh { color: #cb4b16; } /* Generic.Heading */ + .gi { color: #859900; } /* Generic.Inserted */ + .go { color: #586e75; } /* Generic.Output */ + .gp { color: #586e75; } /* Generic.Prompt */ + .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */ + .gu { color: #cb4b16; } /* Generic.Subheading */ + .gt { color: #586e75; } /* Generic.Traceback */ + .kc { color: #cb4b16; } /* Keyword.Constant */ + .kd { color: #268bd2; } /* Keyword.Declaration */ + .kn { color: #859900; } /* Keyword.Namespace */ + .kp { color: #859900; } /* Keyword.Pseudo */ + .kr { color: #268bd2; } /* Keyword.Reserved */ + .kt { color: #dc322f; } /* Keyword.Type */ + .ld { color: #586e75; } /* Literal.Date */ + .m { color: #2aa198; } /* Literal.Number */ + .s { color: #2aa198; } /* Literal.String */ + .na { color: #586e75; } /* Name.Attribute */ + .nb { color: #b58900; } /* Name.Builtin */ + .nc { color: #268bd2; } /* Name.Class */ + .no { color: #cb4b16; } /* Name.Constant */ + .nd { color: #268bd2; } /* Name.Decorator */ + .ni { color: #cb4b16; } /* Name.Entity */ + .ne { color: #cb4b16; } /* Name.Exception */ + .nf { color: #268bd2; } /* Name.Function */ + .nl { color: #586e75; } /* Name.Label */ + .nn { color: #586e75; } /* Name.Namespace */ + .nx { color: #586e75; } /* Name.Other */ + .py { color: #586e75; } /* Name.Property */ + .nt { color: #268bd2; } /* Name.Tag */ + .nv { color: #268bd2; } /* Name.Variable */ + .ow { color: #859900; } /* Operator.Word */ + .w { color: #586e75; } /* Text.Whitespace */ + .mf { color: #2aa198; } /* Literal.Number.Float */ + .mh { color: #2aa198; } /* Literal.Number.Hex */ + .mi { color: #2aa198; } /* Literal.Number.Integer */ + .mo { color: #2aa198; } /* Literal.Number.Oct */ + .sb { color: #93a1a1; } /* Literal.String.Backtick */ + .sc { color: #2aa198; } /* Literal.String.Char */ + .sd { color: #586e75; } /* Literal.String.Doc */ + .s2 { color: #2aa198; } /* Literal.String.Double */ + .se { color: #cb4b16; } /* Literal.String.Escape */ + .sh { color: #586e75; } /* Literal.String.Heredoc */ + .si { color: #2aa198; } /* Literal.String.Interpol */ + .sx { color: #2aa198; } /* Literal.String.Other */ + .sr { color: #dc322f; } /* Literal.String.Regex */ + .s1 { color: #2aa198; } /* Literal.String.Single */ + .ss { color: #2aa198; } /* Literal.String.Symbol */ + .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2; } /* Name.Variable.Class */ + .vg { color: #268bd2; } /* Name.Variable.Global */ + .vi { color: #268bd2; } /* Name.Variable.Instance */ + .il { color: #2aa198; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 36a80a916b2..6f31a5235c0 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -86,7 +86,7 @@ background-color: #fafe3d !important; } - .hll { background-color: #f8f8f8 } + .hll { background-color: #f8f8f8; } .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss index 5bfe9bcb443..8d1a6020ca4 100644 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ b/app/assets/stylesheets/mailers/repository_push_email.scss @@ -78,7 +78,7 @@ span.highlight_word { background-color: #fafe3d !important; } -.hll { background-color: #f8f8f8 } +.hll { background-color: #f8f8f8; } .c { color: #998; font-style: italic; } .err { color: #a61717; background-color: #e3d2d2; } .k { font-weight: bold; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index fc12964872d..ced8c4a9907 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -2,22 +2,28 @@ img { max-width: 100%; height: auto; } + p.details { font-style: italic; - color: #777 + color: #777; } + .footer > p { font-size: small; - color: #777 + color: #777; } + pre.commit-message { white-space: pre-wrap; } + .file-stats > a { text-decoration: none; + > .new-file { color: #090; } + > .deleted-file { color: #b00; } diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index 8f71381f5c4..140d589024b 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -22,7 +22,7 @@ .admin-filter form { .select2-container { - width: 100% + width: 100%; } .controls { @@ -31,7 +31,7 @@ .form-actions { padding-left: 130px; - background: #fff + background: #fff; } .visibility-levels { @@ -106,26 +106,33 @@ .table { table-layout: fixed; } + .subheading { padding-bottom: $gl-padding; } + .message { word-wrap: break-word; } + .btn { white-space: normal; padding: $gl-btn-padding; } + th { width: 15%; + &.wide { width: 55%; } } + @media (max-width: $screen-sm-max) { th { width: 100%; } + td { width: 100%; float: left; @@ -137,6 +144,7 @@ margin-left: $btn-side-margin; margin-top: 3px; } + span { font-size: 19px; } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index fcaba954615..2fbf0cf34bf 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -137,6 +137,7 @@ .retry-link { color: $gl-link-color; + &:hover { text-decoration: underline; } @@ -218,6 +219,7 @@ .build-detail-row { margin-bottom: 5px; + &:last-of-type { margin-bottom: 0; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index 53ec0002afe..264e7e01a34 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -51,6 +51,7 @@ margin-left: 4px; } } + .commit-committer-link, .commit-author-link { color: $gl-gray; @@ -108,21 +109,25 @@ line-height: 20px; } } + .new-file { a { color: $gl-text-green; } } + .renamed-file { a { color: $gl-text-orange; } } + .deleted-file { a { color: $gl-text-red; } } + .edit-file { a { color: $gl-text-color; @@ -158,6 +163,7 @@ position: absolute; z-index: 1; } + > textarea { background-color: rgba(0, 0, 0, 0.0); font-family: inherit; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index dc57a837155..2b5621e20d6 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -161,6 +161,7 @@ .branch-commit { color: $gl-gray; + .commit-id, .commit-row-message { color: $gl-gray; } diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 42928ee279c..76225ed8d06 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -5,6 +5,7 @@ background: $background-color; border-top-left-radius: 0; } + border-top-left-radius: 0; } } @@ -17,6 +18,7 @@ float: left; @extend .col-md-2; } + .btn { margin-left: 5px; float: left; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 84094b0cb1b..bdc82a8f0f5 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -136,15 +136,18 @@ max-width: 50px; width: 35px; @include user-select(none); + a { float: left; width: 35px; font-weight: normal; + &:hover { text-decoration: underline; } } } + .line_content { display: block; margin: 0; @@ -164,10 +167,12 @@ white-space: pre-wrap; } } + .image { background: #ddd; text-align: center; padding: 30px; + .wrap { display: inline-block; } @@ -176,6 +181,7 @@ display: inline-block; background-color: #fff; line-height: 0; + img { border: 1px solid #fff; background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%), @@ -184,6 +190,7 @@ background-position: 0 0, 5px 5px; max-width: 100%; } + &.deleted { border: 1px solid $deleted; } @@ -192,6 +199,7 @@ border: 1px solid $added; } } + .image-info { font-size: 12px; margin: 5px 0 0; @@ -206,6 +214,7 @@ margin: auto; position: relative; } + .swipe-wrap { overflow: hidden; border-left: 1px solid #999; @@ -214,10 +223,12 @@ top: 13px; right: 7px; } + .frame { top: 0; right: 0; position: absolute; + &.deleted { margin: 0; display: block; @@ -225,6 +236,7 @@ right: 7px; } } + .swipe-bar { display: block; height: 100%; @@ -232,14 +244,17 @@ z-index: 100; position: absolute; cursor: pointer; + &:hover { .top-handle { background-position: -15px 3px; } + .bottom-handle { background-position: -15px -11px; } } + .top-handle { display: block; height: 14px; @@ -248,6 +263,7 @@ top: 0; background: image-url('swipemode_sprites.gif') 0 3px no-repeat; } + .bottom-handle { display: block; height: 14px; @@ -265,12 +281,14 @@ margin: auto; position: relative; } + .frame.added, .frame.deleted { position: absolute; display: block; top: 0; left: 0; } + .controls { display: block; height: 14px; @@ -324,6 +342,7 @@ } //.view.onion-skin } + .view-modes { padding: 10px; text-align: center; @@ -341,19 +360,24 @@ border-left: 1px solid #c1c1c1; padding: 0 12px 0 16px; cursor: pointer; + &:first-child { border-left: none; } + &:hover { text-decoration: underline; } + &.active { &:hover { text-decoration: none; } + cursor: default; color: #333; } + &.disabled { display: none; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index fcc5f32c738..029dabd2138 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -15,6 +15,7 @@ .cancel-btn { color: #b94a48; + &:hover { color: #b94a48; } @@ -70,16 +71,20 @@ .soft-wrap-toggle { margin: 0 $btn-side-margin; + .soft-wrap { display: block; } + .no-wrap { display: none; } + &.soft-wrap-active { .soft-wrap { display: none; } + .no-wrap { display: block; } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 789d6237df8..5d9a76dac05 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -78,6 +78,7 @@ margin-bottom: 0; } } + .event-note-icon { color: #777; float: left; @@ -86,6 +87,7 @@ margin-right: 5px; } } + .event_icon { position: relative; float: right; @@ -95,12 +97,13 @@ background: $gray-light; margin-left: 10px; top: -6px; + img { width: 20px; } } - &:last-child { border: none } + &:last-child { border: none; } .event_commits { li { @@ -109,6 +112,7 @@ padding: 3px; padding-left: 0; border: none; + .commit-row-title { font-size: $gl-font-size; } @@ -117,6 +121,7 @@ &.commits-stat { display: block; padding: 0 3px 0 0; + &:hover { background: none; } @@ -158,6 +163,7 @@ overflow: visible; max-width: 100%; } + .avatar { display: none; } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index edc9592f564..ee2a398f031 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -34,6 +34,7 @@ .group-right-buttons { position: absolute; right: 16px; + .btn { @include btn-gray; padding: 3px 10px; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 00ab42bec5c..a48b4c65db8 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -23,28 +23,28 @@ color: #555; tbody:first-child tr:first-child { - padding-top: 0 + padding-top: 0; } th { padding-top: 15px; line-height: 1.5; color: #333; - text-align: left + text-align: left; } td { padding-top: 3px; padding-bottom: 3px; vertical-align: top; - line-height: 20px + line-height: 20px; } .shortcut { padding-right: 10px; color: #999; text-align: right; - white-space: nowrap + white-space: nowrap; } .key { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 41079b6eeb5..230b927a17d 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -27,6 +27,7 @@ margin-right: 5px; margin-bottom: 5px; display: inline-block; + .color-label { padding: 6px 10px; } @@ -128,7 +129,7 @@ } .selectbox { - display: none + display: none; } .btn-clipboard { @@ -199,7 +200,7 @@ display: none; /* Small devices (tablets, 768px and up) */ @media (min-width: $screen-sm-min) { - display: block + display: block; } width: $sidebar_collapsed_width; @@ -276,7 +277,7 @@ } &.btn-primary { - @extend .btn-primary + @extend .btn-primary; } } @@ -400,6 +401,7 @@ .js-issuable-selector { width: 100%; } + @media (max-width: $screen-sm-max) { margin-bottom: $gl-padding; } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 3ac34cbc829..623da67a239 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -37,6 +37,7 @@ ul.related-merge-requests > li { display: -ms-flexbox; display: -webkit-flex; display: flex; + .merge-request-id { flex-shrink: 0; } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 701c29a3986..9bac6d46355 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -1,5 +1,6 @@ .suggest-colors { margin-top: 5px; + a { border-radius: 4px; width: 30px; diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss index 6926448519e..8290519dc25 100644 --- a/app/assets/stylesheets/pages/lint.scss +++ b/app/assets/stylesheets/pages/lint.scss @@ -3,6 +3,7 @@ font-size: 19px; color: red; } + .correct-syntax { font-size: 19px; color: #47a447; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index a08b033dff9..47d112dbbe3 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -68,7 +68,7 @@ a.forgot { float: right; - padding-top: 6px + padding-top: 6px; } .nav .active a { diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 5ec660799e3..49013d7cac9 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -131,6 +131,7 @@ $colors: ( } } } + &.head { background-color: map-get($colors, #{$color}_header_head_neutral); border-color: map-get($colors, #{$color}_header_head_neutral); @@ -174,6 +175,7 @@ $colors: ( background-color: map-get($colors, #{$color}_line_not_chosen); } } + &.head { background-color: map-get($colors, #{$color}_line_head_neutral); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 0ccb0ccfd14..afc4e517fde 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -10,6 +10,7 @@ form { margin-bottom: 0; + .clearfix { margin-bottom: 0; } @@ -46,6 +47,7 @@ &.right { float: right; + a { color: $gl-gray; } @@ -192,6 +194,7 @@ padding-top: 2px; padding-bottom: 2px; list-style: none; + &:hover { background: none; } @@ -215,6 +218,7 @@ padding-top: 20px; padding-bottom: 10px; } + svg { width: 230px; } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 8c2ba3ed58c..dd6d1783667 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -59,6 +59,7 @@ color: $gl-placeholder-color; margin-right: 5px; } + .avatar { float: none; } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index bd875b9823f..17f28959414 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -11,6 +11,7 @@ filter: alpha(opacity=100); } } + .diff-file, .discussion { .new-note { @@ -194,6 +195,7 @@ min-height: 140px; max-height: 500px; } + .note-form-actions { background: transparent; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index efeea96373f..fffcdc812a7 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -158,6 +158,7 @@ ul.notes { .diff-header > span { margin-right: 10px; } + .line_content { white-space: pre-wrap; } @@ -353,6 +354,7 @@ ul.notes { width: 32px; // "hide" it by default display: none; + &:hover { background: $gl-info; color: #fff; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 29dd03bfd60..7b71876b822 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -183,7 +183,7 @@ &::after { content: ''; width: 8px; - position: absolute;; + position: absolute; right: -7px; bottom: 8px; border-bottom: 2px solid $border-color; @@ -360,6 +360,7 @@ &:hover { background-color: $gray-lighter; + .dropdown-menu-toggle { background-color: transparent; } @@ -543,6 +544,7 @@ height: 29px; top: -9px; } + .curve { display: block; } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index c7eac5cf4b9..ed80d2beec2 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -243,6 +243,7 @@ .btn { -webkit-flex-grow: 1; flex-grow: 1; + &:first-child { margin-left: 0; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 530fb0c0d05..d30f02340b9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -17,34 +17,43 @@ &.features .control-label { font-weight: normal; } + .form-group { margin-bottom: 5px; } + &> .form-group { padding-left: 0; } } + .help-block { margin-bottom: 10px; } + .project-path { padding-right: 0; + .form-control { border-radius: $border-radius-base; } } + .input-group > div { &:last-child { padding-right: 0; } } + @media (max-width: $screen-xs-max) { .input-group > div { margin-bottom: 14px; + &:last-child { margin-bottom: 0; } } + fieldset > .form-group:first-child { padding-right: 0; } @@ -56,6 +65,7 @@ border-radius: 3px; border: 1px solid #e5e5e5; } + &+ .select2 a { border-top-left-radius: 0; border-bottom-left-radius: 0; @@ -201,6 +211,7 @@ pointer-events: none; } } + .count { @include btn-gray; display: inline-block; @@ -361,10 +372,12 @@ a.deploy-project-label { margin: $gl-padding; text-align: center; width: 169px; + &:hover, &.forked { background-color: $row-hover; border-color: $row-hover-border; } + .no-avatar { width: 100px; height: 100px; @@ -372,17 +385,20 @@ a.deploy-project-label { border: 1px solid $gray-dark; margin: 0 auto; border-radius: 50%; + i { font-size: 100px; color: $gray-dark; } } + a { display: block; width: 100%; height: 100%; padding-top: $gl-padding; color: $gl-gray; + .caption { min-height: 30px; padding: $gl-padding 0; @@ -644,6 +660,7 @@ pre.light-well { .clone-options { display: table-cell; + a.btn { width: 100%; } @@ -832,6 +849,7 @@ pre.light-well { .form-control { min-width: 100px; } + .select2-choice { border-top-right-radius: 0; border-bottom-right-radius: 0; diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index eec22c5dc96..7b3878c91df 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -6,6 +6,7 @@ &.runner-state-shared { background: #32b186; } + &.runner-state-specific { background: #3498db; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index c05f3d5ff32..f1d53c7b8bc 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -65,6 +65,7 @@ .ci-status-icon-success { color: $gl-success; } + .ci-status-icon-failed { color: $gl-danger; } @@ -77,6 +78,7 @@ .ci-status-icon-running { color: $blue-normal; } + .ci-status-icon-canceled, .ci-status-icon-disabled, .ci-status-icon-not-found, diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 41ad10f07bd..99c0f6362d0 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -5,6 +5,7 @@ .file-finder { width: 50%; + .file-finder-input { width: 95%; display: inline-block; diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index c9846103762..3fa7fa3d7e3 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -23,15 +23,19 @@ .term-bold { font-weight: bold; } + .term-italic { font-style: italic; } + .term-conceal { visibility: hidden; } + .term-underline { text-decoration: underline; } + .term-cross { text-decoration: line-through; } @@ -39,48 +43,63 @@ .term-fg-black { color: $black; } + .term-fg-red { color: $red; } + .term-fg-green { color: $green; } + .term-fg-yellow { color: $yellow; } + .term-fg-blue { color: $blue; } + .term-fg-magenta { color: $magenta; } + .term-fg-cyan { color: $cyan; } + .term-fg-white { color: $white; } + .term-fg-l-black { color: $l-black; } + .term-fg-l-red { color: $l-red; } + .term-fg-l-green { color: $l-green; } + .term-fg-l-yellow { color: $l-yellow; } + .term-fg-l-blue { color: $l-blue; } + .term-fg-l-magenta { color: $l-magenta; } + .term-fg-l-cyan { color: $l-cyan; } + .term-fg-l-white { color: $l-white; } @@ -88,818 +107,1087 @@ .term-bg-black { background-color: $black; } + .term-bg-red { background-color: $red; } + .term-bg-green { background-color: $green; } + .term-bg-yellow { background-color: $yellow; } + .term-bg-blue { background-color: $blue; } + .term-bg-magenta { background-color: $magenta; } + .term-bg-cyan { background-color: $cyan; } + .term-bg-white { background-color: $white; } + .term-bg-l-black { background-color: $l-black; } + .term-bg-l-red { background-color: $l-red; } + .term-bg-l-green { background-color: $l-green; } + .term-bg-l-yellow { background-color: $l-yellow; } + .term-bg-l-blue { background-color: $l-blue; } + .term-bg-l-magenta { background-color: $l-magenta; } + .term-bg-l-cyan { background-color: $l-cyan; } + .term-bg-l-white { background-color: $l-white; } - .xterm-fg-0 { color: #000; } + .xterm-fg-1 { color: #800000; } + .xterm-fg-2 { color: #008000; } + .xterm-fg-3 { color: #808000; } + .xterm-fg-4 { color: #000080; } + .xterm-fg-5 { color: #800080; } + .xterm-fg-6 { color: #008080; } + .xterm-fg-7 { color: #c0c0c0; } + .xterm-fg-8 { color: #808080; } + .xterm-fg-9 { color: #f00; } + .xterm-fg-10 { color: #0f0; } + .xterm-fg-11 { color: #ff0; } + .xterm-fg-12 { color: #00f; } + .xterm-fg-13 { color: #f0f; } + .xterm-fg-14 { color: #0ff; } + .xterm-fg-15 { color: #fff; } + .xterm-fg-16 { color: #000; } + .xterm-fg-17 { color: #00005f; } + .xterm-fg-18 { color: #000087; } + .xterm-fg-19 { color: #0000af; } + .xterm-fg-20 { color: #0000d7; } + .xterm-fg-21 { color: #00f; } + .xterm-fg-22 { color: #005f00; } + .xterm-fg-23 { color: #005f5f; } + .xterm-fg-24 { color: #005f87; } + .xterm-fg-25 { color: #005faf; } + .xterm-fg-26 { color: #005fd7; } + .xterm-fg-27 { color: #005fff; } + .xterm-fg-28 { color: #008700; } + .xterm-fg-29 { color: #00875f; } + .xterm-fg-30 { color: #008787; } + .xterm-fg-31 { color: #0087af; } + .xterm-fg-32 { color: #0087d7; } + .xterm-fg-33 { color: #0087ff; } + .xterm-fg-34 { color: #00af00; } + .xterm-fg-35 { color: #00af5f; } + .xterm-fg-36 { color: #00af87; } + .xterm-fg-37 { color: #00afaf; } + .xterm-fg-38 { color: #00afd7; } + .xterm-fg-39 { color: #00afff; } + .xterm-fg-40 { color: #00d700; } + .xterm-fg-41 { color: #00d75f; } + .xterm-fg-42 { color: #00d787; } + .xterm-fg-43 { color: #00d7af; } + .xterm-fg-44 { color: #00d7d7; } + .xterm-fg-45 { color: #00d7ff; } + .xterm-fg-46 { color: #0f0; } + .xterm-fg-47 { color: #00ff5f; } + .xterm-fg-48 { color: #00ff87; } + .xterm-fg-49 { color: #00ffaf; } + .xterm-fg-50 { color: #00ffd7; } + .xterm-fg-51 { color: #0ff; } + .xterm-fg-52 { color: #5f0000; } + .xterm-fg-53 { color: #5f005f; } + .xterm-fg-54 { color: #5f0087; } + .xterm-fg-55 { color: #5f00af; } + .xterm-fg-56 { color: #5f00d7; } + .xterm-fg-57 { color: #5f00ff; } + .xterm-fg-58 { color: #5f5f00; } + .xterm-fg-59 { color: #5f5f5f; } + .xterm-fg-60 { color: #5f5f87; } + .xterm-fg-61 { color: #5f5faf; } + .xterm-fg-62 { color: #5f5fd7; } + .xterm-fg-63 { color: #5f5fff; } + .xterm-fg-64 { color: #5f8700; } + .xterm-fg-65 { color: #5f875f; } + .xterm-fg-66 { color: #5f8787; } + .xterm-fg-67 { color: #5f87af; } + .xterm-fg-68 { color: #5f87d7; } + .xterm-fg-69 { color: #5f87ff; } + .xterm-fg-70 { color: #5faf00; } + .xterm-fg-71 { color: #5faf5f; } + .xterm-fg-72 { color: #5faf87; } + .xterm-fg-73 { color: #5fafaf; } + .xterm-fg-74 { color: #5fafd7; } + .xterm-fg-75 { color: #5fafff; } + .xterm-fg-76 { color: #5fd700; } + .xterm-fg-77 { color: #5fd75f; } + .xterm-fg-78 { color: #5fd787; } + .xterm-fg-79 { color: #5fd7af; } + .xterm-fg-80 { color: #5fd7d7; } + .xterm-fg-81 { color: #5fd7ff; } + .xterm-fg-82 { color: #5fff00; } + .xterm-fg-83 { color: #5fff5f; } + .xterm-fg-84 { color: #5fff87; } + .xterm-fg-85 { color: #5fffaf; } + .xterm-fg-86 { color: #5fffd7; } + .xterm-fg-87 { color: #5fffff; } + .xterm-fg-88 { color: #870000; } + .xterm-fg-89 { color: #87005f; } + .xterm-fg-90 { color: #870087; } + .xterm-fg-91 { color: #8700af; } + .xterm-fg-92 { color: #8700d7; } + .xterm-fg-93 { color: #8700ff; } + .xterm-fg-94 { color: #875f00; } + .xterm-fg-95 { color: #875f5f; } + .xterm-fg-96 { color: #875f87; } + .xterm-fg-97 { color: #875faf; } + .xterm-fg-98 { color: #875fd7; } + .xterm-fg-99 { color: #875fff; } + .xterm-fg-100 { color: #878700; } + .xterm-fg-101 { color: #87875f; } + .xterm-fg-102 { color: #878787; } + .xterm-fg-103 { color: #8787af; } + .xterm-fg-104 { color: #8787d7; } + .xterm-fg-105 { color: #8787ff; } + .xterm-fg-106 { color: #87af00; } + .xterm-fg-107 { color: #87af5f; } + .xterm-fg-108 { color: #87af87; } + .xterm-fg-109 { color: #87afaf; } + .xterm-fg-110 { color: #87afd7; } + .xterm-fg-111 { color: #87afff; } + .xterm-fg-112 { color: #87d700; } + .xterm-fg-113 { color: #87d75f; } + .xterm-fg-114 { color: #87d787; } + .xterm-fg-115 { color: #87d7af; } + .xterm-fg-116 { color: #87d7d7; } + .xterm-fg-117 { color: #87d7ff; } + .xterm-fg-118 { color: #87ff00; } + .xterm-fg-119 { color: #87ff5f; } + .xterm-fg-120 { color: #87ff87; } + .xterm-fg-121 { color: #87ffaf; } + .xterm-fg-122 { color: #87ffd7; } + .xterm-fg-123 { color: #87ffff; } + .xterm-fg-124 { color: #af0000; } + .xterm-fg-125 { color: #af005f; } + .xterm-fg-126 { color: #af0087; } + .xterm-fg-127 { color: #af00af; } + .xterm-fg-128 { color: #af00d7; } + .xterm-fg-129 { color: #af00ff; } + .xterm-fg-130 { color: #af5f00; } + .xterm-fg-131 { color: #af5f5f; } + .xterm-fg-132 { color: #af5f87; } + .xterm-fg-133 { color: #af5faf; } + .xterm-fg-134 { color: #af5fd7; } + .xterm-fg-135 { color: #af5fff; } + .xterm-fg-136 { color: #af8700; } + .xterm-fg-137 { color: #af875f; } + .xterm-fg-138 { color: #af8787; } + .xterm-fg-139 { color: #af87af; } + .xterm-fg-140 { color: #af87d7; } + .xterm-fg-141 { color: #af87ff; } + .xterm-fg-142 { color: #afaf00; } + .xterm-fg-143 { color: #afaf5f; } + .xterm-fg-144 { color: #afaf87; } + .xterm-fg-145 { color: #afafaf; } + .xterm-fg-146 { color: #afafd7; } + .xterm-fg-147 { color: #afafff; } + .xterm-fg-148 { color: #afd700; } + .xterm-fg-149 { color: #afd75f; } + .xterm-fg-150 { color: #afd787; } + .xterm-fg-151 { color: #afd7af; } + .xterm-fg-152 { color: #afd7d7; } + .xterm-fg-153 { color: #afd7ff; } + .xterm-fg-154 { color: #afff00; } + .xterm-fg-155 { color: #afff5f; } + .xterm-fg-156 { color: #afff87; } + .xterm-fg-157 { color: #afffaf; } + .xterm-fg-158 { color: #afffd7; } + .xterm-fg-159 { color: #afffff; } + .xterm-fg-160 { color: #d70000; } + .xterm-fg-161 { color: #d7005f; } + .xterm-fg-162 { color: #d70087; } + .xterm-fg-163 { color: #d700af; } + .xterm-fg-164 { color: #d700d7; } + .xterm-fg-165 { color: #d700ff; } + .xterm-fg-166 { color: #d75f00; } + .xterm-fg-167 { color: #d75f5f; } + .xterm-fg-168 { color: #d75f87; } + .xterm-fg-169 { color: #d75faf; } + .xterm-fg-170 { color: #d75fd7; } + .xterm-fg-171 { color: #d75fff; } + .xterm-fg-172 { color: #d78700; } + .xterm-fg-173 { color: #d7875f; } + .xterm-fg-174 { color: #d78787; } + .xterm-fg-175 { color: #d787af; } + .xterm-fg-176 { color: #d787d7; } + .xterm-fg-177 { color: #d787ff; } + .xterm-fg-178 { color: #d7af00; } + .xterm-fg-179 { color: #d7af5f; } + .xterm-fg-180 { color: #d7af87; } + .xterm-fg-181 { color: #d7afaf; } + .xterm-fg-182 { color: #d7afd7; } + .xterm-fg-183 { color: #d7afff; } + .xterm-fg-184 { color: #d7d700; } + .xterm-fg-185 { color: #d7d75f; } + .xterm-fg-186 { color: #d7d787; } + .xterm-fg-187 { color: #d7d7af; } + .xterm-fg-188 { color: #d7d7d7; } + .xterm-fg-189 { color: #d7d7ff; } + .xterm-fg-190 { color: #d7ff00; } + .xterm-fg-191 { color: #d7ff5f; } + .xterm-fg-192 { color: #d7ff87; } + .xterm-fg-193 { color: #d7ffaf; } + .xterm-fg-194 { color: #d7ffd7; } + .xterm-fg-195 { color: #d7ffff; } + .xterm-fg-196 { color: #f00; } + .xterm-fg-197 { color: #ff005f; } + .xterm-fg-198 { color: #ff0087; } + .xterm-fg-199 { color: #ff00af; } + .xterm-fg-200 { color: #ff00d7; } + .xterm-fg-201 { color: #f0f; } + .xterm-fg-202 { color: #ff5f00; } + .xterm-fg-203 { color: #ff5f5f; } + .xterm-fg-204 { color: #ff5f87; } + .xterm-fg-205 { color: #ff5faf; } + .xterm-fg-206 { color: #ff5fd7; } + .xterm-fg-207 { color: #ff5fff; } + .xterm-fg-208 { color: #ff8700; } + .xterm-fg-209 { color: #ff875f; } + .xterm-fg-210 { color: #ff8787; } + .xterm-fg-211 { color: #ff87af; } + .xterm-fg-212 { color: #ff87d7; } + .xterm-fg-213 { color: #ff87ff; } + .xterm-fg-214 { color: #ffaf00; } + .xterm-fg-215 { color: #ffaf5f; } + .xterm-fg-216 { color: #ffaf87; } + .xterm-fg-217 { color: #ffafaf; } + .xterm-fg-218 { color: #ffafd7; } + .xterm-fg-219 { color: #ffafff; } + .xterm-fg-220 { color: #ffd700; } + .xterm-fg-221 { color: #ffd75f; } + .xterm-fg-222 { color: #ffd787; } + .xterm-fg-223 { color: #ffd7af; } + .xterm-fg-224 { color: #ffd7d7; } + .xterm-fg-225 { color: #ffd7ff; } + .xterm-fg-226 { color: #ff0; } + .xterm-fg-227 { color: #ffff5f; } + .xterm-fg-228 { color: #ffff87; } + .xterm-fg-229 { color: #ffffaf; } + .xterm-fg-230 { color: #ffffd7; } + .xterm-fg-231 { color: #fff; } + .xterm-fg-232 { color: #080808; } + .xterm-fg-233 { color: #121212; } + .xterm-fg-234 { color: #1c1c1c; } + .xterm-fg-235 { color: #262626; } + .xterm-fg-236 { color: #303030; } + .xterm-fg-237 { color: #3a3a3a; } + .xterm-fg-238 { color: #444; } + .xterm-fg-239 { color: #4e4e4e; } + .xterm-fg-240 { color: #585858; } + .xterm-fg-241 { color: #626262; } + .xterm-fg-242 { color: #6c6c6c; } + .xterm-fg-243 { color: #767676; } + .xterm-fg-244 { color: #808080; } + .xterm-fg-245 { color: #8a8a8a; } + .xterm-fg-246 { color: #949494; } + .xterm-fg-247 { color: #9e9e9e; } + .xterm-fg-248 { color: #a8a8a8; } + .xterm-fg-249 { color: #b2b2b2; } + .xterm-fg-250 { color: #bcbcbc; } + .xterm-fg-251 { color: #c6c6c6; } + .xterm-fg-252 { color: #d0d0d0; } + .xterm-fg-253 { color: #dadada; } + .xterm-fg-254 { color: #e4e4e4; } + .xterm-fg-255 { color: #eee; } -- cgit v1.2.1 From 2b9a25bd5a69c3c6a5bb24bb67838a4d354204e1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 00:11:19 +0300 Subject: Handle unmatched routing with not_found method We need this to prevent routing error when user access URL like /123 when there is no resource located under such name Signed-off-by: Dmitriy Zaporozhets --- app/controllers/application_controller.rb | 4 ++++ config/routes.rb | 2 ++ 2 files changed, 6 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b3455e04c29..705824502eb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -45,6 +45,10 @@ class ApplicationController < ActionController::Base redirect_to request.referer.present? ? :back : default, options end + def not_found + render_404 + end + protected # This filter handles both private tokens and personal access tokens diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..659ea51bc75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,4 +88,6 @@ Rails.application.routes.draw do get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } root to: "root#index" + + get '*unmatched_route', to: 'application#not_found' end -- cgit v1.2.1 From d60d5fe4e422ecd83437653bc5764c6269162125 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 01:36:05 +0300 Subject: Improve ExtractsPath logic related to atom format * Don't set request format to atom if '.atom' suffix was not provided * Don't try '.atom' detection logic on request that uses extended_sha1 Signed-off-by: Dmitriy Zaporozhets --- lib/extracts_path.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index e4d996a3fb6..9b74364849e 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -113,17 +113,18 @@ module ExtractsPath @id = get_id @ref, @path = extract_ref(@id) @repo = @project.repository - if @options[:extended_sha1].blank? - @commit = @repo.commit(@ref) - else - @commit = @repo.commit(@options[:extended_sha1]) - end - if @path.empty? && !@commit - @id = @ref = extract_ref_without_atom(@id) + if @options[:extended_sha1].present? + @commit = @repo.commit(@options[:extended_sha1]) + else @commit = @repo.commit(@ref) - request.format = :atom if @commit + if @path.empty? && !@commit && @id.ends_with?('.atom') + @id = @ref = extract_ref_without_atom(@id) + @commit = @repo.commit(@ref) + + request.format = :atom if @commit + end end raise InvalidPathError unless @commit -- cgit v1.2.1 From b0622d657893b156ac0c265f397efeb28d5b0b4c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 15 Oct 2016 01:48:14 +0300 Subject: Revert "Update git over http test to match new routing" This reverts commit 68ab7047dae98172a0bd8b92956f2ee51b9167a0. --- spec/requests/git_http_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5a1ed7d4a25..27f0fd22ae6 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -412,9 +412,10 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } + before { get path, params } - it "fails to find a route" do - expect { get(path, params) }.to raise_error(ActionController::RoutingError) + it "redirects to the sign-in page" do + expect(response).to redirect_to(new_user_session_path) end end end -- cgit v1.2.1 From 5b5c7e048b6e975a655478b0e5d421c2438276aa Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 16:58:55 -0700 Subject: Fix trending projects Spinach failure The trending projects list is now pre-calculated. To make this work with the Spinach test, we have to manually refresh the list. Partial fix to #23378 --- features/explore/projects.feature | 1 + features/steps/shared/project.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/features/explore/projects.feature b/features/explore/projects.feature index 092e18d1b86..4e0f4486ab7 100644 --- a/features/explore/projects.feature +++ b/features/explore/projects.feature @@ -128,6 +128,7 @@ Feature: Explore Projects And project "Archive" has comments And I sign in as a user And project "Community" has comments + And trending projects are refreshed When I visit the explore trending projects Then I should see project "Community" And I should not see project "Internal" diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index afbd8ef1233..cab85a48396 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -218,6 +218,10 @@ module SharedProject 2.times { create(:note_on_issue, project: project) } end + step 'trending projects are refreshed' do + TrendingProject.refresh! + end + step 'project "Shop" has labels: "bug", "feature", "enhancement"' do project = Project.find_by(name: "Shop") create(:label, project: project, title: 'bug') -- cgit v1.2.1 From 762b63e56965223bd217224a407f15ec6b1b1d3d Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 17:07:38 -0700 Subject: Fix Spinach failure due overprecise percentage matching The percentages in the language match changed by a tenth of a percentage point for Ruby and JavaScript, which led to this failure. Partial fix to #23378 --- features/steps/project/graph.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index b09ec86e5df..7490d2bc6e7 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -19,8 +19,8 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps end step 'page should have languages graphs' do - expect(page).to have_content "Ruby 66.63 %" - expect(page).to have_content "JavaScript 22.96 %" + expect(page).to have_content /Ruby 66.* %/ + expect(page).to have_content /JavaScript 22.* %/ end step 'page should have commits graphs' do -- cgit v1.2.1 From 1dd826d4aad2ce6c195bad24b458b1967b74db1d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 9 Sep 2016 14:21:00 +0200 Subject: Make UX upgrades to SignIn/Register views. - Tab between register and sign in forms - Add individual input validation error messages - Validate username - Update many styles for all login-box forms --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js | 11 + app/assets/javascripts/gl_field_errors.js.es6 | 100 +++ app/assets/javascripts/username_validator.js.es6 | 131 ++++ app/assets/stylesheets/framework/buttons.scss | 3 +- app/assets/stylesheets/framework/forms.scss | 13 +- app/assets/stylesheets/pages/login.scss | 138 +++- app/controllers/users_controller.rb | 6 +- app/views/admin/appearances/preview.html.haml | 17 +- app/views/devise/confirmations/new.html.haml | 12 +- app/views/devise/passwords/edit.html.haml | 20 +- app/views/devise/passwords/new.html.haml | 10 +- app/views/devise/sessions/_new_base.html.haml | 10 +- app/views/devise/sessions/_new_crowd.html.haml | 10 +- app/views/devise/sessions/_new_ldap.html.haml | 10 +- app/views/devise/sessions/new.html.haml | 32 +- app/views/devise/sessions/two_factor.html.haml | 17 +- app/views/devise/shared/_omniauth_box.html.haml | 15 +- app/views/devise/shared/_sign_in_link.html.haml | 1 - app/views/devise/shared/_signin_box.html.haml | 37 +- app/views/devise/shared/_signup_box.html.haml | 36 +- app/views/devise/shared/_tab_single.html.haml | 4 + app/views/devise/shared/_tabs_ldap.html.haml | 10 + app/views/devise/shared/_tabs_normal.html.haml | 5 + app/views/devise/unlocks/new.html.haml | 10 +- app/views/layouts/devise.html.haml | 59 +- app/views/u2f/_authenticate.html.haml | 2 +- config/routes.rb | 792 +++++++++++++++++++++++ spec/features/signup_spec.rb | 8 +- spec/features/u2f_spec.rb | 14 +- spec/features/users_spec.rb | 32 +- spec/javascripts/u2f/authenticate_spec.js | 2 +- 32 files changed, 1391 insertions(+), 177 deletions(-) create mode 100644 app/assets/javascripts/gl_field_errors.js.es6 create mode 100644 app/assets/javascripts/username_validator.js.es6 create mode 100644 app/views/devise/shared/_tab_single.html.haml create mode 100644 app/views/devise/shared/_tabs_ldap.html.haml create mode 100644 app/views/devise/shared/_tabs_normal.html.haml diff --git a/CHANGELOG b/CHANGELOG index 194ee18b74c..e3201cd2250 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -377,6 +377,7 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. + - Login/Register UX upgrade !6328 v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 858621218f8..fb6e82cd37c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -8,6 +8,7 @@ Dispatcher = (function() { function Dispatcher() { this.initSearch(); + this.initFieldErrors(); this.initPageScripts(); } @@ -20,6 +21,10 @@ path = page.split(':'); shortcut_handler = null; switch (page) { + case 'sessions:new': + case 'sessions:create': + new UsernameValidator(); + break; case 'projects:boards:show': case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); @@ -291,6 +296,12 @@ } }; + Dispatcher.prototype.initFieldErrors = function() { + $('form.show-gl-field-errors').each(function(i, form) { + new gl.GlFieldErrors(form); + }); + }; + return Dispatcher; })(); diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 new file mode 100644 index 00000000000..42a2ddeeafe --- /dev/null +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -0,0 +1,100 @@ +((global) => { + /* + * This class overrides the browser's validation error bubbles, displaying custom + * error messages for invalid fields instead. To begin validating any form, add the + * class `show-gl-field-errors` to the form element, and ensure error messages are + * declared in each inputs' title attribute. + * + * Example: + * + *
    + * + *
    + * + * */ + + const fieldErrorClass = 'gl-field-error'; + const fieldErrorSelector = `.${fieldErrorClass}`; + const inputErrorClass = 'gl-field-error-outline'; + + class GlFieldErrors { + constructor(form) { + this.form = $(form); + this.initValidators(); + } + + initValidators () { + this.inputs = this.form.find(':input:not([type=hidden])').toArray(); + this.inputs.forEach((input) => { + $(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + }); + this.form.on('submit', this.catchInvalidFormSubmit); + } + + /* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ + catchInvalidFormSubmit (event) { + if (!event.currentTarget.checkValidity()) { + event.preventDefault(); + // Prevents disabling of invalid submit button by application.js + event.stopPropagation(); + } + } + + handleInvalidInput (event) { + event.preventDefault(); + this.updateFieldValidityState(event); + + const $input = $(event.currentTarget); + + // For UX, wait til after first invalid submission to check each keyup + $input.off('keyup.field_validator') + .on('keyup.field_validator', this.updateFieldValidityState.bind(this)); + + } + + displayFieldValidity (target, isValid) { + const $input = $(target).removeClass(inputErrorClass); + const $existingError = $input.siblings(fieldErrorSelector); + const alreadyInvalid = !!$existingError.length; + const implicitErrorMessage = $input.attr('title'); + const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`

    ${implicitErrorMessage}

    `); + + if (!isValid) { + $input.after($errorToDisplay); + $input.addClass(inputErrorClass); + } + + this.updateFieldSiblings($errorToDisplay, isValid); + } + + updateFieldSiblings($target, isValid) { + const siblings = $target.siblings(`p${fieldErrorSelector}`); + return isValid ? siblings.show() : siblings.hide(); + } + + checkFieldValidity(target) { + return target.validity.valid; + } + + updateFieldValidityState(event) { + const target = event.currentTarget; + const isKeyup = event.type === 'keyup'; + const isValid = this.checkFieldValidity(target); + + this.displayFieldValidity(target, isValid); + + // prevent changing focus while user is typing. + if (!isKeyup) { + this.focusOnFirstInvalid.apply(this); + } + } + + focusOnFirstInvalid () { + const firstInvalid = this.inputs.find((input) => !input.validity.valid); + $(firstInvalid).focus(); + } + } + + global.GlFieldErrors = GlFieldErrors; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 new file mode 100644 index 00000000000..f8be356af04 --- /dev/null +++ b/app/assets/javascripts/username_validator.js.es6 @@ -0,0 +1,131 @@ +((global) => { + const debounceTimeoutDuration = 1000; + const inputErrorClass = 'gl-field-error-outline'; + const inputSuccessClass = 'gl-field-success-outline'; + const messageErrorSelector = '.username .validation-error'; + const messageSuccessSelector = '.username .validation-success'; + const messagePendingSelector = '.username .validation-pending'; + + class UsernameValidator { + constructor() { + this.inputElement = $('#new_user_username'); + this.inputDomElement = this.inputElement.get(0); + + this.available = false; + this.valid = false; + this.pending = false; + this.fresh = true; + this.empty = true; + + const debounceTimeout = _.debounce((username) => { + this.validateUsername(username); + }, debounceTimeoutDuration); + + this.inputElement.on('keyup.username_check', () => { + const username = this.inputElement.val(); + + this.valid = this.inputDomElement.validity.valid; + this.fresh = false; + this.empty = !username.length; + + if (this.valid) { + return debounceTimeout(username); + } + + this.renderState(); + }); + + // Override generic field validation + this.inputElement.on('invalid', this.handleInvalidInput.bind(this)); + } + + renderState() { + // Clear all state + this.clearFieldValidationState(); + + if (this.valid && this.available) { + return this.setSuccessState(); + } + + if (this.empty) { + return this.clearFieldValidationState(); + } + + if (this.pending) { + return this.setPendingState(); + } + + if (!this.available) { + return this.setUnavailableState(); + } + + if (!this.valid) { + return this.setInvalidState(); + } + } + + handleInvalidInput(event) { + event.preventDefault(); + event.stopPropagation(); + } + + validateUsername(username) { + if (this.valid) { + this.pending = true; + this.available = false; + this.renderState(); + return $.ajax({ + type: 'GET', + url: `/u/${username}/exists`, + dataType: 'json', + success: (res) => this.updateValidationState(res.exists) + }); + } + } + + updateValidationState(usernameTaken) { + if (usernameTaken) { + this.valid = false; + this.available = false; + } else { + this.available = true; + } + this.pending = false; + this.renderState(); + } + + clearFieldValidationState() { + this.inputElement.siblings('p').hide(); + this.inputElement.removeClass(inputErrorClass); + this.inputElement.removeClass(inputSuccessClass); + } + + setUnavailableState() { + const $usernameErrorMessage = this.inputElement.siblings(messageErrorSelector); + this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); + $usernameErrorMessage.show(); + } + + setSuccessState() { + const $usernameSuccessMessage = this.inputElement.siblings(messageSuccessSelector); + this.inputElement.addClass(inputSuccessClass).removeClass(inputErrorClass); + $usernameSuccessMessage.show(); + } + + setPendingState(show) { + const $usernamePendingMessage = $(messagePendingSelector); + if (this.pending) { + $usernamePendingMessage.show(); + } else { + $usernamePendingMessage.hide(); + } + } + + setInvalidState() { + this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); + $(`.gl-field-error`).show(); + } + } + + global.UsernameValidator = UsernameValidator; +})(window); diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 7c0ed72dbc5..e6656c2d69a 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -152,7 +152,8 @@ @include btn-blue-medium; } - &.btn-info { + &.btn-info, + &.btn-register { @include btn-blue; } diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 311e3fa1a35..761c07384f4 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -73,8 +73,8 @@ label { } .form-control { - box-shadow: none; - border-radius: 3px; + @include box-shadow(none); + border-radius: 2px; padding: $gl-vert-padding $gl-input-padding; } @@ -127,3 +127,12 @@ label { border-right: 0; } } + +.help-block { + margin-bottom: 0; +} + +.gl-field-error { + color: $red-normal; +} + diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 47d112dbbe3..06b90fbefab 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -17,6 +17,7 @@ line-height: 1.5; p { + font-size: 18px; color: #888; } @@ -36,10 +37,13 @@ } } + p { + font-size: 13px; + } .login-box { - background: #fafafa; - border-radius: 10px; - box-shadow: 0 0 2px #ccc; + box-shadow: 0 0 0 1px $border-color; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; padding: 15px; .login-heading h3 { @@ -74,7 +78,6 @@ .nav .active a { background: transparent; } - } .form-control { font-size: 14px; @@ -92,18 +95,109 @@ border-top: 0; margin-bottom: 20px; } + } + + // Styles the glowing border of focused input for username async validation + .login-body { + font-size: 13px; + + + input + p { + margin-top: 5px; + } + + .gl-field-success-outline { + border: 1px solid $green-normal; + + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } + + .gl-field-error-outline { + border: 1px solid $red-normal; + + &:focus { + opacity: .6; + box-shadow: 0 0 0 1px $red-normal inset, 0 0 4px 0 $red-normal; + border: 0 none; + } + } + + .username .validation-success, + .gl-field-success-message { + color: $green-normal; + } + + .username .validation-error, + .gl-field-error-message { + color: $red-normal; + } + + .gl-field-hint { + color: $gl-text-color; + } + + } + + .new-session-tabs { // Are these being applied to other login-related screens? They need to be. + display: flex; + box-shadow: 0 0 0 1px $border-color; + border-top-right-radius: 2px; + border-top-left-radius: 2px; + + li { + flex: 1; + text-align: center; &.middle { border-top: 0; margin-bottom: 0; border-radius: 0; + &:last-of-type { + border-left: 1px solid $border-color; + } + + &:not(.active) { + background-color: $gray-light; + } + + a { + width: 100%; + font-size: 18px; + &:hover { + border: 1px solid transparent; + } + } + + &.active { + border-bottom: 1px solid $border-color; + + a { + border: none; + border-bottom: 2px solid $link-underline-blue; + color: $black; + + &:hover { + border-bottom: 2px solid $link-underline-blue; + } + } + } } + } + + .form-control { &:active, &:focus { background-color: #fff; } } + label { + font-weight: normal; + } + .devise-errors { h2 { margin-top: 0; @@ -111,14 +205,6 @@ color: #a00; } } - - .remember-me { - margin-top: -10px; - - label { - font-weight: normal; - } - } } @media (max-width: $screen-xs-max) { @@ -137,3 +223,31 @@ height: 32px; } } + +.devise-layout-html { + margin: 0; + padding: 0; + height: 100%; +} + +// Fixes footer container to bottom of viewport +.devise-layout-html body { + // offset height of fixed header + 1 to avoid scroll + height: calc(100% - 51px); + margin: 0; + padding: 0; + + .page-wrap { + min-height: 100%; + position: relative; + } + + .footer-container, hr.footer-fixed { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: $white-light; + } +} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 838ecc837e4..30f0118254a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController skip_before_action :authenticate_user! - before_action :user + before_action :user, except: [:exists] before_action :authorize_read_user!, only: [:show] def show @@ -85,6 +85,10 @@ class UsersController < ApplicationController render 'calendar_activities', layout: false end + def exists + render json: { exists: !User.find_by_username(params[:username]).nil? } + end + private def authorize_read_user! diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index 6c51639b840..0d35702c634 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,9 +1,12 @@ -- page_title "Preview | Appearance" += render 'devise/shared/tab_single', { :tab_title => 'Sign in preview' } .login-box - .login-heading - %h3 Existing user? Sign in - %form - = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" - = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" - = button_tag "Sign in", class: "btn-create btn" + %form.show-gl-field-errors + .form-group + = label_tag :login + = text_field_tag :login, nil, class: "form-control top", title: 'Please provide your username or email address.' + .form-group + = label_tag :password + = password_field_tag :password, nil, class: "form-control bottom", title: 'This field is required.' + .form-group + = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 970ba147111..443a316c6e2 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,14 +1,14 @@ += render 'devise/shared/tab_single', { :tab_title => 'Resend confirmation instructions' } .login-box - .login-heading - %h3 Resend confirmation instructions .login-body - = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, placeholder: 'Email', class: "form-control", required: true + .form-group + = f.label :email + = f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.' .clearfix - = f.submit "Resend confirmation instructions", class: 'btn btn-success' + = f.submit "Resend", class: 'btn btn-success' .clearfix.prepend-top-20 = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 56048e99c17..9c533ef9916 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,19 +1,21 @@ += render 'devise/shared/tab_single', { :tab_title => 'Change your password' } .login-box - .login-heading - %h3 Change your password .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! = f.hidden_field :reset_password_token - %div - = f.password_field :password, class: "form-control top", placeholder: "New password", required: true - %div - = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true + .form-group + = f.label 'New password', for: :password + = f.password_field :password, class: "form-control top", required: true, title: 'This field is required' + .form-group + = f.label 'Confirm new password', for: :password_confirmation + = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', required: true .clearfix = f.submit "Change your password", class: "btn btn-primary" .clearfix.prepend-top-20 %p - = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) - = render 'devise/shared/sign_in_link' + %span.light Didn't receive a confirmation email? + = link_to "Request a new one", new_confirmation_path(resource_name) += render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 535e85869e5..91b46a12ac0 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,12 +1,12 @@ += render 'devise/shared/tab_single', { :tab_title => 'Reset Password' } .login-box - .login-heading - %h3 Reset password .login-body - = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true + .form-group + = f.label :email + = f.email_field :email, class: "form-control", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.' .clearfix = f.submit "Reset password", class: "btn-primary btn" diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 781fd1b32a6..cfb1b964d76 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,6 +1,10 @@ -= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| - = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off" - = f.password_field :password, class: "form-control bottom", placeholder: "Password" += form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user show-gl-field-errors', 'aria-live' => 'assertive'}) do |f| + %div.form-group + = f.label "Username or email", for: :login + = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." + %div.form-group + = f.label :password + = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." .sign-in = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index b7d3acac2b1..5a192c63c7c 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,6 +1,10 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' class: 'show-gl-field-errors') do + .form-group + = label_tag 'Username or email', for: :username + = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } + .form-group + = label_tag :password + = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "remember_me"} diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 2ef383960f4..b26efbb4535 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,6 +1,10 @@ -= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} - = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} += form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "show-gl-field-errors") do + .form-group + = label_tag "#{server['label']} Login", for: :username + = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } + .form-group + = label_tag :password + = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "remember_me"} diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 28194506acc..2fb05b9456b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,19 +1,23 @@ - page_title "Sign in" %div - - if signin_enabled? || ldap_enabled? || crowd_enabled? - = render 'devise/shared/signin_box' + - if form_based_providers.any? + = render 'devise/shared/tabs_ldap' + - else + = render 'devise/shared/tabs_normal' + .tab-content + - if signin_enabled? || ldap_enabled? || crowd_enabled? + = render 'devise/shared/signin_box' - -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix.prepend-top-20 - = render 'devise/shared/omniauth_box' - - -# Signup only makes sense if you can also sign-in - - if signin_enabled? && signup_enabled? - .prepend-top-20 + -# Signup only makes sense if you can also sign-in + - if signin_enabled? && signup_enabled? = render 'devise/shared/signup_box' - -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - No authentication methods configured. + - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + .clearfix + = render 'devise/shared/omniauth_box' + + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + No authentication methods configured. + diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index e623f7cff88..56074c057d7 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,20 +3,19 @@ = page_specific_javascript_tag('u2f.js') %div + = render 'devise/shared/tab_single', { :tab_title => 'Two-Factor Authentication' } .login-box - .login-heading - %h3 Two-Factor Authentication .login-body - if @user.two_factor_otp_enabled? - %h5 Authenticate via Two-Factor App - = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| + = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user show-gl-field-errors' }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) - = f.text_field :otp_attempt, class: 'form-control', placeholder: 'Two-Factor Authentication code', required: true, autofocus: true, autocomplete: 'off' - %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. - .prepend-top-20 - = f.submit "Verify code", class: "btn btn-save" + .form-group + = f.label 'Two-Factor Authentication code', name: :otp_attempt + = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' + %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. + .prepend-top-20 + = f.submit "Verify code", class: "btn btn-save" - if @user.two_factor_u2f_enabled? - %hr = render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name } diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 2e7da2747d0..d5b6db48a29 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,8 +1,9 @@ -%p - %span.light - Sign in with   - - providers = enabled_button_based_providers - - providers.each do |provider| +%div.login-box + %p %span.light - - has_icon = provider_has_icon?(provider) - = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" + Sign in with   + - providers = enabled_button_based_providers + - providers.each do |provider| + %span.light + - has_icon = provider_has_icon?(provider) + = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" diff --git a/app/views/devise/shared/_sign_in_link.html.haml b/app/views/devise/shared/_sign_in_link.html.haml index fafc4b82f53..289bf40f3de 100644 --- a/app/views/devise/shared/_sign_in_link.html.haml +++ b/app/views/devise/shared/_sign_in_link.html.haml @@ -1,5 +1,4 @@ %p %span.light Already have login and password? - %strong = link_to "Sign in", new_session_path(resource_name) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 2c15e2c4891..810dd5ab687 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -1,32 +1,15 @@ -.login-box - - if signup_enabled? - .login-heading - %h3 Existing user? Sign in - - else - .login-heading - %h3 Sign in +#login-pane.login-box{ role: 'tabpanel', class: 'tab-pane active' } .login-body - if form_based_providers.any? - %ul.nav-links - - if crowd_enabled? - %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - elsif signin_enabled? = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 905a8dbcd84..c43a6aa3e49 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,28 +1,30 @@ -.login-box - - if signin_enabled? - .login-heading - %h3 New user? Create an account - - else - .login-heading - %h3 Create an account +#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } .login-body - = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name)) do |f| + = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user show-gl-field-errors", "aria-live" => "assertive" }) do |f| .devise-errors = devise_error_messages! - %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true - %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true - %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + %div.form-group + = f.label :name + = f.text_field :name, class: "form-control top", required: true, title: "This field is required." + %div.username.form-group + = f.label :username + = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true + %p.gl-field-error.hide Please create a username with only alphanumeric characters. + %p.validation-error.hide Username is already taken. + %p.validation-success.hide Username is available. + %p.validation-pending.hide Checking username availability... + %div.form-group + = f.label :email + = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address." .form-group.append-bottom-20#password-strength - = f.password_field :password, class: "form-control bottom", placeholder: "Password - minimum length #{@minimum_password_length} characters", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters" + = f.label :password + = f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters." + %p.gl-field-hint Minimum length is #{@minimum_password_length} characters %div - if current_application_settings.recaptcha_enabled = recaptcha_tags %div - = f.submit "Sign up", class: "btn-create btn" - + = f.submit "Register", class: "btn-register btn" .clearfix.prepend-top-20 %p %span.light Didn't receive a confirmation email? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml new file mode 100644 index 00000000000..8590c43d54d --- /dev/null +++ b/app/views/devise/shared/_tab_single.html.haml @@ -0,0 +1,4 @@ +// = render 'devise/shared/tab_single', :tab_title => 'Tab Title' +%ul.nav-links.nav-tabs.new-session-tabs.single-tab + %li.active + = link_to tab_title, '#', disabled: true diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml new file mode 100644 index 00000000000..e276e91433a --- /dev/null +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -0,0 +1,10 @@ +%ul.new-session-tabs.nav-links.nav-tabs + - if crowd_enabled? + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml new file mode 100644 index 00000000000..48abd6519d6 --- /dev/null +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -0,0 +1,5 @@ +%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} + %li.active{ role: 'presentation' } + %a{ href: '#login-pane', data: {'toggle':'tab'}, role: 'tab'} Sign in + %li{ role: 'presentation'} + %a{ href: '#register-pane', data: {'toggle':'tab'}, role: 'tab'} Register diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 49c087c0646..0036f3b98e5 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,12 +1,12 @@ += render 'devise/shared/tab_single', { :tab_title => 'Resend unlock instructions' } .login-box - .login-heading - %h3 Resend unlock email .login-body - = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| + = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| .devise-errors = devise_error_messages! - .clearfix.append-bottom-20 - = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off' + .form-group.append-bottom-20 + = f.label :email + = f.email_field :email, class: 'form-control', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', title: 'Please provide a valid email address.' .clearfix = f.submit 'Resend unlock instructions', class: 'btn btn-success' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index a9a384bd5f3..825e540cb0c 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,36 +1,37 @@ !!! 5 -%html{ lang: "en"} +%html{ lang: "en", class: "devise-layout-html"} = render "layouts/head" - %body.ui_charcoal.login-page.application.navless - = Gon::Base.render_data - = render "layouts/header/empty" - = render "layouts/broadcast" - .container.navless-container - .content - = render "layouts/flash" - .row - .col-sm-5.pull-right - = yield - .col-sm-7.brand-holder.pull-left - %h1 - = brand_title - - if brand_item - = brand_image - = brand_text - - else - %h3 Open source software to collaborate on code + %body{ class: "ui_charcoal login-page application navless", data: {page: body_data_page}} + .page-wrap + = Gon::Base.render_data + = render "layouts/header/empty" + = render "layouts/broadcast" + .container.navless-container + .content + = render "layouts/flash" + .row + .col-sm-5.pull-right.new-session-forms-container + = yield + .col-sm-7.brand-holder.pull-left + %h1 + = brand_title + - if brand_item + = brand_image + = brand_text + - else + %h3 Open source software to collaborate on code - %p - Manage git repositories with fine grained access controls that keep your code secure. - Perform code reviews and enhance collaboration with merge requests. - Each project can also have an issue tracker and a wiki. + %p + Manage git repositories with fine grained access controls that keep your code secure. + Perform code reviews and enhance collaboration with merge requests. + Each project can also have an issue tracker and a wiki. - if current_application_settings.sign_in_text.present? = markdown_field(current_application_settings, :sign_in_text) - %hr - .container - .footer-links - = link_to "Explore", explore_root_path - = link_to "Help", help_path - = link_to "About GitLab", "https://about.gitlab.com/" + %hr.footer-fixed + .container.footer-container + .footer-links + = link_to "Explore", explore_root_path + = link_to "Help", help_path + = link_to "About GitLab", "https://about.gitlab.com/" diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index 9657101ace5..232ca26c1af 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -6,7 +6,7 @@ %script#js-authenticate-u2f-setup{ type: "text/template" } %div %p Insert your security key (if you haven't already), and press the button below. - %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Login Via U2F Device + %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Sign in via U2F device %script#js-authenticate-u2f-in-progress{ type: "text/template" } %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..93d7f99fb90 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,6 +83,798 @@ Rails.application.routes.draw do draw :group draw :user draw :project + # + # Import + # + namespace :import do + resource :github, only: [:create, :new], controller: :github do + post :personal_access_token + get :status + get :callback + get :jobs + end + + resource :gitlab, only: [:create], controller: :gitlab do + get :status + get :callback + get :jobs + end + + resource :bitbucket, only: [:create], controller: :bitbucket do + get :status + get :callback + get :jobs + end + + resource :google_code, only: [:create, :new], controller: :google_code do + get :status + post :callback + get :jobs + + get :new_user_map, path: :user_map + post :create_user_map, path: :user_map + end + + resource :fogbugz, only: [:create, :new], controller: :fogbugz do + get :status + post :callback + get :jobs + + get :new_user_map, path: :user_map + post :create_user_map, path: :user_map + end + + resource :gitlab_project, only: [:create, :new] do + post :create + end + end + + # + # Uploads + # + + scope path: :uploads do + # Note attachments and User/Group/Project avatars + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + + # Appearance + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } + + # Project markdown uploads + get ":namespace_id/:project_id/:secret/:filename", + to: "projects/uploads#show", + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } + end + + # Redirect old note attachments path to new uploads path. + get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /[^\/]+/ } + + # + # Explore area + # + namespace :explore do + resources :projects, only: [:index] do + collection do + get :trending + get :starred + end + end + + resources :groups, only: [:index] + resources :snippets, only: [:index] + root to: 'projects#trending' + end + + # Compatibility with old routing + get 'public' => 'explore/projects#index' + get 'public/projects' => 'explore/projects#index' + + # + # Admin Area + # + namespace :admin do + resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :keys, only: [:show, :destroy] + resources :identities, except: [:show] + + member do + get :projects + get :keys + get :groups + put :block + put :unblock + put :unlock + put :confirm + post :impersonate + patch :disable_two_factor + delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' + end + end + + resource :impersonation, only: :destroy + + resources :abuse_reports, only: [:index, :destroy] + resources :spam_logs, only: [:index, :destroy] do + member do + post :mark_as_ham + end + end + + resources :applications + + resources :groups, constraints: { id: /[^\/]+/ } do + member do + put :members_update + end + end + + resources :deploy_keys, only: [:index, :new, :create, :destroy] + + resources :hooks, only: [:index, :create, :destroy] do + get :test + end + + resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do + post :preview, on: :collection + end + + resource :logs, only: [:show] + resource :health_check, controller: 'health_check', only: [:show] + resource :background_jobs, controller: 'background_jobs', only: [:show] + resource :system_info, controller: 'system_info', only: [:show] + resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } + + resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + root to: 'projects#index', as: :projects + + resources(:projects, + path: '/', + constraints: { id: /[a-zA-Z.0-9_\-]+/ }, + only: [:index, :show]) do + root to: 'projects#show' + + member do + put :transfer + post :repository_check + end + + resources :runner_projects, only: [:create, :destroy] + end + end + + resource :appearances, only: [:show, :create, :update], path: 'appearance' do + member do + get :preview + delete :logo + delete :header_logos + end + end + + resource :application_settings, only: [:show, :update] do + resources :services, only: [:index, :edit, :update] + put :reset_runners_token + put :reset_health_check_token + put :clear_repository_check_states + end + + resources :labels + + resources :runners, only: [:index, :show, :update, :destroy] do + member do + get :resume + get :pause + end + end + + resources :builds, only: :index do + collection do + post :cancel_all + end + end + + root to: 'dashboard#index' + end + + # + # Profile Area + # + resource :profile, only: [:show, :update] do + member do + get :audit_log + get :applications, to: 'oauth/applications#index' + + put :reset_private_token + put :update_username + end + + scope module: :profiles do + resource :account, only: [:show] do + member do + delete :unlink + end + end + resource :notifications, only: [:show, :update] + resource :password, only: [:new, :create, :edit, :update] do + member do + put :reset + end + end + resource :preferences, only: [:show, :update] + resources :keys, only: [:index, :show, :new, :create, :destroy] + resources :emails, only: [:index, :create, :destroy] + resource :avatar, only: [:destroy] + + resources :personal_access_tokens, only: [:index, :create] do + member do + put :revoke + end + end + + resource :two_factor_auth, only: [:show, :create, :destroy] do + member do + post :create_u2f + post :codes + patch :skip + end + end + + resources :u2f_registrations, only: [:destroy] + end + end + + scope(path: 'u/:username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error + get '/users/almost_there' => 'confirmations#almost_there' + end + + root to: "root#index" + + # + # Project Area + # + resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template + + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + put( + '/blob/*id', + to: 'blob#update', + constraints: { id: /.+/, format: false } + ) + post( + '/blob/*id', + to: 'blob#create', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + + scope do + get( + '/find_file/*id', + to: 'find_file#show', + constraints: { id: /.+/, format: /html/ }, + as: :find_file + ) + end + + scope do + get( + '/files/*id', + to: 'find_file#list', + constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, + as: :files + ) + end + + scope do + post( + '/create_dir/*id', + to: 'tree#create_dir', + constraints: { id: /.+/ }, + as: 'create_dir' + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + as: :commits + ) + end + + resource :avatar, only: [:show, :destroy] + resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do + member do + get :branches + get :builds + get :pipelines + post :cancel_builds + post :retry_builds + post :revert + post :cherry_pick + get :diff_for_path + end + end + + resources :compare, only: [:index, :create] do + collection do + get :diff_for_path + end + end + + get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } + + # Don't use format parameter as file extension (old 3.0.x behavior) + # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments + scope format: false do + resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } + + resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do + member do + get :commits + get :ci + get :languages + end + end + end + + resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get 'raw' + end + end + + WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID + + scope do + # Order matters to give priority to these matches + get '/wikis/git_access', to: 'wikis#git_access' + get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' + post '/wikis', to: 'wikis#create' + + get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID + get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID + + get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID + delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID + put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID + post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' + end + + resource :repository, only: [:create] do + member do + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } + end + end + + resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do + member do + get :test + end + end + + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do + member do + put :enable + put :disable + end + end + + resources :forks, only: [:index, :new, :create] + resource :import, only: [:new, :create, :show] + + resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + # Directories with leading dots erroneously get rejected if git + # ref regex used in constraints. Regex verification now done in controller. + get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { + id: /.*/, + path: /.*/ + } + end + end + + resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do + member do + get :commits + get :diffs + get :conflicts + get :builds + get :pipelines + get :merge_check + post :merge + post :cancel_merge_when_build_succeeds + get :ci_status + post :toggle_subscription + post :remove_wip + get :diff_for_path + post :resolve_conflicts + end + + collection do + get :branch_from + get :branch_to + get :update_branches + get :diff_for_path + post :bulk_update + end + + resources :discussions, only: [], constraints: { id: /\h{40}/ } do + member do + post :resolve + delete :resolve, action: :unresolve + end + end + end + + resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do + resource :release, only: [:edit, :update] + end + + resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :variables, only: [:index, :show, :update, :create, :destroy] + resources :triggers, only: [:index, :create, :destroy] + + resources :pipelines, only: [:index, :new, :create, :show] do + collection do + resource :pipelines_settings, path: 'settings', only: [:show, :update] + end + + member do + post :cancel + post :retry + end + end + + resources :environments + + resource :cycle_analytics, only: [:show] + + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do + collection do + post :cancel_all + + resources :artifacts, only: [] do + collection do + get :latest_succeeded, + path: '*ref_name_and_path', + format: false + end + end + end + + member do + get :status + post :cancel + post :retry + post :play + post :erase + get :trace + get :raw + end + + resource :artifacts, only: [] do + get :download + get :browse, path: 'browse(/*path)', format: false + get :file, path: 'file/*path', format: false + post :keep + end + end + + resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do + member do + get :test + end + end + + resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } + + resources :milestones, constraints: { id: /\d+/ } do + member do + put :sort_issues + put :sort_merge_requests + end + end + + resources :labels, except: [:show], constraints: { id: /\d+/ } do + collection do + post :generate + post :set_priorities + end + + member do + post :toggle_subscription + delete :remove_priority + end + end + + resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do + member do + post :toggle_subscription + post :mark_as_spam + get :referenced_merge_requests + get :related_branches + get :can_create_branch + end + collection do + post :bulk_update + end + end + + resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do + collection do + delete :leave + + # Used for import team + # from another project + get :import + post :apply_import + end + + member do + post :resend_invite + end + end + + resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } + + resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do + member do + delete :delete_attachment + post :resolve + delete :resolve, action: :unresolve + end + end + + resource :board, only: [:show] do + scope module: :boards do + resources :issues, only: [:update] + + resources :lists, only: [:index, :create, :update, :destroy] do + collection do + post :generate + end + + resources :issues, only: [:index] + end + end + end + + resources :todos, only: [:create] + + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + end + end + + resources :runners, only: [:index, :edit, :update, :destroy, :show] do + member do + get :resume + get :pause + end + + collection do + post :toggle_shared_runners + end + end + + resources :runner_projects, only: [:create, :destroy] + resources :badges, only: [:index] do + collection do + scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do + constraints format: /svg/ do + get :build + get :coverage + end + end + end + end + end + end + end # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index a752c1d7235..65544f79eba 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -14,7 +14,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq users_almost_there_path expect(page).to have_content("Please check your email to confirm your account") @@ -33,7 +33,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq dashboard_projects_path expect(page).to have_content("Welcome! You have signed up successfully.") @@ -52,7 +52,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: existing_user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq user_registration_path expect(page).to have_content("error prohibited this user from being saved") @@ -69,7 +69,7 @@ feature 'Signup', feature: true do fill_in 'new_user_username', with: user.username fill_in 'new_user_email', with: existing_user.email fill_in 'new_user_password', with: user.password - click_button "Sign up" + click_button "Register" expect(current_path).to eq user_registration_path expect(page.body).not_to match(/#{user.password}/) diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index ff6933dc8d9..b750f27ea72 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -160,7 +160,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -174,7 +174,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -186,7 +186,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user, remember: true) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') within 'div#js-authenticate-u2f' do @@ -209,7 +209,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the old U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -230,7 +230,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the same U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -244,7 +244,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: unregistered_device = FakeU2fDevice.new(page, FFaker::Name.first_name) login_as(user) unregistered_device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" @@ -271,7 +271,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: [first_device, second_device].each do |device| login_as(user) device.respond_to_u2f_authentication - click_on "Login Via U2F Device" + click_on "Sign in via U2F device" expect(page.body).to match('We heard back from your U2F device') click_on "Authenticate via U2F Device" diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 6498b7317b4..63743169302 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,15 +1,16 @@ require 'spec_helper' -feature 'Users', feature: true do +feature 'Users', feature: true, js: true do let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') } scenario 'GET /users/sign_in creates a new user account' do visit new_user_session_path + click_link 'Register' fill_in 'new_user_name', with: 'Name Surname' fill_in 'new_user_username', with: 'Great' fill_in 'new_user_email', with: 'name@mail.com' fill_in 'new_user_password', with: 'password1234' - expect { click_button 'Sign up' }.to change { User.count }.by(1) + expect { click_button 'Register' }.to change { User.count }.by(1) end scenario 'Successful user signin invalidates password reset token' do @@ -31,11 +32,12 @@ feature 'Users', feature: true do scenario 'Should show one error if email is already taken' do visit new_user_session_path + click_link 'Register' fill_in 'new_user_name', with: 'Another user name' fill_in 'new_user_username', with: 'anotheruser' fill_in 'new_user_email', with: user.email fill_in 'new_user_password', with: '12341234' - expect { click_button 'Sign up' }.to change { User.count }.by(0) + expect { click_button 'Register' }.to change { User.count }.by(0) expect(page).to have_text('Email has already been taken') expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' end @@ -51,6 +53,30 @@ feature 'Users', feature: true do end end + feature 'username validation' do + include WaitForAjax + let(:loading_icon) { '.fa.fa-spinner' } + let(:username_input) { 'new_user_username' } + + before(:each) do + visit new_user_session_path + click_link 'Register' + @username_field = find '.username' + end + + scenario 'shows an error border if the username already exists' do + fill_in username_input, with: user.username + wait_for_ajax + expect(@username_field).to have_css '.gl-field-error-outline' + end + + scenario 'doesn\'t show an error border if the username is available' do + fill_in username_input, with: 'new-user' + wait_for_ajax + expect(@username_field).not_to have_css '.gl-field-error-outline' + end + end + def errors_on_page(page) page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n") end diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index 7ce3884f844..784b43d4846 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -21,7 +21,7 @@ setupButton = this.container.find("#js-login-u2f-device"); setupMessage = this.container.find("p"); expect(setupMessage.text()).toContain('Insert your security key'); - expect(setupButton.text()).toBe('Login Via U2F Device'); + expect(setupButton.text()).toBe('Sign in via U2F device'); setupButton.trigger('click'); inProgressMessage = this.container.find("p"); expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); -- cgit v1.2.1 From d2bad46efee65688ab56d9d7c850775f1339f491 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 06:47:53 +0200 Subject: Add padding to fixed footer, to more quickly support scrolling. --- app/assets/stylesheets/pages/login.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 06b90fbefab..9cc1bf90122 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -250,4 +250,8 @@ height: 40px; background: $white-light; } + + .navless-container { + padding: 65px; // height of footer + bottom padding of email confirmation link + } } -- cgit v1.2.1 From 768cd071afa41acc5b9c91958ac280e1d6870dae Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 07:25:47 +0200 Subject: Clean up username_validator private vars and members. --- app/assets/javascripts/username_validator.js.es6 | 91 ++++++++++++------------ 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index f8be356af04..a22f598b753 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -1,34 +1,34 @@ ((global) => { const debounceTimeoutDuration = 1000; - const inputErrorClass = 'gl-field-error-outline'; - const inputSuccessClass = 'gl-field-success-outline'; - const messageErrorSelector = '.username .validation-error'; - const messageSuccessSelector = '.username .validation-success'; - const messagePendingSelector = '.username .validation-pending'; + const invalidInputClass = 'gl-field-error-outline'; + const successInputClass = 'gl-field-success-outline'; + const unavailableMessageSelector = '.username .validation-error'; + const successMessageSelector = '.username .validation-success'; + const pendingMessageSelector = '.username .validation-pending'; + const invalidMessageSelector = '.username .gl-field-error'; class UsernameValidator { constructor() { this.inputElement = $('#new_user_username'); this.inputDomElement = this.inputElement.get(0); - - this.available = false; - this.valid = false; - this.pending = false; - this.fresh = true; - this.empty = true; + this.state = { + available: false, + valid: false, + pending: false, + empty: true + }; const debounceTimeout = _.debounce((username) => { - this.validateUsername(username); + this.state.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.on('keyup.username_check', () => { const username = this.inputElement.val(); - this.valid = this.inputDomElement.validity.valid; - this.fresh = false; - this.empty = !username.length; + this.state.valid = this.inputDomElement.validity.valid; + this.state.empty = !username.length; - if (this.valid) { + if (this.state.valid) { return debounceTimeout(username); } @@ -36,43 +36,43 @@ }); // Override generic field validation - this.inputElement.on('invalid', this.handleInvalidInput.bind(this)); + this.inputElement.on('invalid', this.interceptInvalid.bind(this)); } renderState() { // Clear all state this.clearFieldValidationState(); - if (this.valid && this.available) { + if (this.state.valid && this.state.available) { return this.setSuccessState(); } - if (this.empty) { + if (this.state.empty) { return this.clearFieldValidationState(); } - if (this.pending) { + if (this.state.pending) { return this.setPendingState(); } - if (!this.available) { + if (!this.state.available) { return this.setUnavailableState(); } - if (!this.valid) { + if (!this.state.valid) { return this.setInvalidState(); } } - handleInvalidInput(event) { + interceptInvalid(event) { event.preventDefault(); event.stopPropagation(); } validateUsername(username) { - if (this.valid) { - this.pending = true; - this.available = false; + if (this.state.valid) { + this.state.pending = true; + this.state.available = false; this.renderState(); return $.ajax({ type: 'GET', @@ -83,38 +83,40 @@ } } - updateValidationState(usernameTaken) { + setAvailabilityState(usernameTaken) { if (usernameTaken) { - this.valid = false; - this.available = false; + this.state.valid = false; + this.state.available = false; } else { - this.available = true; + this.state.available = true; } - this.pending = false; + this.state.pending = false; this.renderState(); } clearFieldValidationState() { - this.inputElement.siblings('p').hide(); - this.inputElement.removeClass(inputErrorClass); - this.inputElement.removeClass(inputSuccessClass); + // TODO: Double check if this is valid chaining + const $input = this.inputElement + .siblings('p').hide().end() + .removeClass(invalidInputClass); + removeClass(successInputClass); } setUnavailableState() { - const $usernameErrorMessage = this.inputElement.siblings(messageErrorSelector); - this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); - $usernameErrorMessage.show(); + const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $usernameUnavailableMessage.show(); } setSuccessState() { - const $usernameSuccessMessage = this.inputElement.siblings(messageSuccessSelector); - this.inputElement.addClass(inputSuccessClass).removeClass(inputErrorClass); + const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); + this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); $usernameSuccessMessage.show(); } - setPendingState(show) { - const $usernamePendingMessage = $(messagePendingSelector); - if (this.pending) { + setPendingState() { + const $usernamePendingMessage = $(pendingMessageSelector); + if (this.state.pending) { $usernamePendingMessage.show(); } else { $usernamePendingMessage.hide(); @@ -122,8 +124,9 @@ } setInvalidState() { - this.inputElement.addClass(inputErrorClass).removeClass(inputSuccessClass); - $(`.gl-field-error`).show(); + const $inputErrorMessage = $(invalidMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $inputErrorMessage.show(); } } -- cgit v1.2.1 From a449b9b8a171c659c7b3ce37f685624e3e079192 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 08:31:04 +0200 Subject: Refactor gl field errors for simpler state management. --- app/assets/javascripts/gl_field_errors.js.es6 | 134 +++++++++++++++++--------- 1 file changed, 89 insertions(+), 45 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 42a2ddeeafe..f4c09dd407d 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -13,79 +13,123 @@ * * */ - const fieldErrorClass = 'gl-field-error'; - const fieldErrorSelector = `.${fieldErrorClass}`; + const errorMessageClass = 'gl-field-error'; const inputErrorClass = 'gl-field-error-outline'; - class GlFieldErrors { - constructor(form) { - this.form = $(form); - this.initValidators(); + class GlFieldError { + constructor({ input, form }) { + this.inputElement = $(input); + this.inputDomElement = this.inputElement.get(0); + this.form = form; + this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; + this.fieldErrorElement = $(`

    ${ this.errorMessage }

    `); + + this.state = { + valid: false, + empty: true + }; + + this.initFieldValidation(); } - initValidators () { - this.inputs = this.form.find(':input:not([type=hidden])').toArray(); - this.inputs.forEach((input) => { - $(input).off('invalid').on('invalid', this.handleInvalidInput.bind(this)); - }); - this.form.on('submit', this.catchInvalidFormSubmit); + initFieldValidation() { + // hidden when injected into DOM + $input.after(this.fieldErrorElement); + this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); } - /* Neccessary because Safari & iOS quietly allow form submission when form is invalid */ - catchInvalidFormSubmit (event) { - if (!event.currentTarget.checkValidity()) { - event.preventDefault(); - // Prevents disabling of invalid submit button by application.js - event.stopPropagation(); + renderValidity() { + this.setClearState(); + + if (this.state.valid) { + this.setValidState(); + } + + if (this.state.empty) { + this.setEmptyState(); + } + + if (!this.state.valid) { + this.setInvalidState(); } + + this.form.focusOnFirstInvalid.apply(this); } - handleInvalidInput (event) { + handleInvalidInput(event) { event.preventDefault(); - this.updateFieldValidityState(event); - const $input = $(event.currentTarget); + this.state.valid = true; + this.state.empty = false; + + this.renderValidity(); // For UX, wait til after first invalid submission to check each keyup - $input.off('keyup.field_validator') - .on('keyup.field_validator', this.updateFieldValidityState.bind(this)); + this.inputElement.off('keyup.field_validator') + .on('keyup.field_validator', this.updateValidityState.bind(this)); } - displayFieldValidity (target, isValid) { - const $input = $(target).removeClass(inputErrorClass); - const $existingError = $input.siblings(fieldErrorSelector); - const alreadyInvalid = !!$existingError.length; - const implicitErrorMessage = $input.attr('title'); - const $errorToDisplay = alreadyInvalid ? $existingError.detach() : $(`

    ${implicitErrorMessage}

    `); + getInputValidity() { + return this.inputDomElement.validity.valid; + } - if (!isValid) { - $input.after($errorToDisplay); - $input.addClass(inputErrorClass); - } + updateValidityState() { + const inputVal = this.inputElement.val(); + this.state.empty = !!inputVal.length; + this.state.valid = this.getInputValidity; - this.updateFieldSiblings($errorToDisplay, isValid); + this.renderValidity(); } - updateFieldSiblings($target, isValid) { - const siblings = $target.siblings(`p${fieldErrorSelector}`); - return isValid ? siblings.show() : siblings.hide(); + setValidState() { + return this.setClearState(); + } + + setEmptyState() { + return this.setClearState(); + } + + setInvalidState() { + $input.addClass(inputErrorClass); + return this.$fieldErrorElement.show(); + } + + setClearState() { + $input.removeClass(inputErrorClass); + return this.fieldErrorElement.hide(); } checkFieldValidity(target) { return target.validity.valid; } + } - updateFieldValidityState(event) { - const target = event.currentTarget; - const isKeyup = event.type === 'keyup'; - const isValid = this.checkFieldValidity(target); + class GlFieldErrors { + constructor(form) { + this.form = $(form); + this.initValidators(); + } - this.displayFieldValidity(target, isValid); + initValidators () { + // select all non-hidden inputs in form + const form = this.form; + + this.inputs = this.form.find(':input:not([type=hidden])') + .toArray() + .map((input) => new GlFieldError({ input, form })); + + this.form.on('submit', this.catchInvalidFormSubmit); + } - // prevent changing focus while user is typing. - if (!isKeyup) { - this.focusOnFirstInvalid.apply(this); + /* Neccessary to prevent intercept and override invalid form submit + * because Safari & iOS quietly allow form submission when form is invalid + * and prevents disabling of invalid submit button by application.js */ + + catchInvalidFormSubmit (event) { + if (!event.currentTarget.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); } } -- cgit v1.2.1 From 00bfb645e16b24ce929211bf5080aa3083f8543b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 10:29:21 +0200 Subject: Fix errors, get validation running for signup box and sign in. --- app/assets/javascripts/gl_field_errors.js.es6 | 52 ++++++++++++++---------- app/assets/javascripts/username_validator.js.es6 | 12 +++--- app/views/devise/shared/_signup_box.html.haml | 2 +- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index f4c09dd407d..e1de7f78efc 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -17,10 +17,10 @@ const inputErrorClass = 'gl-field-error-outline'; class GlFieldError { - constructor({ input, form }) { + constructor({ input, formErrors }) { this.inputElement = $(input); this.inputDomElement = this.inputElement.get(0); - this.form = form; + this.form = formErrors; this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; this.fieldErrorElement = $(`

    ${ this.errorMessage }

    `); @@ -34,7 +34,7 @@ initFieldValidation() { // hidden when injected into DOM - $input.after(this.fieldErrorElement); + this.inputElement.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); } @@ -42,24 +42,24 @@ this.setClearState(); if (this.state.valid) { - this.setValidState(); + return this.setValidState(); } if (this.state.empty) { - this.setEmptyState(); + return this.setEmptyState(); } if (!this.state.valid) { - this.setInvalidState(); + return this.setInvalidState(); } - this.form.focusOnFirstInvalid.apply(this); + this.form.focusOnFirstInvalid.apply(this.form); } handleInvalidInput(event) { event.preventDefault(); - this.state.valid = true; + this.state.valid = false; this.state.empty = false; this.renderValidity(); @@ -77,8 +77,7 @@ updateValidityState() { const inputVal = this.inputElement.val(); this.state.empty = !!inputVal.length; - this.state.valid = this.getInputValidity; - + this.state.valid = this.getInputValidity(); this.renderValidity(); } @@ -87,17 +86,24 @@ } setEmptyState() { - return this.setClearState(); + return this.setInvalidState(); } setInvalidState() { - $input.addClass(inputErrorClass); - return this.$fieldErrorElement.show(); + this.inputElement.addClass(inputErrorClass); + this.inputElement.siblings('p').hide(); + return this.fieldErrorElement.show(); } setClearState() { - $input.removeClass(inputErrorClass); - return this.fieldErrorElement.hide(); + const inputVal = this.inputElement.val(); + if (!inputVal.split(' ').length) { + const trimmedInput = this.inputElement.val().trim(); + this.inputElement.val(trimmedInput); + } + this.inputElement.removeClass(inputErrorClass); + this.inputElement.siblings('p').hide(); + this.fieldErrorElement.hide(); } checkFieldValidity(target) { @@ -105,19 +111,23 @@ } } + const customValidationFlag = 'no-gl-field-errors'; + class GlFieldErrors { constructor(form) { this.form = $(form); + this.state = { + inputs: [], + valid: false + }; this.initValidators(); } initValidators () { // select all non-hidden inputs in form - const form = this.form; - - this.inputs = this.form.find(':input:not([type=hidden])') - .toArray() - .map((input) => new GlFieldError({ input, form })); + this.state.inputs = this.form.find(':input:not([type=hidden])').toArray() + .filter((input) => !input.classList.contains(customValidationFlag)) + .map((input) => new GlFieldError({ input, formErrors: this })); this.form.on('submit', this.catchInvalidFormSubmit); } @@ -134,7 +144,7 @@ } focusOnFirstInvalid () { - const firstInvalid = this.inputs.find((input) => !input.validity.valid); + const firstInvalid = this.state.inputs.find((input) => !input.inputDomElement.validity.valid); $(firstInvalid).focus(); } } diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index a22f598b753..b19fb9b4771 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -19,7 +19,7 @@ }; const debounceTimeout = _.debounce((username) => { - this.state.validateUsername(username); + this.validateUsername(username); }, debounceTimeoutDuration); this.inputElement.on('keyup.username_check', () => { @@ -78,7 +78,7 @@ type: 'GET', url: `/u/${username}/exists`, dataType: 'json', - success: (res) => this.updateValidationState(res.exists) + success: (res) => this.setAvailabilityState(res.exists) }); } } @@ -96,10 +96,10 @@ clearFieldValidationState() { // TODO: Double check if this is valid chaining - const $input = this.inputElement - .siblings('p').hide().end() - .removeClass(invalidInputClass); - removeClass(successInputClass); + this.inputElement.siblings('p').hide(); + + this.inputElement.removeClass(invalidInputClass) + .removeClass(successInputClass); } setUnavailableState() { diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index c43a6aa3e49..7382042cc50 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,7 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle", pattern: "[a-zA-Z0-9]+", required: true + = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true %p.gl-field-error.hide Please create a username with only alphanumeric characters. %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. -- cgit v1.2.1 From 83fb1190205c3d1fbe16a684794ed1af10ed1bff Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:15:07 +0200 Subject: Get tests passing. --- spec/features/users_spec.rb | 9 +++++---- spec/support/login_helpers.rb | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 63743169302..73d6fb6b651 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -61,16 +61,17 @@ feature 'Users', feature: true, js: true do before(:each) do visit new_user_session_path click_link 'Register' - @username_field = find '.username' + @username_form_group = find '.username' + @username_field = find '#new_user_username' end - scenario 'shows an error border if the username already exists' do + scenario 'shows an error border if the username already exists', focus: true do fill_in username_input, with: user.username wait_for_ajax - expect(@username_field).to have_css '.gl-field-error-outline' + expect(@username_form_group).to have_css '.gl-field-error-outline' end - scenario 'doesn\'t show an error border if the username is available' do + scenario 'doesn\'t show an error border if the username is available', focus: true do fill_in username_input, with: 'new-user' wait_for_ajax expect(@username_field).not_to have_css '.gl-field-error-outline' diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index c0b3e83244d..3e90c95918c 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -33,7 +33,9 @@ module LoginHelpers fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember - click_button "Sign in" + page.within '.login-box' do + click_button "Sign in" + end Thread.current[:current_user] = user end -- cgit v1.2.1 From 74bfba72263ae28a1a9a9fd53984e5d11a125fa7 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 11:25:30 +0200 Subject: Include correct validation error with username invalid. --- app/views/devise/shared/_signup_box.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 7382042cc50..fb7ee2dbd3b 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,8 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." %div.username.form-group = f.label :username - = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true - %p.gl-field-error.hide Please create a username with only alphanumeric characters. + = f.text_field :username, class: "form-control middle no-gl-field-error", pattern: "[a-zA-Z0-9]+", required: true, title: 'Please create a username with only alphanumeric characters.' %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. %p.validation-pending.hide Checking username availability... -- cgit v1.2.1 From bd0bf40763ed7acd43d24413f4b4a421f0076242 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 13:30:26 +0200 Subject: Make style fixes, make all submit buttons full-width btn-block. --- app/assets/stylesheets/pages/login.scss | 5 +++++ app/views/devise/sessions/_new_base.html.haml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 9cc1bf90122..f680fd68d70 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -198,6 +198,11 @@ font-weight: normal; } + input[type="submit"] { + @extend .btn-block; + margin-bottom: 0px; + } + .devise-errors { h2 { margin-top: 0; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index cfb1b964d76..d0cd418236a 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -5,8 +5,6 @@ %div.form-group = f.label :password = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." - .sign-in - = f.submit "Sign in", class: "btn btn-save" - if devise_mapping.rememberable? .remember-me.checkbox %label{for: "user_remember_me"} @@ -14,3 +12,5 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) + %div.prepend-top-20 + = f.submit "Sign in", class: "btn btn-save" -- cgit v1.2.1 From 2c7a1af66f45e475b5aa7b8b91e7f8c85134a762 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 23 Sep 2016 13:38:39 +0200 Subject: Add submit button contain with custom margin. --- app/assets/stylesheets/pages/login.scss | 4 ++++ app/views/devise/sessions/_new_base.html.haml | 2 +- app/views/devise/shared/_signup_box.html.haml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f680fd68d70..b235eb845b0 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -198,6 +198,10 @@ font-weight: normal; } + .submit-container { + margin-top: 16px; + } + input[type="submit"] { @extend .btn-block; margin-bottom: 0px; diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index d0cd418236a..a96b579c593 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -12,5 +12,5 @@ %span Remember me .pull-right = link_to "Forgot your password?", new_password_path(resource_name) - %div.prepend-top-20 + %div.submit-container = f.submit "Sign in", class: "btn btn-save" diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index fb7ee2dbd3b..d0bbcf3115e 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -24,7 +24,7 @@ = recaptcha_tags %div = f.submit "Register", class: "btn-register btn" -.clearfix.prepend-top-20 +.clearfix.submit-container %p %span.light Didn't receive a confirmation email? = succeed '.' do -- cgit v1.2.1 From 49688d399a3b3c7d3ec50ae0bb728ff0d25671b0 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 14:06:18 +0200 Subject: Add third box shadow to validation focus, for depth. --- app/assets/stylesheets/pages/login.scss | 90 ++++++++++++++------------------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index b235eb845b0..f1d15417705 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -40,7 +40,7 @@ p { font-size: 13px; } - .login-box { + .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; @@ -79,69 +79,57 @@ background: transparent; } - .form-control { - font-size: 14px; - padding: 10px 8px; - width: 100%; - height: auto; - - &.top { - border-radius: 5px 5px 0 0; - margin-bottom: 0; - } + // Styles the glowing border of focused input for username async validation + .login-body { + font-size: 13px; - &.bottom { - border-radius: 0 0 5px 5px; - border-top: 0; - margin-bottom: 20px; - } - } - // Styles the glowing border of focused input for username async validation - .login-body { - font-size: 13px; + input + p { + margin-top: 5px; + } + .gl-field-success-outline { + border: 1px solid $green-normal; - input + p { - margin-top: 5px; - } + &:focus { + box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; + border: 0 none; + } + } - .gl-field-success-outline { - border: 1px solid $green-normal; + .gl-field-error-outline { + border: 1px solid $red-normal; - &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 0 4px 0 $green-normal; - border: 0 none; + &:focus { + box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); + border: 0 none; + } } - } - .gl-field-error-outline { - border: 1px solid $red-normal; + .username .validation-success, + .gl-field-success-message { + color: $green-normal; + } - &:focus { - opacity: .6; - box-shadow: 0 0 0 1px $red-normal inset, 0 0 4px 0 $red-normal; - border: 0 none; + .username .validation-error, + .gl-field-error-message { + color: $red-normal; } - } - .username .validation-success, - .gl-field-success-message { - color: $green-normal; - } + .gl-field-hint { + color: $gl-text-color; + } - .username .validation-error, - .gl-field-error-message { - color: $red-normal; } + } - .gl-field-hint { - color: $gl-text-color; + .omniauth-container { + p { + margin: 0; } - } - - .new-session-tabs { // Are these being applied to other login-related screens? They need to be. + .new-session-tabs { + display: -webkit-flex; display: flex; box-shadow: 0 0 0 1px $border-color; border-top-right-radius: 2px; @@ -151,10 +139,6 @@ flex: 1; text-align: center; - &.middle { - border-top: 0; - margin-bottom: 0; - border-radius: 0; &:last-of-type { border-left: 1px solid $border-color; } @@ -204,7 +188,7 @@ input[type="submit"] { @extend .btn-block; - margin-bottom: 0px; + margin-bottom: 0; } .devise-errors { -- cgit v1.2.1 From 076749645a51c4fe948cdf5ed16dc8455a9aae05 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 14:54:15 +0200 Subject: Shush rubocop. --- spec/features/users_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 73d6fb6b651..17a555e4673 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -65,13 +65,13 @@ feature 'Users', feature: true, js: true do @username_field = find '#new_user_username' end - scenario 'shows an error border if the username already exists', focus: true do + scenario 'shows an error border if the username already exists' do fill_in username_input, with: user.username wait_for_ajax expect(@username_form_group).to have_css '.gl-field-error-outline' end - scenario 'doesn\'t show an error border if the username is available', focus: true do + scenario 'doesn\'t show an error border if the username is available' do fill_in username_input, with: 'new-user' wait_for_ajax expect(@username_field).not_to have_css '.gl-field-error-outline' -- cgit v1.2.1 From 80cbc9838eae8836a9bf85bac1dca7e965d1d77d Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 27 Sep 2016 17:07:51 +0200 Subject: Fix omniauth box styling. --- app/views/devise/sessions/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 2fb05b9456b..dbdbc9689aa 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -13,7 +13,7 @@ = render 'devise/shared/signup_box' - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix + .clearfix.omniauth-box = render 'devise/shared/omniauth_box' -# Show a message if none of the mechanisms above are enabled -- cgit v1.2.1 From 503dcacaa42bc5870a87b579009c53c991b03c4e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 12:59:58 +0200 Subject: Properly implement focus on first invalid. --- app/assets/javascripts/gl_field_errors.js.es6 | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index e1de7f78efc..91c25047f7b 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -53,17 +53,16 @@ return this.setInvalidState(); } - this.form.focusOnFirstInvalid.apply(this.form); } handleInvalidInput(event) { event.preventDefault(); - + const currentValue = this.inputElement.val(); this.state.valid = false; - this.state.empty = false; + this.state.empty = currentValue === ''; this.renderValidity(); - + this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup this.inputElement.off('keyup.field_validator') .on('keyup.field_validator', this.updateValidityState.bind(this)); @@ -76,7 +75,7 @@ updateValidityState() { const inputVal = this.inputElement.val(); - this.state.empty = !!inputVal.length; + this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); } @@ -105,10 +104,6 @@ this.inputElement.siblings('p').hide(); this.fieldErrorElement.hide(); } - - checkFieldValidity(target) { - return target.validity.valid; - } } const customValidationFlag = 'no-gl-field-errors'; @@ -144,8 +139,8 @@ } focusOnFirstInvalid () { - const firstInvalid = this.state.inputs.find((input) => !input.inputDomElement.validity.valid); - $(firstInvalid).focus(); + const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0]; + firstInvalid.inputElement.focus(); } } -- cgit v1.2.1 From 69f9c00cfb7d80959a523a10ef7d5f53a37b21e5 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 13:01:03 +0200 Subject: Add tests for gl_field_errors. --- .../javascripts/fixtures/gl_field_errors.html.haml | 15 +++ spec/javascripts/gl_field_errors_spec.js.es6 | 111 +++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 spec/javascripts/fixtures/gl_field_errors.html.haml create mode 100644 spec/javascripts/gl_field_errors_spec.js.es6 diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml new file mode 100644 index 00000000000..2526e5e33a5 --- /dev/null +++ b/spec/javascripts/fixtures/gl_field_errors.html.haml @@ -0,0 +1,15 @@ +%form.show-gl-field-errors{action: 'submit', method: 'post'} + .form-group + %input.required-text{required: true, type: 'text'} Text + .form-group + %input.email{type: 'email', title: 'Please provide a valid email address.', required: true } Email + .form-group + %input.password{type: 'password', required: true} Password + .form-group + %input.alphanumeric{type: 'text', pattern: '[a-zA-Z0-9]', required: true} Alphanumeric + .form-group + %input.hidden{ type:'hidden' } + .form-group + %input.custom.no-gl-field-errors{ type:'text' } Custom, do not validate + .form-group + %input.submit{type: 'submit'} Submit diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 new file mode 100644 index 00000000000..36feb2b2aa5 --- /dev/null +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -0,0 +1,111 @@ +//= require jquery +//= require gl_field_errors + +((global) => { + fixture.preload('gl_field_errors.html'); + + describe('GL Style Field Errors', function() { + beforeEach(function() { + fixture.load('gl_field_errors.html'); + const $form = this.$form = $('form.show-gl-field-errors'); + this.fieldErrors = new global.GlFieldErrors($form); + }); + + it('should properly initialize the form', function() { + expect(this.$form).toBeDefined(); + expect(this.$form.length).toBe(1); + expect(this.fieldErrors).toBeDefined(); + const inputs = this.fieldErrors.state.inputs; + expect(inputs.length).toBe(5); + }); + + it('should ignore elements with custom error handling', function() { + const customErrorFlag = 'no-gl-field-errors'; + const customErrorElem = $(`.${customErrorFlag}`); + + expect(customErrorElem.length).toBe(1); + + const customErrors = this.fieldErrors.state.inputs.filter((input) => { + return input.inputElement.hasClass(customErrorFlag); + }); + expect(customErrors.length).toBe(0); + }); + + it('should not show any errors before submit attempt', function() { + this.$form.find('.email').val('not-a-valid-email').keyup(); + this.$form.find('.text-required').val('').keyup(); + this.$form.find('.alphanumberic').val('?---*').keyup(); + + const errorsShown = this.$form.find('.gl-field-error-outline'); + expect(errorsShown.length).toBe(0); + }); + + it('should show errors when input valid is submitted', function() { + this.$form.find('.email').val('not-a-valid-email').keyup(); + this.$form.find('.text-required').val('').keyup(); + this.$form.find('.alphanumberic').val('?---*').keyup(); + + this.$form.submit(); + + const errorsShown = this.$form.find('.gl-field-error-outline'); + expect(errorsShown.length).toBe(4); + }); + + it('should properly track validity state on input after invalid submission attempt', function() { + this.$form.submit(); + + const emailInputModel = this.fieldErrors.state.inputs[1]; + const fieldState = emailInputModel.state; + const emailInputElement = emailInputModel.inputElement; + + // No input + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(true); + expect(fieldState.valid).toBe(false); + + // Then invalid input + emailInputElement.val('not-a-valid-email').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(false); + + // Then valid input + emailInputElement.val('email@gitlab.com').keyup(); + expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(true); + + // Then invalid input + emailInputElement.val('not-a-valid-email').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(false); + + // Then empty input + emailInputElement.val('').keyup(); + expect(emailInputElement).toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(true); + expect(fieldState.valid).toBe(false); + + // Then valid input + emailInputElement.val('email@gitlab.com').keyup(); + expect(emailInputElement).not.toHaveClass('gl-field-error-outline'); + expect(fieldState.empty).toBe(false); + expect(fieldState.valid).toBe(true); + }); + + it('should properly infer error messages', function() { + this.$form.submit(); + const trackedInputs = this.fieldErrors.state.inputs; + const inputHasTitle = trackedInputs[1]; + const hasTitleErrorElem = inputHasTitle.inputElement.siblings('.gl-field-error'); + const inputNoTitle = trackedInputs[2]; + const noTitleErrorElem = inputNoTitle.inputElement.siblings('.gl-field-error'); + + expect(noTitleErrorElem.text()).toBe('This field is required.'); + expect(hasTitleErrorElem.text()).toBe('Please provide a valid email address.'); + }); + + }); + +})(window.gl || (window.gl = {})); -- cgit v1.2.1 From 673c23dd7ebef7d6c370a0d0d5c024c1c042261c Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 13:26:37 +0200 Subject: Unbreak all the tests relying on one login-box. --- app/views/devise/sessions/new.html.haml | 2 +- app/views/devise/shared/_omniauth_box.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index dbdbc9689aa..2fb05b9456b 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -13,7 +13,7 @@ = render 'devise/shared/signup_box' - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix.omniauth-box + .clearfix = render 'devise/shared/omniauth_box' -# Show a message if none of the mechanisms above are enabled diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index d5b6db48a29..8908b64cdac 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,4 +1,4 @@ -%div.login-box +%div.omniauth-container %p %span.light Sign in with   -- cgit v1.2.1 From 1fd09260826413da928363c3199d4dfbb770d2c3 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 15:47:21 +0200 Subject: Click first Sign In in login_helper. --- spec/support/login_helpers.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 3e90c95918c..ea9edc85248 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -33,9 +33,7 @@ module LoginHelpers fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember - page.within '.login-box' do - click_button "Sign in" - end + first('.login-box').click_button('Sign in') Thread.current[:current_user] = user end -- cgit v1.2.1 From 445711675ca198660dcaee014cd4229b8eb266ab Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 17:13:42 +0200 Subject: Fix nesting on sessions/new. --- app/views/devise/sessions/new.html.haml | 15 +++++++-------- spec/support/login_helpers.rb | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index 2fb05b9456b..fa8e7979461 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -12,12 +12,11 @@ - if signin_enabled? && signup_enabled? = render 'devise/shared/signup_box' - - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? - .clearfix - = render 'devise/shared/omniauth_box' - - -# Show a message if none of the mechanisms above are enabled - - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) - %div - No authentication methods configured. + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?) + %div + No authentication methods configured. + - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? + .clearfix + = render 'devise/shared/omniauth_box' diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index ea9edc85248..c0b3e83244d 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -33,7 +33,7 @@ module LoginHelpers fill_in "user_login", with: user.email fill_in "user_password", with: "12345678" check 'user_remember_me' if remember - first('.login-box').click_button('Sign in') + click_button "Sign in" Thread.current[:current_user] = user end -- cgit v1.2.1 From c2766614ddc04b8df54fe8649d1dbe516f532987 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 11:58:43 +0200 Subject: Fix syntax error -- missing comma in _new_crowd. --- app/views/devise/sessions/_new_crowd.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 5a192c63c7c..e82a08cdb0c 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,4 +1,4 @@ -= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' class: 'show-gl-field-errors') do += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'show-gl-field-errors') do .form-group = label_tag 'Username or email', for: :username = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } -- cgit v1.2.1 From 091f4cb26a79bf20542f5e260d461a235ec85011 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 29 Sep 2016 12:00:12 +0200 Subject: Update filled in field for two_factor auth to use id. --- spec/features/login_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 2523b4b7898..996f39ea06d 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -29,7 +29,7 @@ feature 'Login', feature: true do describe 'with two-factor authentication' do def enter_code(code) - fill_in 'Two-Factor Authentication code', with: code + fill_in 'user_otp_attempt', with: code click_button 'Verify code' end -- cgit v1.2.1 From 2486078d5b93aaaf5bb0f60e580f65fa3da0271a Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:45:12 +0200 Subject: Prevent unneccessary loading of UsernameValidator. --- app/assets/javascripts/dispatcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index fb6e82cd37c..956e6085bfb 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -22,7 +22,6 @@ shortcut_handler = null; switch (page) { case 'sessions:new': - case 'sessions:create': new UsernameValidator(); break; case 'projects:boards:show': -- cgit v1.2.1 From cbd68e5bd10b8cc8b933a8bfffd446a433628ddc Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 30 Sep 2016 10:45:53 +0200 Subject: Remove superfluous comment. --- app/assets/javascripts/username_validator.js.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index b19fb9b4771..2517f778365 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -95,7 +95,6 @@ } clearFieldValidationState() { - // TODO: Double check if this is valid chaining this.inputElement.siblings('p').hide(); this.inputElement.removeClass(invalidInputClass) -- cgit v1.2.1 From 80864a1939e495ca5e85ba3c0c3ea869d40abda4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 14:28:17 +0200 Subject: Safely scope siblings of validated input. --- app/assets/javascripts/gl_field_errors.js.es6 | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 91c25047f7b..e54a10a1901 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -36,6 +36,19 @@ // hidden when injected into DOM this.inputElement.after(this.fieldErrorElement); this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + this.scopedSiblings = this.safelySelectSiblings(); + } + + safelySelectSiblings() { + // Apply `ignoreSelector` in markup to siblings whose visibility should not be toggled with input validity + const ignoreSelector = '.validation-ignore'; + const unignoredSiblings = this.inputElement.siblings(`p:not(${ignoreSelector})`); + const parentContainer = this.inputElement.parent('.form-group'); + + // Only select siblings when they're scoped within a form-group with one input + const safelyScoped = parentContainer.length && parentContainer.find('input').length === 1; + + return safelyScoped ? unignoredSiblings : this.fieldErrorElement; } renderValidity() { @@ -90,7 +103,7 @@ setInvalidState() { this.inputElement.addClass(inputErrorClass); - this.inputElement.siblings('p').hide(); + this.scopedSiblings.hide(); return this.fieldErrorElement.show(); } @@ -101,7 +114,7 @@ this.inputElement.val(trimmedInput); } this.inputElement.removeClass(inputErrorClass); - this.inputElement.siblings('p').hide(); + this.scopedSiblings.hide(); this.fieldErrorElement.hide(); } } -- cgit v1.2.1 From cdf232752a4c2bfc6ebb57bcca3b33bcf8755b40 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 5 Oct 2016 14:28:52 +0200 Subject: Add accessor for current val for convenience. --- app/assets/javascripts/gl_field_errors.js.es6 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index e54a10a1901..86aea25fbbb 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -68,9 +68,13 @@ } + accessCurrentVal(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + handleInvalidInput(event) { event.preventDefault(); - const currentValue = this.inputElement.val(); + const currentValue = this.accessCurrentVal(); this.state.valid = false; this.state.empty = currentValue === ''; @@ -87,7 +91,7 @@ } updateValidityState() { - const inputVal = this.inputElement.val(); + const inputVal = this.accessCurrentVal(); this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); @@ -108,10 +112,10 @@ } setClearState() { - const inputVal = this.inputElement.val(); + const inputVal = this.accessCurrentVal(); if (!inputVal.split(' ').length) { - const trimmedInput = this.inputElement.val().trim(); - this.inputElement.val(trimmedInput); + const trimmedInput = inputVal.trim(); + this.accessCurrentVal(trimmedInput); } this.inputElement.removeClass(inputErrorClass); this.scopedSiblings.hide(); -- cgit v1.2.1 From 349caec3088905cf4dc740b8809f9fd6fbdeeb0e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:35:23 +0200 Subject: Stringify username before passing to ActiveRecord. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 30f0118254a..7a2bd83f22f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: !User.find_by_username(params[:username]).nil? } + render json: { exists: User.where(username: params[:username].to_s).any? } end private -- cgit v1.2.1 From 3c536bcc9d89e6d28d5a9bfb3ac8d1191ad03b88 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:51:02 +0200 Subject: Improve method naming in gl_field_errors. --- app/assets/javascripts/gl_field_errors.js.es6 | 43 ++++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index 86aea25fbbb..bd8f3c52234 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -35,7 +35,7 @@ initFieldValidation() { // hidden when injected into DOM this.inputElement.after(this.fieldErrorElement); - this.inputElement.off('invalid').on('invalid', this.handleInvalidInput.bind(this)); + this.inputElement.off('invalid').on('invalid', this.handleInvalidSubmit.bind(this)); this.scopedSiblings = this.safelySelectSiblings(); } @@ -52,29 +52,25 @@ } renderValidity() { - this.setClearState(); + this.renderClear(); if (this.state.valid) { - return this.setValidState(); + return this.renderValid(); } if (this.state.empty) { - return this.setEmptyState(); + return this.renderEmpty(); } if (!this.state.valid) { - return this.setInvalidState(); + return this.renderInvalid(); } } - accessCurrentVal(newVal) { - return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); - } - - handleInvalidInput(event) { + handleInvalidSubmit(event) { event.preventDefault(); - const currentValue = this.accessCurrentVal(); + const currentValue = this.accessCurrentValue(); this.state.valid = false; this.state.empty = currentValue === ''; @@ -86,36 +82,41 @@ } + /* Get or set current input value */ + accessCurrentValue(newVal) { + return newVal ? this.inputElement.val(newVal) : this.inputElement.val(); + } + getInputValidity() { return this.inputDomElement.validity.valid; } - updateValidityState() { - const inputVal = this.accessCurrentVal(); + updateValidity() { + const inputVal = this.accessCurrentValue(); this.state.empty = !inputVal.length; this.state.valid = this.getInputValidity(); this.renderValidity(); } - setValidState() { - return this.setClearState(); + renderValid() { + return this.renderClear(); } - setEmptyState() { - return this.setInvalidState(); + renderEmpty() { + return this.renderInvalid(); } - setInvalidState() { + renderInvalid() { this.inputElement.addClass(inputErrorClass); this.scopedSiblings.hide(); return this.fieldErrorElement.show(); } - setClearState() { - const inputVal = this.accessCurrentVal(); + renderClear() { + const inputVal = this.accessCurrentValue(); if (!inputVal.split(' ').length) { const trimmedInput = inputVal.trim(); - this.accessCurrentVal(trimmedInput); + this.accessCurrentValue(trimmedInput); } this.inputElement.removeClass(inputErrorClass); this.scopedSiblings.hide(); -- cgit v1.2.1 From 0f57245f0c81fe00ecedbb39c1e93fc48ef55099 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 11:57:49 +0200 Subject: Use native dom in dispatcher field error init. --- app/assets/javascripts/dispatcher.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 956e6085bfb..846884ea793 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -296,9 +296,10 @@ }; Dispatcher.prototype.initFieldErrors = function() { - $('form.show-gl-field-errors').each(function(i, form) { - new gl.GlFieldErrors(form); - }); + return document.querySelectorAll('.show-gl-field-errors') + .forEach(function(form) { + new gl.GlFieldErrors(form); + }); }; return Dispatcher; -- cgit v1.2.1 From 80aa0a8f86a02e7b36a866906b902a3dfce325f3 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 12:06:45 +0200 Subject: Revert conflict resolution in routes.rb. --- config/routes.rb | 792 ------------------------------------------------------- 1 file changed, 792 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 93d7f99fb90..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,798 +83,6 @@ Rails.application.routes.draw do draw :group draw :user draw :project - # - # Import - # - namespace :import do - resource :github, only: [:create, :new], controller: :github do - post :personal_access_token - get :status - get :callback - get :jobs - end - - resource :gitlab, only: [:create], controller: :gitlab do - get :status - get :callback - get :jobs - end - - resource :bitbucket, only: [:create], controller: :bitbucket do - get :status - get :callback - get :jobs - end - - resource :google_code, only: [:create, :new], controller: :google_code do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :fogbugz, only: [:create, :new], controller: :fogbugz do - get :status - post :callback - get :jobs - - get :new_user_map, path: :user_map - post :create_user_map, path: :user_map - end - - resource :gitlab_project, only: [:create, :new] do - post :create - end - end - - # - # Uploads - # - - scope path: :uploads do - # Note attachments and User/Group/Project avatars - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } - - # Appearance - get ":model/:mounted_as/:id/:filename", - to: "uploads#show", - constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } - - # Project markdown uploads - get ":namespace_id/:project_id/:secret/:filename", - to: "projects/uploads#show", - constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } - end - - # Redirect old note attachments path to new uploads path. - get "files/note/:id/:filename", - to: redirect("uploads/note/attachment/%{id}/%{filename}"), - constraints: { filename: /[^\/]+/ } - - # - # Explore area - # - namespace :explore do - resources :projects, only: [:index] do - collection do - get :trending - get :starred - end - end - - resources :groups, only: [:index] - resources :snippets, only: [:index] - root to: 'projects#trending' - end - - # Compatibility with old routing - get 'public' => 'explore/projects#index' - get 'public/projects' => 'explore/projects#index' - - # - # Admin Area - # - namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do - resources :keys, only: [:show, :destroy] - resources :identities, except: [:show] - - member do - get :projects - get :keys - get :groups - put :block - put :unblock - put :unlock - put :confirm - post :impersonate - patch :disable_two_factor - delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' - end - end - - resource :impersonation, only: :destroy - - resources :abuse_reports, only: [:index, :destroy] - resources :spam_logs, only: [:index, :destroy] do - member do - post :mark_as_ham - end - end - - resources :applications - - resources :groups, constraints: { id: /[^\/]+/ } do - member do - put :members_update - end - end - - resources :deploy_keys, only: [:index, :new, :create, :destroy] - - resources :hooks, only: [:index, :create, :destroy] do - get :test - end - - resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do - post :preview, on: :collection - end - - resource :logs, only: [:show] - resource :health_check, controller: 'health_check', only: [:show] - resource :background_jobs, controller: 'background_jobs', only: [:show] - resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects - - resources(:projects, - path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' - - member do - put :transfer - post :repository_check - end - - resources :runner_projects, only: [:create, :destroy] - end - end - - resource :appearances, only: [:show, :create, :update], path: 'appearance' do - member do - get :preview - delete :logo - delete :header_logos - end - end - - resource :application_settings, only: [:show, :update] do - resources :services, only: [:index, :edit, :update] - put :reset_runners_token - put :reset_health_check_token - put :clear_repository_check_states - end - - resources :labels - - resources :runners, only: [:index, :show, :update, :destroy] do - member do - get :resume - get :pause - end - end - - resources :builds, only: :index do - collection do - post :cancel_all - end - end - - root to: 'dashboard#index' - end - - # - # Profile Area - # - resource :profile, only: [:show, :update] do - member do - get :audit_log - get :applications, to: 'oauth/applications#index' - - put :reset_private_token - put :update_username - end - - scope module: :profiles do - resource :account, only: [:show] do - member do - delete :unlink - end - end - resource :notifications, only: [:show, :update] - resource :password, only: [:new, :create, :edit, :update] do - member do - put :reset - end - end - resource :preferences, only: [:show, :update] - resources :keys, only: [:index, :show, :new, :create, :destroy] - resources :emails, only: [:index, :create, :destroy] - resource :avatar, only: [:destroy] - - resources :personal_access_tokens, only: [:index, :create] do - member do - put :revoke - end - end - - resource :two_factor_auth, only: [:show, :create, :destroy] do - member do - post :create_u2f - post :codes - patch :skip - end - end - - resources :u2f_registrations, only: [:destroy] - end - end - - scope(path: 'u/:username', - as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? 'omniauth_callbacks#omniauth_error', as: :omniauth_error - get '/users/almost_there' => 'confirmations#almost_there' - end - - root to: "root#index" - - # - # Project Area - # - resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(? 'templates#show', as: :template - - scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) - end - - scope do - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) - end - - scope do - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) - end - - scope do - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) - end - - scope do - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) - end - - scope do - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) - end - - scope do - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) - end - - scope do - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, - as: :commits - ) - end - - resource :avatar, only: [:show, :destroy] - resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do - member do - get :branches - get :builds - get :pipelines - post :cancel_builds - post :retry_builds - post :revert - post :cherry_pick - get :diff_for_path - end - end - - resources :compare, only: [:index, :create] do - collection do - get :diff_for_path - end - end - - get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - - # Don't use format parameter as file extension (old 3.0.x behavior) - # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments - scope format: false do - resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do - member do - get :commits - get :ci - get :languages - end - end - end - - resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get 'raw' - end - end - - WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID - - scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' - - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' - end - - resource :repository, only: [:create] do - member do - get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do - member do - put :enable - put :disable - end - end - - resources :forks, only: [:index, :new, :create] - resource :import, only: [:new, :create, :show] - - resources :refs, only: [] do - collection do - get 'switch' - end - - member do - # tree viewer logs - get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } - # Directories with leading dots erroneously get rejected if git - # ref regex used in constraints. Regex verification now done in controller. - get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: { - id: /.*/, - path: /.*/ - } - end - end - - resources :merge_requests, concerns: :awardable, constraints: { id: /\d+/ } do - member do - get :commits - get :diffs - get :conflicts - get :builds - get :pipelines - get :merge_check - post :merge - post :cancel_merge_when_build_succeeds - get :ci_status - post :toggle_subscription - post :remove_wip - get :diff_for_path - post :resolve_conflicts - end - - collection do - get :branch_from - get :branch_to - get :update_branches - get :diff_for_path - post :bulk_update - end - - resources :discussions, only: [], constraints: { id: /\h{40}/ } do - member do - post :resolve - delete :resolve, action: :unresolve - end - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :show, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } do - resource :release, only: [:edit, :update] - end - - resources :protected_branches, only: [:index, :show, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :variables, only: [:index, :show, :update, :create, :destroy] - resources :triggers, only: [:index, :create, :destroy] - - resources :pipelines, only: [:index, :new, :create, :show] do - collection do - resource :pipelines_settings, path: 'settings', only: [:show, :update] - end - - member do - post :cancel - post :retry - end - end - - resources :environments - - resource :cycle_analytics, only: [:show] - - resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do - collection do - post :cancel_all - - resources :artifacts, only: [] do - collection do - get :latest_succeeded, - path: '*ref_name_and_path', - format: false - end - end - end - - member do - get :status - post :cancel - post :retry - post :play - post :erase - get :trace - get :raw - end - - resource :artifacts, only: [] do - get :download - get :browse, path: 'browse(/*path)', format: false - get :file, path: 'file/*path', format: false - post :keep - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do - member do - get :test - end - end - - resources :container_registry, only: [:index, :destroy], constraints: { id: Gitlab::Regex.container_registry_reference_regex } - - resources :milestones, constraints: { id: /\d+/ } do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, except: [:show], constraints: { id: /\d+/ } do - collection do - post :generate - post :set_priorities - end - - member do - post :toggle_subscription - delete :remove_priority - end - end - - resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do - member do - post :toggle_subscription - post :mark_as_spam - get :referenced_merge_requests - get :related_branches - get :can_create_branch - end - collection do - post :bulk_update - end - end - - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - - member do - post :resend_invite - end - end - - resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ } - - resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do - member do - delete :delete_attachment - post :resolve - delete :resolve, action: :unresolve - end - end - - resource :board, only: [:show] do - scope module: :boards do - resources :issues, only: [:update] - - resources :lists, only: [:index, :create, :update, :destroy] do - collection do - post :generate - end - - resources :issues, only: [:index] - end - end - end - - resources :todos, only: [:create] - - resources :uploads, only: [:create] do - collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } - end - end - - resources :runners, only: [:index, :edit, :update, :destroy, :show] do - member do - get :resume - get :pause - end - - collection do - post :toggle_shared_runners - end - end - - resources :runner_projects, only: [:create, :destroy] - resources :badges, only: [:index] do - collection do - scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do - constraints format: /svg/ do - get :build - get :coverage - end - end - end - end - end - end - end # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } -- cgit v1.2.1 From bad1feb23c63a86f8f4103e4b75ec590fc90da4f Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 21:45:55 +0200 Subject: Convert field error NodeList to an Array. --- app/assets/javascripts/dispatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 846884ea793..6452ae675b4 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -296,8 +296,8 @@ }; Dispatcher.prototype.initFieldErrors = function() { - return document.querySelectorAll('.show-gl-field-errors') - .forEach(function(form) { + var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); + return [...taggedForms].forEach(function(form) { new gl.GlFieldErrors(form); }); }; -- cgit v1.2.1 From f141cdb3dddd16c729a828838e907473132a3a18 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 7 Oct 2016 21:50:35 +0200 Subject: Fixup invalid refs. --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/gl_field_errors.js.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 6452ae675b4..abc077a404e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -297,7 +297,7 @@ Dispatcher.prototype.initFieldErrors = function() { var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...taggedForms].forEach(function(form) { + return [...flaggedForms].forEach(function(form) { new gl.GlFieldErrors(form); }); }; diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6 index bd8f3c52234..8657e7b4abf 100644 --- a/app/assets/javascripts/gl_field_errors.js.es6 +++ b/app/assets/javascripts/gl_field_errors.js.es6 @@ -78,7 +78,7 @@ this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup this.inputElement.off('keyup.field_validator') - .on('keyup.field_validator', this.updateValidityState.bind(this)); + .on('keyup.field_validator', this.updateValidity.bind(this)); } -- cgit v1.2.1 From 23fd1f1630f4977ecc452ede086eaebfd836b978 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:34:57 +0200 Subject: Use Ruby 1.9 syntax in tab_single def and usage. --- app/views/admin/appearances/preview.html.haml | 2 +- app/views/devise/confirmations/new.html.haml | 2 +- app/views/devise/passwords/edit.html.haml | 2 +- app/views/devise/passwords/new.html.haml | 2 +- app/views/devise/sessions/two_factor.html.haml | 2 +- app/views/devise/shared/_tab_single.html.haml | 3 +-- app/views/devise/unlocks/new.html.haml | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index 0d35702c634..acbe17036f7 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Sign in preview' } += render 'devise/shared/tab_single', tab_title: 'Sign in preview' .login-box %form.show-gl-field-errors .form-group diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml index 443a316c6e2..5d25dd398d6 100644 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Resend confirmation instructions' } += render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' .login-box .login-body = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 9c533ef9916..b518fae7c95 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Change your password' } += render 'devise/shared/tab_single', tab_title:'Change your password' .login-box .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index 91b46a12ac0..1fcfd06419a 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Reset Password' } += render 'devise/shared/tab_single', tab_title: 'Reset Password' .login-box .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 56074c057d7..0e865b807c1 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -3,7 +3,7 @@ = page_specific_javascript_tag('u2f.js') %div - = render 'devise/shared/tab_single', { :tab_title => 'Two-Factor Authentication' } + = render 'devise/shared/tab_single', tab_title: 'Two-Factor Authentication' .login-box .login-body - if @user.two_factor_otp_enabled? diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index 8590c43d54d..f943d25e41a 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,4 +1,3 @@ -// = render 'devise/shared/tab_single', :tab_title => 'Tab Title' %ul.nav-links.nav-tabs.new-session-tabs.single-tab %li.active - = link_to tab_title, '#', disabled: true + %a= tab_title diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml index 0036f3b98e5..49b2f77111f 100644 --- a/app/views/devise/unlocks/new.html.haml +++ b/app/views/devise/unlocks/new.html.haml @@ -1,4 +1,4 @@ -= render 'devise/shared/tab_single', { :tab_title => 'Resend unlock instructions' } += render 'devise/shared/tab_single', tab_title: 'Resend unlock instructions' .login-box .login-body = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'show-gl-field-errors' }) do |f| -- cgit v1.2.1 From 85db5ba847ffc8f4b71ad9fe0ba22ab42f90b3e4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:35:27 +0200 Subject: Clean up layouts/devise.html. --- app/views/layouts/devise.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 825e540cb0c..6922f1e153f 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en", class: "devise-layout-html"} = render "layouts/head" - %body{ class: "ui_charcoal login-page application navless", data: {page: body_data_page}} + %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page }} .page-wrap = Gon::Base.render_data = render "layouts/header/empty" @@ -22,7 +22,7 @@ %h3 Open source software to collaborate on code %p - Manage git repositories with fine grained access controls that keep your code secure. + Manage Git repositories with fine-grained access controls that keep your code secure. Perform code reviews and enhance collaboration with merge requests. Each project can also have an issue tracker and a wiki. -- cgit v1.2.1 From 5439bd9f952c7e7ee9fa10613655a9761396e1f9 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 10 Oct 2016 16:54:55 +0200 Subject: Attempt to fix username validation ruby. --- app/controllers/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 7a2bd83f22f..1aa22995692 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: User.where(username: params[:username].to_s).any? } + render json: { exists: !Namespace.where(name: params[:username]).nil? } end private -- cgit v1.2.1 From 568a405ce43834d5b4da3ff81f1e8a6972db802b Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 18:28:00 +0200 Subject: Add exists to users routes and fix endpoint. --- app/controllers/users_controller.rb | 2 +- config/routes/user.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1aa22995692..6a881b271d7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -86,7 +86,7 @@ class UsersController < ApplicationController end def exists - render json: { exists: !Namespace.where(name: params[:username]).nil? } + render json: { exists: Namespace.where(path: params[:username].downcase).any? } end private diff --git a/config/routes/user.rb b/config/routes/user.rb index 54bbcb18f6a..dfb5d2a2ba4 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -33,5 +33,6 @@ scope(path: 'u/:username', get :projects get :contributed, as: :contributed_projects get :snippets + get :exists get '/', to: redirect('/%{username}') end -- cgit v1.2.1 From eb866dfe05c82b2a45da69d924bcbe15b151a054 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 18:34:38 +0200 Subject: Convert dispatcher to es6. --- app/assets/javascripts/dispatcher.js | 309 ------------------------------- app/assets/javascripts/dispatcher.js.es6 | 309 +++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+), 309 deletions(-) delete mode 100644 app/assets/javascripts/dispatcher.js create mode 100644 app/assets/javascripts/dispatcher.js.es6 diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js deleted file mode 100644 index abc077a404e..00000000000 --- a/app/assets/javascripts/dispatcher.js +++ /dev/null @@ -1,309 +0,0 @@ -(function() { - var Dispatcher; - - $(function() { - return new Dispatcher(); - }); - - Dispatcher = (function() { - function Dispatcher() { - this.initSearch(); - this.initFieldErrors(); - this.initPageScripts(); - } - - Dispatcher.prototype.initPageScripts = function() { - var page, path, shortcut_handler; - page = $('body').attr('data-page'); - if (!page) { - return false; - } - path = page.split(':'); - shortcut_handler = null; - switch (page) { - case 'sessions:new': - new UsernameValidator(); - break; - case 'projects:boards:show': - case 'projects:boards:index': - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:merge_requests:index': - case 'projects:issues:index': - Issuable.init(); - new gl.IssuableBulkActions(); - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:issues:show': - new Issue(); - shortcut_handler = new ShortcutsIssuable(); - new ZenMode(); - break; - case 'projects:milestones:show': - case 'groups:milestones:show': - case 'dashboard:milestones:show': - new Milestone(); - break; - case 'dashboard:todos:index': - new gl.Todos(); - break; - case 'projects:milestones:new': - case 'projects:milestones:edit': - new ZenMode(); - new DueDateSelect(); - new GLForm($('.milestone-form')); - break; - case 'groups:milestones:new': - new ZenMode(); - break; - case 'projects:compare:show': - new Diff(); - break; - case 'projects:issues:new': - case 'projects:issues:edit': - shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.issue-form')); - new IssuableForm($('.issue-form')); - new LabelsSelect(); - new MilestoneSelect(); - new gl.IssuableTemplateSelectors(); - break; - case 'projects:merge_requests:new': - case 'projects:merge_requests:edit': - new Diff(); - shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.merge-request-form')); - new IssuableForm($('.merge-request-form')); - new LabelsSelect(); - new MilestoneSelect(); - new gl.IssuableTemplateSelectors(); - break; - case 'projects:tags:new': - new ZenMode(); - new GLForm($('.tag-form')); - break; - case 'projects:releases:edit': - new ZenMode(); - new GLForm($('.release-form')); - break; - case 'projects:merge_requests:show': - new Diff(); - shortcut_handler = new ShortcutsIssuable(true); - new ZenMode(); - new MergedButtons(); - break; - case 'projects:merge_requests:commits': - case 'projects:merge_requests:builds': - new MergedButtons(); - break; - case "projects:merge_requests:diffs": - new Diff(); - new ZenMode(); - new MergedButtons(); - break; - case "projects:merge_requests:conflicts": - window.mcui = new MergeConflictResolver() - break; - case 'projects:merge_requests:index': - shortcut_handler = new ShortcutsNavigation(); - Issuable.init(); - break; - case 'dashboard:activity': - new Activities(); - break; - case 'dashboard:projects:starred': - new Activities(); - break; - case 'projects:commit:show': - new Commit(); - new Diff(); - new ZenMode(); - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:commits:show': - case 'projects:activity': - shortcut_handler = new ShortcutsNavigation(); - break; - case 'projects:show': - shortcut_handler = new ShortcutsNavigation(); - new NotificationsForm(); - if ($('#tree-slider').length) { - new TreeView(); - } - break; - case 'projects:pipelines:show': - new gl.Pipelines(); - break; - case 'groups:activity': - new Activities(); - break; - case 'groups:show': - shortcut_handler = new ShortcutsNavigation(); - new NotificationsForm(); - new NotificationsDropdown(); - break; - case 'groups:group_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); - new UsersSelect(); - break; - case 'projects:project_members:index': - new gl.MemberExpirationDate(); - new gl.Members(); - new UsersSelect(); - break; - case 'groups:new': - case 'groups:edit': - case 'admin:groups:edit': - case 'admin:groups:new': - new GroupAvatar(); - break; - case 'projects:tree:show': - shortcut_handler = new ShortcutsNavigation(); - new TreeView(); - break; - case 'projects:find_file:show': - shortcut_handler = true; - break; - case 'projects:blob:show': - case 'projects:blame:show': - new LineHighlighter(); - shortcut_handler = new ShortcutsNavigation(); - new ShortcutsBlob(true); - break; - case 'projects:labels:new': - case 'projects:labels:edit': - new Labels(); - break; - case 'projects:labels:index': - if ($('.prioritized-labels').length) { - new gl.LabelManager(); - } - break; - case 'projects:network:show': - // Ensure we don't create a particular shortcut handler here. This is - // already created, where the network graph is created. - shortcut_handler = true; - break; - case 'projects:forks:new': - new ProjectFork(); - break; - case 'projects:artifacts:browse': - new BuildArtifacts(); - break; - case 'projects:group_links:index': - new gl.MemberExpirationDate(); - new GroupsSelect(); - break; - case 'search:show': - new Search(); - break; - case 'projects:protected_branches:index': - new gl.ProtectedBranchCreate(); - new gl.ProtectedBranchEditList(); - break; - case 'projects:cycle_analytics:show': - new gl.CycleAnalytics(); - break; - } - switch (path.first()) { - case 'admin': - new Admin(); - switch (path[1]) { - case 'groups': - new UsersSelect(); - break; - case 'projects': - new NamespaceSelects(); - break; - case 'labels': - switch (path[2]) { - case 'new': - case 'edit': - new Labels(); - } - case 'abuse_reports': - new gl.AbuseReports(); - break; - } - break; - case 'dashboard': - case 'root': - shortcut_handler = new ShortcutsDashboardNavigation(); - break; - case 'profiles': - new NotificationsForm(); - new NotificationsDropdown(); - break; - case 'projects': - new Project(); - new ProjectAvatar(); - switch (path[1]) { - case 'compare': - new CompareAutocomplete(); - break; - case 'edit': - shortcut_handler = new ShortcutsNavigation(); - new ProjectNew(); - break; - case 'new': - new ProjectNew(); - break; - case 'show': - new Star(); - new ProjectNew(); - new ProjectShow(); - new NotificationsDropdown(); - break; - case 'wikis': - new Wikis(); - shortcut_handler = new ShortcutsNavigation(); - new ZenMode(); - new GLForm($('.wiki-form')); - break; - case 'snippets': - shortcut_handler = new ShortcutsNavigation(); - if (path[2] === 'show') { - new ZenMode(); - } - break; - case 'labels': - case 'graphs': - case 'compare': - case 'pipelines': - case 'forks': - case 'milestones': - case 'project_members': - case 'deploy_keys': - case 'builds': - case 'hooks': - case 'services': - case 'protected_branches': - shortcut_handler = new ShortcutsNavigation(); - } - } - // If we haven't installed a custom shortcut handler, install the default one - if (!shortcut_handler) { - return new Shortcuts(); - } - }; - - Dispatcher.prototype.initSearch = function() { - // Only when search form is present - if ($('.search').length) { - return new gl.SearchAutocomplete(); - } - }; - - Dispatcher.prototype.initFieldErrors = function() { - var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...flaggedForms].forEach(function(form) { - new gl.GlFieldErrors(form); - }); - }; - - return Dispatcher; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 new file mode 100644 index 00000000000..abc077a404e --- /dev/null +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -0,0 +1,309 @@ +(function() { + var Dispatcher; + + $(function() { + return new Dispatcher(); + }); + + Dispatcher = (function() { + function Dispatcher() { + this.initSearch(); + this.initFieldErrors(); + this.initPageScripts(); + } + + Dispatcher.prototype.initPageScripts = function() { + var page, path, shortcut_handler; + page = $('body').attr('data-page'); + if (!page) { + return false; + } + path = page.split(':'); + shortcut_handler = null; + switch (page) { + case 'sessions:new': + new UsernameValidator(); + break; + case 'projects:boards:show': + case 'projects:boards:index': + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:merge_requests:index': + case 'projects:issues:index': + Issuable.init(); + new gl.IssuableBulkActions(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:issues:show': + new Issue(); + shortcut_handler = new ShortcutsIssuable(); + new ZenMode(); + break; + case 'projects:milestones:show': + case 'groups:milestones:show': + case 'dashboard:milestones:show': + new Milestone(); + break; + case 'dashboard:todos:index': + new gl.Todos(); + break; + case 'projects:milestones:new': + case 'projects:milestones:edit': + new ZenMode(); + new DueDateSelect(); + new GLForm($('.milestone-form')); + break; + case 'groups:milestones:new': + new ZenMode(); + break; + case 'projects:compare:show': + new Diff(); + break; + case 'projects:issues:new': + case 'projects:issues:edit': + shortcut_handler = new ShortcutsNavigation(); + new GLForm($('.issue-form')); + new IssuableForm($('.issue-form')); + new LabelsSelect(); + new MilestoneSelect(); + new gl.IssuableTemplateSelectors(); + break; + case 'projects:merge_requests:new': + case 'projects:merge_requests:edit': + new Diff(); + shortcut_handler = new ShortcutsNavigation(); + new GLForm($('.merge-request-form')); + new IssuableForm($('.merge-request-form')); + new LabelsSelect(); + new MilestoneSelect(); + new gl.IssuableTemplateSelectors(); + break; + case 'projects:tags:new': + new ZenMode(); + new GLForm($('.tag-form')); + break; + case 'projects:releases:edit': + new ZenMode(); + new GLForm($('.release-form')); + break; + case 'projects:merge_requests:show': + new Diff(); + shortcut_handler = new ShortcutsIssuable(true); + new ZenMode(); + new MergedButtons(); + break; + case 'projects:merge_requests:commits': + case 'projects:merge_requests:builds': + new MergedButtons(); + break; + case "projects:merge_requests:diffs": + new Diff(); + new ZenMode(); + new MergedButtons(); + break; + case "projects:merge_requests:conflicts": + window.mcui = new MergeConflictResolver() + break; + case 'projects:merge_requests:index': + shortcut_handler = new ShortcutsNavigation(); + Issuable.init(); + break; + case 'dashboard:activity': + new Activities(); + break; + case 'dashboard:projects:starred': + new Activities(); + break; + case 'projects:commit:show': + new Commit(); + new Diff(); + new ZenMode(); + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:commits:show': + case 'projects:activity': + shortcut_handler = new ShortcutsNavigation(); + break; + case 'projects:show': + shortcut_handler = new ShortcutsNavigation(); + new NotificationsForm(); + if ($('#tree-slider').length) { + new TreeView(); + } + break; + case 'projects:pipelines:show': + new gl.Pipelines(); + break; + case 'groups:activity': + new Activities(); + break; + case 'groups:show': + shortcut_handler = new ShortcutsNavigation(); + new NotificationsForm(); + new NotificationsDropdown(); + break; + case 'groups:group_members:index': + new gl.MemberExpirationDate(); + new gl.Members(); + new UsersSelect(); + break; + case 'projects:project_members:index': + new gl.MemberExpirationDate(); + new gl.Members(); + new UsersSelect(); + break; + case 'groups:new': + case 'groups:edit': + case 'admin:groups:edit': + case 'admin:groups:new': + new GroupAvatar(); + break; + case 'projects:tree:show': + shortcut_handler = new ShortcutsNavigation(); + new TreeView(); + break; + case 'projects:find_file:show': + shortcut_handler = true; + break; + case 'projects:blob:show': + case 'projects:blame:show': + new LineHighlighter(); + shortcut_handler = new ShortcutsNavigation(); + new ShortcutsBlob(true); + break; + case 'projects:labels:new': + case 'projects:labels:edit': + new Labels(); + break; + case 'projects:labels:index': + if ($('.prioritized-labels').length) { + new gl.LabelManager(); + } + break; + case 'projects:network:show': + // Ensure we don't create a particular shortcut handler here. This is + // already created, where the network graph is created. + shortcut_handler = true; + break; + case 'projects:forks:new': + new ProjectFork(); + break; + case 'projects:artifacts:browse': + new BuildArtifacts(); + break; + case 'projects:group_links:index': + new gl.MemberExpirationDate(); + new GroupsSelect(); + break; + case 'search:show': + new Search(); + break; + case 'projects:protected_branches:index': + new gl.ProtectedBranchCreate(); + new gl.ProtectedBranchEditList(); + break; + case 'projects:cycle_analytics:show': + new gl.CycleAnalytics(); + break; + } + switch (path.first()) { + case 'admin': + new Admin(); + switch (path[1]) { + case 'groups': + new UsersSelect(); + break; + case 'projects': + new NamespaceSelects(); + break; + case 'labels': + switch (path[2]) { + case 'new': + case 'edit': + new Labels(); + } + case 'abuse_reports': + new gl.AbuseReports(); + break; + } + break; + case 'dashboard': + case 'root': + shortcut_handler = new ShortcutsDashboardNavigation(); + break; + case 'profiles': + new NotificationsForm(); + new NotificationsDropdown(); + break; + case 'projects': + new Project(); + new ProjectAvatar(); + switch (path[1]) { + case 'compare': + new CompareAutocomplete(); + break; + case 'edit': + shortcut_handler = new ShortcutsNavigation(); + new ProjectNew(); + break; + case 'new': + new ProjectNew(); + break; + case 'show': + new Star(); + new ProjectNew(); + new ProjectShow(); + new NotificationsDropdown(); + break; + case 'wikis': + new Wikis(); + shortcut_handler = new ShortcutsNavigation(); + new ZenMode(); + new GLForm($('.wiki-form')); + break; + case 'snippets': + shortcut_handler = new ShortcutsNavigation(); + if (path[2] === 'show') { + new ZenMode(); + } + break; + case 'labels': + case 'graphs': + case 'compare': + case 'pipelines': + case 'forks': + case 'milestones': + case 'project_members': + case 'deploy_keys': + case 'builds': + case 'hooks': + case 'services': + case 'protected_branches': + shortcut_handler = new ShortcutsNavigation(); + } + } + // If we haven't installed a custom shortcut handler, install the default one + if (!shortcut_handler) { + return new Shortcuts(); + } + }; + + Dispatcher.prototype.initSearch = function() { + // Only when search form is present + if ($('.search').length) { + return new gl.SearchAutocomplete(); + } + }; + + Dispatcher.prototype.initFieldErrors = function() { + var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); + return [...flaggedForms].forEach(function(form) { + new gl.GlFieldErrors(form); + }); + }; + + return Dispatcher; + + })(); + +}).call(this); -- cgit v1.2.1 From 716406bc71fc3866588ccca8c132060d4f33e53e Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 18:47:31 +0200 Subject: Back off the array spreading, bc poltergeist freaks out. --- app/assets/javascripts/dispatcher.js.es6 | 7 +++---- spec/features/merge_requests/user_uses_slash_commands_spec.rb | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index abc077a404e..f3957ed374b 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -296,10 +296,9 @@ }; Dispatcher.prototype.initFieldErrors = function() { - var flaggedForms = document.querySelectorAll('.show-gl-field-errors'); - return [...flaggedForms].forEach(function(form) { - new gl.GlFieldErrors(form); - }); + $('.show-gl-field-errors').each((i, form) => { + new gl.GlFieldErrors(form); + }); }; return Dispatcher; diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index cb3cea3fd51..7b8af555f0e 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -20,7 +20,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do login_with(user) visit namespace_project_merge_request_path(project.namespace, project, merge_request) end - + after do wait_for_ajax end @@ -34,7 +34,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do expect(page).to have_content 'Your commands have been executed!' expect(merge_request.reload.work_in_progress?).to eq true - end + end it 'removes the WIP: prefix from the title' do merge_request.title = merge_request.wip_title @@ -45,7 +45,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do expect(page).to have_content 'Your commands have been executed!' expect(merge_request.reload.work_in_progress?).to eq false - end + end end context 'when the current user cannot toggle the WIP prefix' do -- cgit v1.2.1 From 75962a2ddd320fa03fc18d04861b42342c91a978 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 13 Oct 2016 17:50:35 +0200 Subject: Clean up stray Sign up ref. --- app/views/projects/notes/_notes_with_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 8352eba7446..00b62a595ff 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -14,7 +14,7 @@ .disabled-comment.text-center .disabled-comment-text.inline Please - = link_to "sign up", new_session_path(:user, redirect_to_referer: 'yes') + = link_to "register", new_session_path(:user, redirect_to_referer: 'yes') or = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes') to post a comment -- cgit v1.2.1 From b46307d3c59e3cf24eef3a2411ea8af2f25e78ba Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Thu, 13 Oct 2016 17:59:55 +0200 Subject: Don't use member properties in users_spec, and remove loading ref. --- spec/features/users_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 17a555e4673..ec4c4d62f53 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -61,20 +61,17 @@ feature 'Users', feature: true, js: true do before(:each) do visit new_user_session_path click_link 'Register' - @username_form_group = find '.username' - @username_field = find '#new_user_username' end - scenario 'shows an error border if the username already exists' do fill_in username_input, with: user.username wait_for_ajax - expect(@username_form_group).to have_css '.gl-field-error-outline' + expect(find('.username')).to have_css '.gl-field-error-outline' end scenario 'doesn\'t show an error border if the username is available' do fill_in username_input, with: 'new-user' wait_for_ajax - expect(@username_field).not_to have_css '.gl-field-error-outline' + expect(find('#new_user_username')).not_to have_css '.gl-field-error-outline' end end -- cgit v1.2.1 From 391e109c813542d5f32953088e91a6ea82dfdcfc Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Sat, 15 Oct 2016 09:05:37 +0200 Subject: Revert "Improve tabbing usability for sign in page" This reverts commit 8751491b8db471dc661daa19bc82a9dbd58e4aae. --- app/assets/stylesheets/pages/login.scss | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index f1d15417705..e210dc4c0b8 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -52,16 +52,6 @@ margin: 0 0 10px; } - .new_user { - position: relative; - padding-bottom: 35px; - } - - .sign-in { - position: absolute; - bottom: 0; - } - .login-footer { margin-top: 10px; -- cgit v1.2.1 From 54c0286a3f7306f8e5bfbbbf321cd098e4fc6e11 Mon Sep 17 00:00:00 2001 From: De Wet Blomerus Date: Fri, 14 Oct 2016 21:03:27 +0200 Subject: remove ashley since she no longer works here --- doc/development/doc_styleguide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 0b725cf200c..975ff9a4612 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -96,7 +96,7 @@ merge request. - When introducing a new document, be careful for the headings to be grammatically and syntactically correct. It is advised to mention one or all of the following GitLab members for a review: `@axil`, `@rspeicher`, - `@dblessing`, `@ashleys`. This is to ensure that no document + `@dblessing`. This is to ensure that no document with wrong heading is going live without an audit, thus preventing dead links and redirection issues when corrected - Leave exactly one newline after a heading -- cgit v1.2.1 From 5a8738a7b43ae567829bca36db6f13f430b56c83 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 14:49:20 -0700 Subject: Fix Spinach merge request diff failures gitlab-git-test `master` was updated in ff076d88, and this caused the merge request diffs to change in a way that broke assumptions in the Spinach tests. Partial fix to #23378 --- features/steps/project/merge_requests.rb | 7 ++++--- spec/support/test_env.rb | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 4a67cf06fba..b0c33aa6f2f 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -114,7 +114,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps source_project: project, target_project: project, source_branch: 'fix', - target_branch: 'master', + target_branch: 'merge-test', author: project.users.first, description: "# Description header" ) @@ -137,7 +137,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps title: "Bug NS-05", source_project: project, target_project: project, - author: project.users.first) + author: project.users.first, + source_branch: 'merge-test') end step 'project "Shop" have "Feature NS-05" merged merge request' do @@ -508,7 +509,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see new target branch changes' do expect(page).to have_content 'Request to merge fix into feature' - expect(page).to have_content 'Target branch changed from master to feature' + expect(page).to have_content 'Target branch changed from merge-test to feature' end step 'I click on "Email Patches"' do diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index d56274d0979..ad8ae763f6d 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -17,6 +17,7 @@ module TestEnv 'markdown' => '0ed8c6c', 'lfs' => 'be93687', 'master' => 'b83d6e3', + 'merge-test' => '5937ac0', "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', -- cgit v1.2.1 From fa25f61d474849a7a1ac893af9896b3227c33404 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 20:31:40 -0700 Subject: Fix Hash syntax to work for both Ruby 2.1 and 2.3 --- app/views/devise/shared/_tabs_normal.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 48abd6519d6..79b1d447a92 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,5 +1,5 @@ %ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} %li.active{ role: 'presentation' } - %a{ href: '#login-pane', data: {'toggle':'tab'}, role: 'tab'} Sign in + %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in %li{ role: 'presentation'} - %a{ href: '#register-pane', data: {'toggle':'tab'}, role: 'tab'} Register + %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register -- cgit v1.2.1 From 4cea4d25270f604fb4b9335442657168b294e633 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 15 Oct 2016 20:40:25 -0700 Subject: Fix broken SCSS linter errors due to missing newlines --- app/assets/stylesheets/pages/login.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index e210dc4c0b8..4c0c0839fe2 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -40,6 +40,7 @@ p { font-size: 13px; } + .login-box, .omniauth-container { box-shadow: 0 0 0 1px $border-color; border-bottom-right-radius: 2px; @@ -118,6 +119,7 @@ margin: 0; } } + .new-session-tabs { display: -webkit-flex; display: flex; @@ -140,6 +142,7 @@ a { width: 100%; font-size: 18px; + &:hover { border: 1px solid transparent; } -- cgit v1.2.1 From 5174b7ad7e4b90761467388a29fa016d77e7a16d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 16 Oct 2016 11:35:51 +0200 Subject: Add the tech writers usernames in the doc_sytleguide doc [ci skip] --- doc/development/doc_styleguide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 0b725cf200c..47ebcebc770 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -95,10 +95,10 @@ merge request. someone in the Merge Request - When introducing a new document, be careful for the headings to be grammatically and syntactically correct. It is advised to mention one or all - of the following GitLab members for a review: `@axil`, `@rspeicher`, - `@dblessing`, `@ashleys`. This is to ensure that no document - with wrong heading is going live without an audit, thus preventing dead links - and redirection issues when corrected + of the following GitLab members for a review: `@axil`, `@rspeicher`, `@marcia`, + `@SeanPackham`. This is to ensure that no document with wrong heading is going + live without an audit, thus preventing dead links and redirection issues when + corrected - Leave exactly one newline after a heading ## Links -- cgit v1.2.1 From c48f7153181eab0d872d84b1c00e83cec5f2f046 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 12:49:23 +0300 Subject: Fix 404 when group path has dot in the name Signed-off-by: Dmitriy Zaporozhets --- config/routes/group.rb | 5 ++++- spec/routing/routing_spec.rb | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/config/routes/group.rb b/config/routes/group.rb index 33143f0dfa2..06b464d79c8 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -1,7 +1,10 @@ require 'constraints/group_url_constrainer' constraints(GroupUrlConstrainer.new) do - scope(path: ':id', as: :group, controller: :groups) do + scope(path: ':id', + as: :group, + constraints: { id: /[a-zA-Z.0-9_\-]+(? Date: Sun, 16 Oct 2016 15:03:44 +0300 Subject: Fix 500 error when creating mileston from group page Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/_init_auto_complete.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 67ff4b272b9..e138ebab018 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,7 +1,8 @@ - project = @target_project || @project - noteable_type = @noteable.class if @noteable.present? -:javascript - GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" - GitLab.GfmAutoComplete.cachedData = undefined; - GitLab.GfmAutoComplete.setup(); +- if project + :javascript + GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: noteable_type, type_id: params[:id])}" + GitLab.GfmAutoComplete.cachedData = undefined; + GitLab.GfmAutoComplete.setup(); -- cgit v1.2.1 From 7e3ff1852340c33f5b8853190d6a24813d5920a2 Mon Sep 17 00:00:00 2001 From: Ebrahim Byagowi Date: Sun, 11 Sep 2016 20:38:09 +0430 Subject: Add RTL support to markdown renderer --- CHANGELOG | 1 + app/assets/stylesheets/framework/typography.scss | 13 +++++++++++++ lib/banzai/filter/set_direction_filter.rb | 15 +++++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 4 +++- spec/features/atom/users_spec.rb | 2 +- spec/lib/banzai/object_renderer_spec.rb | 6 +++--- spec/lib/banzai/pipeline/description_pipeline_spec.rb | 12 +++++++++--- spec/models/concerns/cache_markdown_field_spec.rb | 2 +- 8 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 lib/banzai/filter/set_direction_filter.rb diff --git a/CHANGELOG b/CHANGELOG index e3201cd2250..76b483d9019 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,7 @@ v 8.13.0 (unreleased) - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Show the time ago a merge request was deployed to an environment + - Add RTL support to markdown renderer (Ebrahim Byagowi) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8df0067fac1..287653beac5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -90,6 +90,11 @@ border-left: 3px solid #e7e9ed; } + blockquote:dir(rtl) { + border-left: 0; + border-right: 3px solid #e7e9ed; + } + blockquote p { color: #7f8fa4 !important; font-size: inherit; @@ -112,6 +117,10 @@ } } + table:dir(rtl) th { + text-align: right; + } + pre { margin: 12px 0; font-size: 13px; @@ -129,6 +138,10 @@ margin: 3px 0 3px 28px !important; } + ul:dir(rtl), ol:dir(rtl) { + margin: 3px 28px 3px 0 !important; + } + li { line-height: 1.6em; } diff --git a/lib/banzai/filter/set_direction_filter.rb b/lib/banzai/filter/set_direction_filter.rb new file mode 100644 index 00000000000..c2976aeb7c6 --- /dev/null +++ b/lib/banzai/filter/set_direction_filter.rb @@ -0,0 +1,15 @@ +module Banzai + module Filter + # HTML filter that sets dir="auto" for RTL languages support + class SetDirectionFilter < HTML::Pipeline::Filter + def call + # select these elements just on top level of the document + doc.xpath('p|h1|h2|h3|h4|h5|h6|ol|ul[not(@class="section-nav")]|blockquote|table').each do |el| + el['dir'] = 'auto' + end + + doc + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 8d94b199c66..5da2d0b008c 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -25,7 +25,9 @@ module Banzai Filter::MilestoneReferenceFilter, Filter::TaskListFilter, - Filter::InlineDiffFilter + Filter::InlineDiffFilter, + + Filter::SetDirectionFilter ] end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index a8833194421..f8c3ccb416b 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -53,7 +53,7 @@ describe "User Feed", feature: true do end it 'has XHTML summaries in issue descriptions' do - expect(body).to match /we have a bug!<\/p>\n\n
    \n\n

    I guess/ + expect(body).to match /we have a bug!<\/p>\n\n


    \n\n

    I guess/ end it 'has XHTML summaries in notes' do diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 90da78a67dd..6bcda87c999 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -24,7 +24,7 @@ describe Banzai::ObjectRenderer do with(an_instance_of(Array)). and_call_original - expect(object).to receive(:redacted_note_html=).with('

    hello

    ') + expect(object).to receive(:redacted_note_html=).with('

    hello

    ') expect(object).to receive(:user_visible_reference_count=).with(0) renderer.render([object], :note) @@ -92,10 +92,10 @@ describe Banzai::ObjectRenderer do docs = renderer.render_attributes(objects, :note) expect(docs[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) - expect(docs[0].to_html).to eq('

    hello

    ') + expect(docs[0].to_html).to eq('

    hello

    ') expect(docs[1]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment) - expect(docs[1].to_html).to eq('

    bye

    ') + expect(docs[1].to_html).to eq('

    bye

    ') end it 'returns when no objects to render' do diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb index 76f42071810..8cce1b96698 100644 --- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb @@ -4,11 +4,11 @@ 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?('

    ') + unwrap = !html.start_with?('

    (.*)

    (.*)\z}, '\1\2') if unwrap + output.gsub!(%r{\A

    (.*)

    (.*)\z}, '\1\2') if unwrap output end @@ -27,11 +27,17 @@ describe Banzai::Pipeline::DescriptionPipeline do end end - %w(b i strong em a ins del sup sub p).each do |elem| + %w(b i strong em a ins del sup sub).each do |elem| it "still allows '#{elem}' elements" do exp = act = "<#{elem}>Description" expect(parse(act).strip).to eq exp end end + + it "still allows 'p' elements" do + exp = act = "

    Description

    " + + expect(parse(act).strip).to eq exp + end end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 15cd3a7ed70..2e3702f7520 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -64,7 +64,7 @@ describe CacheMarkdownField do let(:html) { "

    Foo

    " } let(:updated_markdown) { "`Bar`" } - let(:updated_html) { "

    Bar

    " } + let(:updated_html) { "

    Bar

    " } subject { ThingWithMarkdownFields.new(foo: markdown, foo_html: html) } -- cgit v1.2.1 From b1b197ae5e42c7f9a645f249bff28235bdba4c80 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 20:03:07 +0300 Subject: Wait for ajax call in merge request unsubscribe test Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/merge_requests.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index b0c33aa6f2f..de065dffbc2 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -7,6 +7,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps include SharedMarkdown include SharedDiffNote include SharedUser + include WaitForAjax step 'I click link "New Merge Request"' do click_link "New Merge Request" @@ -90,6 +91,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I click button "Unsubscribe"' do click_on "Unsubscribe" + wait_for_ajax end step 'I click link "Close"' do -- cgit v1.2.1 From 9e30b75e9c5ab91de955b6212df825f5e85c0e57 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 20:40:52 +0300 Subject: Fix merge requests feature tests Signed-off-by: Dmitriy Zaporozhets --- features/steps/shared/note.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index 3d7c6ef9d2d..d3b5b0bdebe 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -1,5 +1,8 @@ +require Rails.root.join('features/support/wait_for_ajax') + module SharedNote include Spinach::DSL + include WaitForAjax step 'I delete a comment' do page.within('.main-notes-list') do @@ -116,8 +119,9 @@ module SharedNote page.within(".js-main-target-form") do fill_in "note[note]", with: "# Comment with a header" click_button "Comment" - sleep 0.05 end + + wait_for_ajax end step 'The comment with the header should not have an ID' do -- cgit v1.2.1 From cd6af26de21b1d321306f69f5e0311d19e7c6b28 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sun, 16 Oct 2016 20:49:05 +0300 Subject: Fix active tab test for branches page Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/active_tab.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 80043463188..58225032859 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -54,7 +54,7 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps end step 'I click the "Branches" tab' do - page.within '.content' do + page.within '.sub-nav' do click_link('Branches') end end -- cgit v1.2.1 From 9f7a7115a4c7b90e0203516f5aabf913ce02cbfe Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 16 Oct 2016 20:29:45 +0200 Subject: Convert CHANGELOG to Markdown All this does is convert the version sections into headers. The list items shouldn't really be indented by two spaces, but it makes no difference to the rendering and this way we retain authorship history for the actual changes. Related to https://gitlab.com/gitlab-org/release-tools/merge_requests/29 --- .gitattributes | 2 +- CHANGELOG | 2335 ---------------------------------------- CHANGELOG.md | 2465 +++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- doc/workflow/gitlab_flow.md | 2 +- scripts/lint-doc.sh | 6 +- 6 files changed, 2471 insertions(+), 2341 deletions(-) delete mode 100644 CHANGELOG create mode 100644 CHANGELOG.md diff --git a/.gitattributes b/.gitattributes index 17cbaa5eef5..ab791a4cd6c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -CHANGELOG merge=union +CHANGELOG.md merge=union *.js.es6 gitlab-language=javascript diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index e3201cd2250..00000000000 --- a/CHANGELOG +++ /dev/null @@ -1,2335 +0,0 @@ -Please view this file on the master branch, on stable branches it's out of date. - -v 8.13.0 (unreleased) - - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - - Respond with 404 Not Found for non-existent tags (Linus Thiel) - - Truncate long labels with ellipsis in labels page - - Improve tabbing usability for sign in page (ClemMakesApps) - - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint - - Adding members no longer silently fails when there is extra whitespace - - Update runner version only when updating contacted_at - - Add link from system note to compare with previous version - - Use gitlab-shell v3.6.6 - - Add `/projects/visible` API endpoint (Ben Boeckel) - - Fix centering of custom header logos (Ashley Dumaine) - - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - - Updating verbiage on git basics to be more intuitive - - Clarify documentation for Runners API (Gennady Trafimenkov) - - The instrumentation for Banzai::Renderer has been restored - - Change user & group landing page routing from /u/:username to /:username - - Prevent running GfmAutocomplete setup for each diff note !6569 - - Added documentation for .gitattributes files - - AbstractReferenceFilter caches project_refs on RequestStore when active - - Replaced the check sign to arrow in the show build view. !6501 - - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - - Fix Error 500 when viewing old merge requests with bad diff data - - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - - Speed-up group milestones show page - - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService - - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - - Add tag shortcut from the Commit page. !6543 - - Keep refs for each deployment - - Allow browsing branches that end with '.atom' - - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) - - Add more tests for calendar contribution (ClemMakesApps) - - Update Gitlab Shell to fix some problems with moving projects between storages - - Cache rendered markdown in the database, rather than Redis - - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - - Simplify Mentionable concern instance methods - - API: Ability to retrieve version information (Robert Schilling) - - Fix permission for setting an issue's due date - - API: Multi-file commit !6096 (mahcsig) - - Unicode emoji are now converted to images - - Revert "Label list shows all issues (opened or closed) with that label" - - Expose expires_at field when sharing project on API - - Fix VueJS template tags being rendered in code comments - - Added copy file path button to merge request diff files - - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - - Add Issue Board API support (andrebsguedes) - - Allow the Koding integration to be configured through the API - - Add new issue button to each list on Issues Board - - Added soft wrap button to repository file/blob editor - - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - - Show the time ago a merge request was deployed to an environment - - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - - Fix todos page mobile viewport layout (ClemMakesApps) - - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - - Remove redundant mixins (ClemMakesApps) - - Added 'Download' button to the Snippets page (Justin DiPierro) - - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - - Fix that manual jobs would no longer block jobs in the next stage. !6604 - - Add configurable email subject suffix (Fu Xu) - - Use defined colour for a language when available !6748 (nilsding) - - Added tooltip to fork count on project show page. (Justin DiPierro) - - Use a ConnectionPool for Rails.cache on Sidekiq servers - - Replace `alias_method_chain` with `Module#prepend` - - Enable GitLab Import/Export for non-admin users. - - Preserve label filters when sorting !6136 (Joseph Frazier) - - MergeRequest#new form load diff asynchronously - - Only update issuable labels if they have been changed - - Take filters in account in issuable counters. !6496 - - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - - Prevent flash alert text from being obscured when container is fluid - - Append issue template to existing description !6149 (Joseph Frazier) - - Trending projects now only show public projects and the list of projects is cached for a day - - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) - - Revoke button in Applications Settings underlines on hover. - - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - - Fix Long commit messages overflow viewport in file tree - - Revert avoid touching file system on Build#artifacts? - - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - - Add disabled delete button to protected branches (ClemMakesApps) - - Add broadcast messages and alerts below sub-nav - - Better empty state for Groups view - - API: New /users/:id/events endpoint - - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - - Add organization field to user profile - - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages - - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - - Fix deploy status responsiveness error !6633 - - Make searching for commits case insensitive - - Fix resolved discussion display in side-by-side diff view !6575 - - Optimize GitHub importing for speed and memory - - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - - Notify the Merger about merge after successful build (Dimitris Karakasilis) - - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - - Reduce queries needed to find users using their SSH keys when pushing commits - - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - - Fix broken repository 500 errors in project list - - Fix Pipeline list commit column width should be adjusted - - Close todos when accepting merge requests via the API !6486 (tonygambone) - - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - - Retouch environments list and deployments list - - Add multiple command support for all label related slash commands !6780 (barthc) - - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - - Allow empty merge requests !6384 (Artem Sidorenko) - - Grouped pipeline dropdown is a scrollable container - - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - - Fixes padding in all clipboard icons that have .btn class - - Fix a typo in doc/api/labels.md - - API: all unknown routing will be handled with 404 Not Found - - Add docs for request profiling - - Make guests unable to view MRs on private projects - -v 8.12.7 - - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 - - Fix GFM autocomplete setup being called several times - -v 8.12.6 - - Update mailroom to 0.8.1 in Gemfile.lock !6814 - -v 8.12.5 - - Switch from request to env in ::API::Helpers. !6615 - - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 - - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 - - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - - Don't send Private-Token (API authentication) headers to Sentry - - Share projects via the API only with groups the authenticated user can access - -v 8.12.4 - - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) - - Fix padding in build sidebar. !6506 - - Changed compare dropdowns to dropdowns with isolated search input. !6550 - - Fix race condition on LFS Token. !6592 - - Fix type mismatch bug when closing Jira issue. !6619 - - Fix lint-doc error. !6623 - - Skip wiki creation when GitHub project has wiki enabled. !6665 - - Fix issues importing services via Import/Export. !6667 - - Restrict failed login attempts for users with 2FA enabled. !6668 - - Fix failed project deletion when feature visibility set to private. !6688 - - Prevent claiming associated model IDs via import. - - Set GitLab project exported file permissions to owner only - - Improve the way merge request versions are compared with each other - -v 8.12.3 - - Update Gitlab Shell to support low IO priority for storage moves - -v 8.12.2 - - Fix Import/Export not recognising correctly the imported services. - - Fix snippets pagination - - Fix "Create project" button layout when visibility options are restricted - - Fix List-Unsubscribe header in emails - - Fix IssuesController#show degradation including project on loaded notes - - Fix an issue with the "Commits" section of the cycle analytics summary. !6513 - - Fix errors importing project feature and milestone models using GitLab project import - - Make JWT messages Docker-compatible - - Fix duplicate branch entry in the merge request version compare dropdown - - Respect the fork_project permission when forking projects - - Only update issuable labels if they have been changed - - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - - Fix resolve discussion buttons endpoint path - - Refactor remnants of CoffeeScript destructured opts and super !6261 - -v 8.12.1 - - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - - Fix issue with search filter labels not displaying - -v 8.12.0 - - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - - Only check :can_resolve permission if the note is resolvable - - Bump fog-aws to v0.11.0 to support ap-south-1 region - - Add ability to fork to a specific namespace using API. (ritave) - - Allow to set request_access_enabled for groups and projects - - Cleanup misalignments in Issue list view !6206 - - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist - - Add Pipelines for Commit - - Prune events older than 12 months. (ritave) - - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - - Fix issues/merge-request templates dropdown for forked projects - - Filter tags by name !6121 - - Update gitlab shell secret file also when it is empty. !3774 (glensc) - - Give project selection dropdowns responsive width, make non-wrapping. - - Fix note form hint showing slash commands supported for commits. - - Make push events have equal vertical spacing. - - API: Ensure invitees are not returned in Members API. - - Preserve applied filters on issues search. - - Add two-factor recovery endpoint to internal API !5510 - - Pass the "Remember me" value to the U2F authentication form - - Display stages in valid order in stages dropdown on build page - - Only update projects.last_activity_at once per hour when creating a new event - - Cycle analytics (first iteration) !5986 - - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - - Move pushes_since_gc from the database to Redis - - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags - - Add font color contrast to external label in admin area (ClemMakesApps) - - Fix find file navigation links (ClemMakesApps) - - Change logo animation to CSS (ClemMakesApps) - - Instructions for enabling Git packfile bitmaps !6104 - - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - - Fix long comments in diffs messing with table width - - Add spec covering 'Gitlab::Git::committer_hash' !6433 (dandunckelman) - - Fix pagination on user snippets page - - Honor "fixed layout" preference in more places !6422 - - Run CI builds with the permissions of users !5735 - - Fix sorting of issues in API - - Fix download artifacts button links !6407 - - Sort project variables by key. !6275 (Diego Souza) - - Ensure specs on sorting of issues in API are deterministic on MySQL - - Added ability to use predefined CI variables for environment name - - Added ability to specify URL in environment configuration in gitlab-ci.yml - - Escape search term before passing it to Regexp.new !6241 (winniehell) - - Fix pinned sidebar behavior in smaller viewports !6169 - - Fix file permissions change when updating a file on the Gitlab UI !5979 - - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev) - - Change merge_error column from string to text type - - Fix issue with search filter labels not displaying - - Reduce contributions calendar data payload (ClemMakesApps) - - Show all pipelines for merge requests even from discarded commits !6414 - - Replace contributions calendar timezone payload with dates (ClemMakesApps) - - Changed MR widget build status to pipeline status !6335 - - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - - Enable pipeline events by default !6278 - - Move parsing of sidekiq ps into helper !6245 (pascalbetz) - - Added go to issue boards keyboard shortcut - - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - - Emoji can be awarded on Snippets !4456 - - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - - Fix blame table layout width - - Spec testing if issue authors can read issues on private projects - - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - - Request only the LDAP attributes we need !6187 - - Center build stage columns in pipeline overview (ClemMakesApps) - - Fix bug with tooltip not hiding on discussion toggle button - - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - - Fix bug stopping issue description being scrollable after selecting issue template - - Remove suggested colors hover underline (ClemMakesApps) - - Fix jump to discussion button being displayed on commit notes - - Shorten task status phrase (ClemMakesApps) - - Fix project visibility level fields on settings - - Add hover color to emoji icon (ClemMakesApps) - - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - - Add textarea autoresize after comment (ClemMakesApps) - - Do not write SSH public key 'comments' to authorized_keys !6381 - - Add due date to issue todos - - Refresh todos count cache when an Issue/MR is deleted - - Fix branches page dropdown sort alignment (ClemMakesApps) - - Hides merge request button on branches page is user doesn't have permissions - - Add white background for no readme container (ClemMakesApps) - - API: Expose issue confidentiality flag. (Robert Schilling) - - Fix markdown anchor icon interaction (ClemMakesApps) - - Test migration paths from 8.5 until current release !4874 - - Replace animateEmoji timeout with eventListener (ClemMakesApps) - - Show badges in Milestone tabs. !5946 (Dan Rowden) - - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) - - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - - Remove Gitorious import - - Loads GFM autocomplete source only when required - - Fix issue with slash commands not loading on new issue page - - Fix inconsistent background color for filter input field (ClemMakesApps) - - Remove prefixes from transition CSS property (ClemMakesApps) - - Add Sentry logging to API calls - - Add BroadcastMessage API - - Merge request tabs are fixed when scrolling page - - Use 'git update-ref' for safer web commits !6130 - - Sort pipelines requested through the API - - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - - Fix issue boards loading on large screens - - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - - Show queued time when showing a pipeline !6084 - - Remove unused mixins (ClemMakesApps) - - Fix issue board label filtering appending already filtered labels - - Add search to all issue board lists - - Scroll active tab into view on mobile - - Fix groups sort dropdown alignment (ClemMakesApps) - - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - - Use JavaScript tooltips for mentions !5301 (winniehell) - - Add hover state to todos !5361 (winniehell) - - Fix icon alignment of star and fork buttons !5451 (winniehell) - - Fix alignment of icon buttons !5887 (winniehell) - - Added Ubuntu 16.04 support for packager.io (JonTheNiceGuy) - - Fix markdown help references (ClemMakesApps) - - Add last commit time to repo view (ClemMakesApps) - - Fix accessibility and visibility of project list dropdown button !6140 - - Fix missing flash messages on service edit page (airatshigapov) - - Added project-specific enable/disable setting for LFS !5997 - - Added group-specific enable/disable setting for LFS !6164 - - Add optional 'author' param when making commits. !5822 (dandunckelman) - - Don't expose a user's token in the `/api/v3/user` API (!6047) - - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - - Ability to manage project issues, snippets, wiki, merge requests and builds access level - - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) - - Align add button on repository view (ClemMakesApps) - - Fix contributions calendar month label truncation (ClemMakesApps) - - Import release note descriptions from GitHub (EspadaV8) - - Added tests for diff notes - - Add pipeline events to Slack integration !5525 - - Add a button to download latest successful artifacts for branches and tags !5142 - - Remove redundant pipeline tooltips (ClemMakesApps) - - Expire commit info views after one day, instead of two weeks, to allow for user email updates - - Add delimiter to project stars and forks count (ClemMakesApps) - - Fix badge count alignment (ClemMakesApps) - - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - - Fix repo title alignment (ClemMakesApps) - - Change update interval of contacted_at - - Add LFS support to SSH !6043 - - Fix branch title trailing space on hover (ClemMakesApps) - - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - - Order award emoji tooltips in order they were added (EspadaV8) - - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - - Update merge_requests.md with a simpler way to check out a merge request. !5944 - - Fix button missing type (ClemMakesApps) - - Gitlab::Checks is now instrumented - - Move to project dropdown with infinite scroll for better performance - - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) - - Load branches asynchronously in Cherry Pick and Revert dialogs. - - Convert datetime coffeescript spec to ES6 (ClemMakesApps) - - Add merge request versions !5467 - - Change using size to use count and caching it for number of group members. !5935 - - Replace play icon font with svg (ClemMakesApps) - - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - - Reduce number of database queries on builds tab - - Wrap text in commit message containers - - Capitalize mentioned issue timeline notes (ClemMakesApps) - - Fix inconsistent checkbox alignment (ClemMakesApps) - - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - - Adds response mime type to transaction metric action when it's not HTML - - Fix hover leading space bug in pipeline graph !5980 - - Avoid conflict with admin labels when importing GitHub labels - - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - - Fix repository page ui issues - - Avoid protected branches checks when verifying access without branch name - - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) - - Fixed invisible scroll controls on build page on iPhone - - Fix error on raw build trace download for old builds stored in database !4822 - - Refactor the triggers page and documentation !6217 - - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) - - Use default clone protocol on "check out, review, and merge locally" help page URL - - Let the user choose a namespace and name on GitHub imports - - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - - Allow bulk update merge requests from merge requests index page - - Ensure validation messages are shown within the milestone form - - Add notification_settings API calls !5632 (mahcsig) - - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - - Fix URLs with anchors in wiki !6300 (houqp) - - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) - - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 - - Fix Gitlab::Popen.popen thread-safety issue - - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - - Clean environment variables when running git hooks - - Fix Import/Export issues importing protected branches and some specific models - - Fix non-master branch readme display in tree view - - Add UX improvements for merge request version diffs - -v 8.11.9 - - Don't send Private-Token (API authentication) headers to Sentry - - Share projects via the API only with groups the authenticated user can access - -v 8.11.8 - - Respect the fork_project permission when forking projects - - Set a restrictive CORS policy on the API for credentialed requests - - API: disable rails session auth for non-GET/HEAD requests - - Escape HTML nodes in builds commands in CI linter - -v 8.11.7 - - Avoid conflict with admin labels when importing GitHub labels. !6158 - - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - - Allow the Rails cookie to be used for API authentication. - - Login/Register UX upgrade !6328 - -v 8.11.6 - - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 - - Make merge conflict file size limit 200 KB, to match the docs. !6052 - - Fix an error where we were unable to create a CommitStatus for running state. !6107 - - Optimize discussion notes resolving and unresolving. !6141 - - Fix GitLab import button. !6167 - - Restore SSH Key title auto-population behavior. !6186 - - Fix DB schema to match latest migration. !6256 - - Exclude some pending or inactivated rows in Member scopes. - -v 8.11.5 - - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 - - Fix member expiration date picker after update. !6184 - - Fix suggested colors options for new labels in the admin area. !6138 - - Optimize discussion notes resolving and unresolving - - Fix GitLab import button - - Fix confidential issues being exposed as public using gitlab.com export - - Remove gitorious from import_sources. !6180 - - Scope webhooks/services that will run for confidential issues - - Remove gitorious from import_sources - - Fix confidential issues being exposed as public using gitlab.com export - - Use oj gem for faster JSON processing - -v 8.11.4 - - Fix resolving conflicts on forks. !6082 - - Fix diff commenting on merge requests created prior to 8.10. !6029 - - Fix pipelines tab layout regression. !5952 - - Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057 - - Do not enforce using hash with hidden key in CI configuration. !6079 - - Fix hover leading space bug in pipeline graph !5980 - - Fix sorting issues by "last updated" doesn't work after import from GitHub - - GitHub importer use default project visibility for non-private projects - - Creating an issue through our API now emails label subscribers !5720 - - Block concurrent updates for Pipeline - - Don't create groups for unallowed users when importing projects - - Fix issue boards leak private label names and descriptions - - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - - Remove gitorious. !5866 - - Allow compare merge request versions - -v 8.11.3 - - Allow system info page to handle case where info is unavailable - - Label list shows all issues (opened or closed) with that label - - Don't show resolve conflicts link before MR status is updated - - Fix IE11 fork button bug !5982 - - Don't prevent viewing the MR when git refs for conflicts can't be found on disk - - Fix external issue tracker "Issues" link leading to 404s - - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters - - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - - Issues filters reset button - -v 8.11.2 - - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - - Use gitlab-workhorse 0.7.11 !5983 - - Does not halt the GitHub import process when an error occurs. !5763 - - Fix file links on project page when default view is Files !5933 - - Fixed enter key in search input not working !5888 - -v 8.11.1 - - Pulled due to packaging error. - -v 8.11.0 - - Use test coverage value from the latest successful pipeline in badge. !5862 - - Add test coverage report badge. !5708 - - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) - - Add Koding (online IDE) integration - - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) - - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - - Fix adding line comments on the initial commit to a repo !5900 - - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz) - - Update to Ruby 2.3.1. !4948 - - Add Issues Board !5548 - - Allow resolving merge conflicts in the UI !5479 - - Improve diff performance by eliminating redundant checks for text blobs - - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi) - - Convert switch icon into icon font (ClemMakesApps) - - API: Endpoints for enabling and disabling deploy keys - - API: List access requests, request access, approve, and deny access requests to a project or a group. !4833 - - Use long options for curl examples in documentation !5703 (winniehell) - - Added tooltip listing label names to the labels value in the collapsed issuable sidebar - - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository - - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - - Allow naming U2F devices !5833 - - Ignore URLs starting with // in Markdown links !5677 (winniehell) - - Fix CI status icon link underline (ClemMakesApps) - - The Repository class is now instrumented - - Fix commit mention font inconsistency (ClemMakesApps) - - Do not escape URI when extracting path !5878 (winniehell) - - Fix filter label tooltip HTML rendering (ClemMakesApps) - - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - - Expand commit message width in repo view (ClemMakesApps) - - Cache highlighted diff lines for merge requests - - Pre-create all builds for a Pipeline when the new Pipeline is created !5295 - - Allow merge request diff notes and discussions to be explicitly marked as resolved - - API: Add deployment endpoints - - API: Add Play endpoint on Builds - - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - - Show wall clock time when showing a pipeline. !5734 - - Show member roles to all users on members page - - Project.visible_to_user is instrumented again - - Fix awardable button mutuality loading spinners (ClemMakesApps) - - Sort todos by date and priority - - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - - Optimize maximum user access level lookup in loading of notes - - Send notification emails to users newly mentioned in issue and MR edits !5800 - - Add "No one can push" as an option for protected branches. !5081 - - Improve performance of AutolinkFilter#text_parse by using XPath - - Add experimental Redis Sentinel support !1877 - - Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB - - Fix branches page dropdown sort initial state (ClemMakesApps) - - Environments have an url to link to - - Various redundant database indexes have been removed - - Update `timeago` plugin to use multiple string/locale settings - - Remove unused images (ClemMakesApps) - - Get issue and merge request description templates from repositories - - Enforce 2FA restrictions on API authentication endpoints !5820 - - Limit git rev-list output count to one in forced push check - - Show deployment status on merge requests with external URLs - - Clean up unused routes (Josef Strzibny) - - Fix issue on empty project to allow developers to only push to protected branches if given permission - - API: Add enpoints for pipelines - - Add green outline to New Branch button. !5447 (winniehell) - - Optimize generating of cache keys for issues and notes - - Fix repository push email formatting in Outlook - - Improve performance of syntax highlighting Markdown code blocks - - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - - Remove delay when hitting "Reply..." button on page with a lot of discussions - - Retrieve rendered HTML from cache in one request - - Fix renaming repository when name contains invalid chararacters under project settings - - Upgrade Grape from 0.13.0 to 0.15.0. !4601 - - Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries - - Fix devise deprecation warnings. - - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 - - Update version_sorter and use new interface for faster tag sorting - - Optimize checking if a user has read access to a list of issues !5370 - - Store all DB secrets in secrets.yml, under descriptive names !5274 - - Fix syntax highlighting in file editor - - Support slash commands in issue and merge request descriptions as well as comments. !5021 - - Nokogiri's various parsing methods are now instrumented - - Add archived badge to project list !5798 - - Add simple identifier to public SSH keys (muteor) - - Admin page now references docs instead of a specific file !5600 (AnAverageHuman) - - Fix filter input alignment (ClemMakesApps) - - Include old revision in merge request update hooks (Ben Boeckel) - - Add build event color in HipChat messages (David Eisner) - - Make fork counter always clickable. !5463 (winniehell) - - Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon) - - Gitlab::Highlight is now instrumented - - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - - Allow users to import cross-repository pull requests from GitHub - - The overhead of instrumented method calls has been reduced - - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - - Add pipeline events hook - - Bump gitlab_git to speedup DiffCollection iterations - - Rewrite description of a blocked user in admin settings. (Elias Werberich) - - Make branches sortable without push permission !5462 (winniehell) - - Check for Ci::Build artifacts at database level on pipeline partial - - Convert image diff background image to CSS (ClemMakesApps) - - Remove unnecessary index_projects_on_builds_enabled index from the projects table - - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - - Fix search for notes which belongs to deleted objects - - Allow Akismet to be trained by submitting issues as spam or ham !5538 - - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - - Allow branch names ending with .json for graph and network page !5579 (winniehell) - - Add the `sprockets-es6` gem - - Improve OAuth2 client documentation (muteor) - - Fix diff comments inverted toggle bug (ClemMakesApps) - - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - - Profile requests when a header is passed - - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - - Add commit stats in commit api. !5517 (dixpac) - - Add CI configuration button on project page - - Fix merge request new view not changing code view rendering style - - edit_blob_link will use blob passed onto the options parameter - - Make error pages responsive (Takuya Noguchi) - - The performance of the project dropdown used for moving issues has been improved - - Fix skip_repo parameter being ignored when destroying a namespace - - Add all builds into stage/job dropdowns on builds page - - Change requests_profiles resource constraint to catch virtually any file - - Bump gitlab_git to lazy load compare commits - - Reduce number of queries made for merge_requests/:id/diffs - - Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski) - - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - - Fix bug where destroying a namespace would not always destroy projects - - Fix RequestProfiler::Middleware error when code is reloaded in development - - Allow horizontal scrolling of code blocks in issue body - - Catch what warden might throw when profiling requests to re-throw it - - Avoid commit lookup on diff_helper passing existing local variable to the helper method - - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac) - - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker - - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) - - Adds support for pending invitation project members importing projects - - Add pipeline visualization/graph on pipeline page - - Update devise initializer to turn on changed password notification emails. !5648 (tombell) - - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro) - - Fix importing GitLab projects with an invalid MR source project - - Sort folders with submodules in Files view !5521 - - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0 - - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska) - - Add pipelines tab to merge requests - - Fix notification_service argument error of declined invitation emails - - Fix a memory leak caused by Banzai::Filter::SanitizationFilter - - Speed up todos queries by limiting the projects set we join with - - Ensure file editing in UI does not overwrite commited changes without warning user - - Eliminate unneeded calls to Repository#blob_at when listing commits with no path - - Update gitlab_git gem to 10.4.7 - - Simplify SQL queries of marking a todo as done - -v 8.10.12 - - Don't send Private-Token (API authentication) headers to Sentry - - Share projects via the API only with groups the authenticated user can access - -v 8.10.11 - - Respect the fork_project permission when forking projects - - Set a restrictive CORS policy on the API for credentialed requests - - API: disable rails session auth for non-GET/HEAD requests - - Escape HTML nodes in builds commands in CI linter - -v 8.10.10 - - Allow the Rails cookie to be used for API authentication. - -v 8.10.9 - - Exclude some pending or inactivated rows in Member scopes - -v 8.10.8 - - Fix information disclosure in issue boards. - - Fix privilege escalation in project import. - -v 8.10.7 - - Upgrade Hamlit to 2.6.1. !5873 - - Upgrade Doorkeeper to 4.2.0. !5881 - -v 8.10.6 - - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - - Restore "Largest repository" sort option on Admin > Projects page. !5797 - - Fix privilege escalation via project export. - - Require administrator privileges to perform a project import. - -v 8.10.5 - - Add a data migration to fix some missing timestamps in the members table. !5670 - - Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706 - - Cache project count for 5 minutes to reduce DB load. !5746 & !5754 - -v 8.10.4 - - Don't close referenced upstream issues from a forked project. - - Fixes issue with dropdowns `enter` key not working correctly. !5544 - - Fix Import/Export project import not working in HA mode. !5618 - - Fix Import/Export error checking versions. !5638 - -v 8.10.3 - - Fix Import/Export issue importing milestones and labels not associated properly. !5426 - - Fix timing problems running imports on production. !5523 - - Add a log message when a project is scheduled for destruction for debugging. !5540 - - Fix hooks missing on imported GitLab projects. !5549 - - Properly abort a merge when merge conflicts occur. !5569 - - Fix importer for GitHub Pull Requests when a branch was removed. !5573 - - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584 - - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 - - Fix label already exist error message in the right sidebar. - -v 8.10.2 - - User can now search branches by name. !5144 - - Page is now properly rendered after committing the first file and creating the first branch. !5399 - - Add branch or tag icon to ref in builds page. !5434 - - Fix backup restore. !5459 - - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 - - Fix issue with autocomplete search not working with enter key. !5466 - - Add iid to MR API response. !5468 - - Disable MySQL foreign key checks before dropping all tables. !5472 - - Ensure relative paths for video are rewritten as we do for images. !5474 - - Ensure current user can retry a build before showing the 'Retry' button. !5476 - - Add ENV variable to skip repository storages validations. !5478 - - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486 - - Don't show comment button in gutter of diffs on MR discussion tab. !5493 - - Rescue Rugged::OSError (lock exists) when creating references. !5497 - - Fix expand all diffs button in compare view. !5500 - - Show release notes in tags list. !5503 - - Fix a bug where forking a project from a repository storage to another would fail. !5509 - - Fix missing schema update for `20160722221922`. !5512 - - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516 - -v 8.10.1 - - Refactor repository storages documentation. !5428 - - Gracefully handle case when keep-around references are corrupted or exist already. !5430 - - Add detailed info on storage path mountpoints. !5437 - - Fix Error 500 when creating Wiki pages with hyphens or spaces. !5444 - - Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446 - - Ignore invalid trusted proxies in X-Forwarded-For header. !5454 - - Add links to the real markdown.md file for all GFM examples. !5458 - -v 8.10.0 - - Fix profile activity heatmap to show correct day name (eanplatter) - - Speed up ExternalWikiHelper#get_project_wiki_path - - Expose {should,force}_remove_source_branch (Ben Boeckel) - - Add the functionality to be able to rename a file. !5049 - - Disable PostgreSQL statement timeout during migrations - - Fix projects dropdown loading performance with a simplified api cal. !5113 - - Fix commit builds API, return all builds for all pipelines for given commit. !4849 - - Replace Haml with Hamlit to make view rendering faster. !3666 - - Refresh the branch cache after `git gc` runs - - Allow to disable request access button on projects/groups - - Refactor repository paths handling to allow multiple git mount points - - Optimize system note visibility checking by memoizing the visible reference count. !5070 - - Add Application Setting to configure default Repository Path for new projects - - Delete award emoji when deleting a user - - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell) - - Add an API for downloading latest successful build from a particular branch or tag. !5347 - - Avoid data-integrity issue when cleaning up repository archive cache. - - Add link to profile to commit avatar. !5163 (winniehell) - - Wrap code blocks on Activies and Todos page. !4783 (winniehell) - - Align flash messages with left side of page content. !4959 (winniehell) - - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell) - - Use default cursor for table header of project files. !5165 (winniehell) - - Store when and yaml variables in builds table - - Display last commit of deleted branch in push events. !4699 (winniehell) - - Escape file extension when parsing search results. !5141 (winniehell) - - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 - - Add image border in Markdown preview. !5162 (winniehell) - - Apply the trusted_proxies config to the rack request object for use with rack_attack - - Added the ability to block sign ups using a domain blacklist. !5259 - - Upgrade to Rails 4.2.7. !5236 - - Extend exposed environment variables for CI builds - - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead - - Add API "deploy_keys" for admins to get all deploy keys - - Allow to pull code with deploy key from public projects - - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) - - Add Sidekiq queue duration to transaction metrics. - - Add a new column `artifacts_size` to table `ci_builds`. !4964 - - Let Workhorse serve format-patch diffs - - Display tooltip for mentioned users and groups. !5261 (winniehell) - - Allow build email service to be tested - - Added day name to contribution calendar tooltips - - Refactor user authorization check for a single project to avoid querying all user projects - - Make images fit to the size of the viewport. !4810 - - Fix check for New Branch button on Issue page. !4630 (winniehell) - - Fix GFM autocomplete not working on wiki pages - - Fixed enter key not triggering click on first row when searching in a dropdown - - Updated dropdowns in issuable form to use new GitLab dropdown style - - Make images fit to the size of the viewport !4810 - - Fix check for New Branch button on Issue page !4630 (winniehell) - - Fix MR-auto-close text added to description. !4836 - - Support U2F devices in Firefox. !5177 - - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) - - Add Spring EmojiOne updates. - - Added Rake task for tracking deployments. !5320 - - Fix fetching LFS objects for private CI projects - - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 - - Add syntax for multiline blockquote using `>>>` fence. !3954 - - Fix viewing notification settings when a project is pending deletion - - Updated compare dropdown menus to use GL dropdown - - Redirects back to issue after clicking login link - - Eager load award emoji on notes - - Allow to define manual actions/builds on Pipelines and Environments - - Fix pagination when sorting by columns with lots of ties (like priority) - - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 - - Updated project header design - - Issuable collapsed assignee tooltip is now the users name - - Fix compare view not changing code view rendering style - - Exclude email check from the standard health check - - Updated layout for Projects, Groups, Users on Admin area. !4424 - - Fix changing issue state columns in milestone view - - Update health_check gem to version 2.1.0 - - Add notification settings dropdown for groups - - Render inline diffs for multiple changed lines following eachother - - Wildcards for protected branches. !4665 - - Allow importing from Github using Personal Access Tokens. (Eric K Idema) - - API: Expose `due_date` for issues (Robert Schilling) - - API: Todos. !3188 (Robert Schilling) - - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling) - - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling) - - Add "Enabled Git access protocols" to Application Settings - - Diffs will create button/diff form on demand no on server side - - Reduce size of HTML used by diff comment forms - - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard) - - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt) - - Only show New Snippet button to users that can create snippets. - - PipelinesFinder uses git cache data - - Track a user who created a pipeline - - Actually render old and new sections of parallel diff next to each other - - Throttle the update of `project.pushes_since_gc` to 1 minute. - - Allow expanding and collapsing files in diff view. !4990 - - Collapse large diffs by default (!4990) - - Fix mentioned users list on diff notes - - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes) - - Fix creation of deployment on build that is retried, redeployed or rollback - - Don't parse Rinku returned value to DocFragment when it didn't change the original html string. - - Check for conflicts with existing Project's wiki path when creating a new project. - - Show last push widget in upstream after push to fork - - Fix stage status shown for pipelines - - Cache todos pending/done dashboard query counts. - - Don't instantiate a git tree on Projects show default view - - Bump Rinku to 2.0.0 - - Remove unused front-end variable -> default_issues_tracker - - ObjectRenderer retrieve renderer content using Rails.cache.read_multi - - Better caching of git calls on ProjectsController#show. - - Avoid to retrieve MR closes_issues as much as possible. - - Hide project name in project activities. !5068 (winniehell) - - Add API endpoint for a group issues. !4520 (mahcsig) - - Add Bugzilla integration. !4930 (iamtjg) - - Fix new snippet style bug (elliotec) - - Instrument Rinku usage - - Be explicit to define merge request discussion variables - - Use cache for todos counter calling TodoService - - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab - - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. - - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) - - Made project list visibility icon fixed width - - Set import_url validation to be more strict - - Memoize MR merged/closed events retrieval - - Don't render discussion notes when requesting diff tab through AJAX - - Add basic system information like memory and disk usage to the admin panel - - Don't garbage collect commits that have related DB records like comments - - Allow to setup event by channel on slack service - - More descriptive message for git hooks and file locks - - Aliases of award emoji should be stored as original name. !5060 (dixpac) - - Handle custom Git hook result in GitLab UI - - Allow to access Container Registry for Public and Internal projects - - Allow '?', or '&' for label names - - Support redirected blobs for Container Registry integration - - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - - Add date when user joined the team on the member page - - Fix 404 redirect after validation fails importing a GitLab project - - Added setting to set new users by default as external. !4545 (Dravere) - - Add min value for project limit field on user's form. !3622 (jastkand) - - Reset project pushes_since_gc when we enqueue the git gc call - - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt) - - Collapsed diffs lines/size don't acumulate to overflow diffs. - - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) - - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) - - Fix GitHub client requests when rate limit is disabled - - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention) - - Redesign Builds and Pipelines pages - - Change status color and icon for running builds - - Fix commenting issue in side by side diff view for unchanged lines - - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` - - Project export filename now includes the project and namespace path - - Fix last update timestamp on issues not preserved on gitlab.com and project imports - - Fix issues importing projects from EE to CE - - Fix creating group with space in group path - - Improve cron_jobs loading error messages. !5318 / !5360 - - Prevent toggling sidebar when clipboard icon clicked - - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) - - Limit the number of retries on error to 3 for exporting projects - - Allow empty repositories on project import/export - - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) - - Allow bulk (un)subscription from issues in issue index - - Fix MR diff encoding issues exporting GitLab projects - - Move builds settings out of project settings and rename Pipelines - - Add builds badge to Pipelines settings page - - Export and import avatar as part of project import/export - - Fix migration corrupting import data for old version upgrades - - Show tooltip on GitLab export link in new project page - - Fix import_data wrongly saved as a result of an invalid import_url !5206 - -v 8.9.11 - - Respect the fork_project permission when forking projects - - Set a restrictive CORS policy on the API for credentialed requests - - API: disable rails session auth for non-GET/HEAD requests - - Escape HTML nodes in builds commands in CI linter - -v 8.9.10 - - Allow the Rails cookie to be used for API authentication. - -v 8.9.9 - - Exclude some pending or inactivated rows in Member scopes - -v 8.9.8 - - Upgrade Doorkeeper to 4.2.0. !5881 - -v 8.9.7 - - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - - Require administrator privileges to perform a project import. - -v 8.9.6 - - Fix importing of events under notes for GitLab projects. !5154 - - Fix log statements in import/export. !5129 - - Fix commit avatar alignment in compare view. !5128 - - Fix broken migration in MySQL. !5005 - - Overwrite Host and X-Forwarded-Host headers in NGINX !5213 - - Keeps issue number when importing from Gitlab.com - - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) - -v 8.9.5 - - Add more debug info to import/export and memory killer. !5108 - - Fixed avatar alignment in new MR view. !5095 - - Fix diff comments not showing up in activity feed. !5069 - - Add index on both Award Emoji user and name. !5061 - - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 - - Re-enable import button when import process fails due to namespace already being taken. !5053 - - Fix snippets comments not displayed. !5045 - - Fix emoji paths in relative root configurations. !5027 - - Fix issues importing events in Import/Export. !4987 - - Fixed 'use shortcuts' button on docs. !4979 - - Admin should be able to turn shared runners into specific ones. !4961 - - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) - - Improve the request / withdraw access button. !4860 - -v 8.9.4 - - Fix privilege escalation issue with OAuth external users. - - Ensure references to private repos aren't shown to logged-out users. - - Fixed search field blur not removing focus. !4704 - - Resolve "Sub nav isn't showing on file view". !4890 - - Fixes middle click and double request when navigating through the file browser. !4891 - - Fixed URL on label button when filtering. !4897 - - Fixed commit avatar alignment. !4933 - - Do not show build retry link when build is active. !4967 - - Fix restore Rake task warning message output. !4980 - - Handle external issues in IssueReferenceFilter. !4988 - - Expiry date on pinned nav cookie. !5009 - - Updated breakpoint for sidebar pinning. !5019 - -v 8.9.3 - - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 - - Fix rendering of commit notes. !4953 - - Resolve "Pin should show up at 1280px min". !4947 - - Switched mobile button icons to ellipsis and angle. !4944 - - Correctly returns todo ID after creating todo. !4941 - - Better debugging for memory killer middleware. !4936 - - Remove duplicate new page btn from edit wiki. !4904 - - Use clock_gettime for all performance timestamps. !4899 - - Use memorized tags array when searching tags by name. !4859 - - Fixed avatar alignment in new MR view. !4901 - - Removed fade when filtering results. !4932 - - Fix missing avatar on system notes. !4954 - - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 - - Use update_columns to bypass all the dirty code on active_record. !4985 - - Fix restore Rake task warning message output !4980 - -v 8.9.2 - - Fix visibility of snippets when searching. - - Fix an information disclosure when requesting access to a group containing private projects. - - Update omniauth-saml to 1.6.0 !4951 - -v 8.9.1 - - Refactor labels documentation. !3347 - - Eager load award emoji on notes. !4628 - - Fix some CI wording in documentation. !4660 - - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720 - - Add documentation for the export & import features. !4732 - - Add some docs for Docker Registry configuration. !4738 - - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744 - - Display group/project access requesters separately in the admin area. !4798 - - Add documentation and examples for configuring cloud storage for registry images. !4812 - - Clarifies documentation about artifact expiry. !4831 - - Fix the Network graph links. !4832 - - Fix MR-auto-close text added to description. !4836 - - Add documentation for award emoji now that comments can be awarded with emojis. !4839 - - Fix typo in export failure email. !4847 - - Fix header vertical centering. !4170 - - Fix subsequent SAML sign ins. !4718 - - Set button label when picking an option from status dropdown. !4771 - - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775 - - Handle external issues in IssueReferenceFilter. !4789 - - Support for rendering/redacting multiple documents. !4828 - - Update Todos documentation and screenshots to include new functionality. !4840 - - Hide nav arrows by default. !4843 - - Added bottom padding to label color suggestion link. !4845 - - Use jQuery objects in ref dropdown. !4850 - - Fix GitLab project import issues related to notes and builds. !4855 - - Restrict header logo to 36px so it doesn't overflow. !4861 - - Fix unwanted label unassignment. !4863 - - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869 - - Restore old behavior around diff notes to outdated discussions. !4870 - - Fix merge requests project settings help link anchor. !4873 - - Fix 404 when accessing pipelines as guest user on public projects. !4881 - - Remove width restriction for logo on sign-in page. !4888 - - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884 - - Apply selected value as label. !4886 - - Change Retry to Re-deploy on Deployments page - - Fix temp file being deleted after the request while importing a GitLab project. !4894 - - Fix pagination when sorting by columns with lots of ties (like priority) - - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. - - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 - - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 - - Remove duplicate 'New Page' button on edit wiki page - -v 8.9.0 - - Fix group visibility form layout in application settings - - Fix builds API response not including commit data - - Fix error when CI job variables key specified but not defined - - Fix pipeline status when there are no builds in pipeline - - Fix Error 500 when using closes_issues API with an external issue tracker - - Add more information into RSS feed for issues (Alexander Matyushentsev) - - Bulk assign/unassign labels to issues. - - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - - Show Star and Fork buttons on mobile. - - Performance improvements on RelativeLinkFilter - - Fix endless redirections when accessing user OAuth applications when they are disabled - - Allow enabling wiki page events from Webhook management UI - - Bump rouge to 1.11.0 - - Fix issue with arrow keys not working in search autocomplete dropdown - - Fix an issue where note polling stopped working if a window was in the - background during a refresh. - - Pre-processing Markdown now only happens when needed - - Make EmailsOnPushWorker use Sidekiq mailers queue - - Redesign all Devise emails. !4297 - - Don't show 'Leave Project' to group members - - Fix wiki page events' webhook to point to the wiki repository - - Add a border around images to differentiate them from the background. - - Don't show tags for revert and cherry-pick operations - - Show image ID on registry page - - Fix issue todo not remove when leave project !4150 (Long Nguyen) - - Allow customisable text on the 'nearly there' page after a user signs up - - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - - Fix SVG sanitizer to allow more elements - - Allow forking projects with restricted visibility level - - Added descriptions to notification settings dropdown - - Improve note validation to prevent errors when creating invalid note via API - - Reduce number of fog gem dependencies - - Add number of merge requests for a given milestone to the milestones view. - - Implement a fair usage of shared runners - - Remove project notification settings associated with deleted projects - - Fix 404 page when viewing TODOs that contain milestones or labels in different projects - - Add a metric for the number of new Redis connections created by a transaction - - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark - - Redesign navigation for project pages - - Fix images in sign-up confirmation email - - Added shortcut 'y' for copying a files content hash URL #14470 - - Fix groups API to list only user's accessible projects - - Fix horizontal scrollbar for long commit message. - - GitLab Performance Monitoring now tracks the total method execution time and call count per method - - Add Environments and Deployments - - Redesign account and email confirmation emails - - Don't fail builds for projects that are deleted - - Support Docker Registry manifest v1 - - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - - Bump nokogiri to 1.6.8 - - Use gitlab-shell v3.0.0 - - Fixed alignment of download dropdown in merge requests - - Upgrade to jQuery 2 - - Adds selected branch name to the dropdown toggle - - Add API endpoint for Sidekiq Metrics !4653 - - Refactoring Award Emoji with API support for Issues and MergeRequests - - Use Knapsack to evenly distribute tests across multiple nodes - - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged - - Don't allow MRs to be merged when commits were added since the last review / page load - - Add DB index on users.state - - Limit email on push diff size to 30 files / 150 KB - - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - - Changed the Slack build message to use the singular duration if necessary (Aran Koning) - - Fix race condition on merge when build succeeds - - Added shortcut to focus filter search fields and added documentation #18120 - - Links from a wiki page to other wiki pages should be rewritten as expected - - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) - - Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393 - - Fix issues filter when ordering by milestone - - Disable SAML account unlink feature - - Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3 - - Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid) - - TeamCity Service: Fix URL handling when base URL contains a path - - Todos will display target state if issuable target is 'Closed' or 'Merged' - - Validate only and except regexp - - Fix bug when sorting issues by milestone due date and filtering by two or more labels - - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project - - Add support for using Yubikeys (U2F) for two-factor authentication - - Link to blank group icon doesn't throw a 404 anymore - - Remove 'main language' feature - - Toggle whitespace button now available for compare branches diffs #17881 - - Pipelines can be canceled only when there are running builds - - Allow authentication using personal access tokens - - Use downcased path to container repository as this is expected path by Docker - - Allow to use CI token to fetch LFS objects - - Custom notification settings - - Projects pending deletion will render a 404 page - - Measure queue duration between gitlab-workhorse and Rails - - Added Gfm autocomplete for labels - - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114 - - Make Omniauth providers specs to not modify global configuration - - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) - - Make authentication service for Container Registry to be compatible with < Docker 1.11 - - Make it possible to lock a runner from being enabled for other projects - - Add Application Setting to configure Container Registry token expire delay (default 5min) - - Cache assigned issue and merge request counts in sidebar nav - - Use Knapsack only in CI environment - - Updated project creation page to match new UI #2542 - - Cache project build count in sidebar nav - - Add milestone expire date to the right sidebar - - Manually mark a issue or merge request as a todo - - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing - - Reduce number of queries needed to render issue labels in the sidebar - - Improve error handling importing projects - - Remove duplicated notification settings - - Put project Files and Commits tabs under Code tab - - Decouple global notification level from user model - - Replace Colorize with Rainbow for coloring console output in Rake tasks. - - Add workhorse controller and API helpers - - An indicator is now displayed at the top of the comment field for confidential issues. - - Show categorised search queries in the search autocomplete - - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented - - Dropdown for `.gitlab-ci.yml` templates - - Improve issuables APIs performance when accessing notes !4471 - - Add sorting dropdown to tags page !4423 - - External links now open in a new tab - - Prevent default actions of disabled buttons and links - - Markdown editor now correctly resets the input value on edit cancellation !4175 - - Toggling a task list item in a issue/mr description does not creates a Todo for mentions - - Improved UX of date pickers on issue & milestone forms - - Cache on the database if a project has an active external issue tracker. - - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav - - GitLab project import and export functionality - - All classes in the Banzai::ReferenceParser namespace are now instrumented - - Remove deprecated issues_tracker and issues_tracker_id from project model - - Allow users to create confidential issues in private projects - - Measure CPU time for instrumented methods - - Instrument private methods and private instance methods by default instead just public methods - - Only show notes through JSON on confidential issues that the user has access to - - Updated the allocations Gem to version 1.0.5 - - The background sampler now ignores classes without names - - Update design for `Close` buttons - - New custom icons for navigation - - Horizontally scrolling navigation on project, group, and profile settings pages - - Hide global side navigation by default - - Fix project Star/Unstar project button tooltip - - Remove tanuki logo from side navigation; center on top nav - - Include user relationships when retrieving award_emoji - - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - - Set inverse_of for Project/Service association to reduce the number of queries - - Update tanuki logo highlight/loading colors - - Remove explicit Gitlab::Metrics.action assignments, are already automatic. - - Use Git cached counters for branches and tags on project page - - Cache participable participants in an instance variable. - - Filter parameters for request_uri value on instrumented transactions. - - Remove duplicated keys add UNIQUE index to keys fingerprint column - - ExtractsPath get ref_names from repository cache, if not there access git. - - Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500 - - Cache user todo counts from TodoService - - Ensure Todos counters doesn't count Todos for projects pending delete - - Add left/right arrows horizontal navigation - - Add tooltip to pin/unpin navbar - - Add new sub nav style to Wiki and Graphs sub navigation - -v 8.8.9 - - Upgrade Doorkeeper to 4.2.0. !5881 - -v 8.8.8 - - Upgrade Rails to 4.2.7.1 for security fixes. !5781 - -v 8.8.7 - - Fix privilege escalation issue with OAuth external users. - - Ensure references to private repos aren't shown to logged-out users. - -v 8.8.6 - - Fix visibility of snippets when searching. - - Update omniauth-saml to 1.6.0 !4951 - -v 8.8.5 - - Import GitHub repositories respecting the API rate limit !4166 - - Fix todos page throwing errors when you have a project pending deletion !4300 - - Disable Webhooks before proceeding with the GitHub import !4470 - - Fix importer for GitHub comments on diff !4488 - - Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498 - - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541 - - Prevent unauthorized access for projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - - Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions - - Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions - -v 8.8.4 - - Fix LDAP-based login for users with 2FA enabled. !4493 - - Added descriptions to notification settings dropdown - - Due date can be removed from milestones - -v 8.8.3 - - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 - - Fixed JS error when trying to remove discussion form. !4303 - - Fixed issue with button color when no CI enabled. !4287 - - Fixed potential issue with 2 CI status polling events happening. !3869 - - Improve design of Pipeline view. !4230 - - Fix gitlab importer failing to import new projects due to missing credentials. !4301 - - Fix import URL migration not rescuing with the correct Error. !4321 - - Fix health check access token changing due to old application settings being used. !4332 - - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363 - - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364 - - Pass the "Remember me" value to the 2FA token form. !4369 - - Fix incorrect links on pipeline page when merge request created from fork. !4376 - - Use downcased path to container repository as this is expected path by Docker. !4420 - - Fix wiki project clone address error (chujinjin). !4429 - - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392 - - Fix missing number on generated ordered list element. !4437 - - Prevent disclosure of notes on confidential issues in search results. - -v 8.8.2 - - Added remove due date button. !4209 - - Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources. !4242 - - Fix Error 500 in CI charts by gracefully handling commits with no durations. !4245 - - Fix table UI on CI builds page. !4249 - - Fix backups if registry is disabled. !4263 - - Fixed issue with merge button color. !4211 - - Fixed issue with enter key selecting wrong option in dropdown. !4210 - - When creating a .gitignore file a dropdown with templates will be provided. !4075 - - Fix concurrent request when updating build log in browser. !4183 - -v 8.8.1 - - Add documentation for the "Health Check" feature - - Allow anonymous users to access a public project's pipelines !4233 - - Fix MySQL compatibility in zero downtime migrations helpers - - Fix the CI login to Container Registry (the gitlab-ci-token user) - -v 8.8.0 - - Implement GFM references for milestones (Alejandro Rodríguez) - - Snippets tab under user profile. !4001 (Long Nguyen) - - Fix error when using link to uploads in global snippets - - Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref - - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - - Use a case-insensitive comparison in sanitizing URI schemes - - Toggle sign-up confirmation emails in application settings - - Make it possible to prevent tagged runner from picking untagged jobs - - Added `InlineDiffFilter` to the markdown parser. (Adam Butler) - - Added inline diff styling for `change_title` system notes. (Adam Butler) - - Project#open_branches has been cleaned up and no longer loads entire records into memory. - - Escape HTML in commit titles in system note messages - - Improve design of Pipeline View - - Fix scope used when accessing container registry - - Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios - - Improve multiple branch push performance by memoizing permission checking - - Log to application.log when an admin starts and stops impersonating a user - - Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi) - - Updated gitlab_git to 10.1.0 - - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists - - Reduce delay in destroying a project from 1-minute to immediately - - Make build status canceled if any of the jobs was canceled and none failed - - Upgrade Sidekiq to 4.1.2 - - Added /health_check endpoint for checking service status - - Make 'upcoming' filter for milestones work better across projects - - Sanitize repo paths in new project error message - - Bump mail_room to 0.7.0 to fix stuck IDLE connections - - Remove future dates from contribution calendar graph. - - Support e-mail notifications for comments on project snippets - - Fix API leak of notes of unauthorized issues, snippets and merge requests - - Use ActionDispatch Remote IP for Akismet checking - - Fix error when visiting commit builds page before build was updated - - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project - - Update SVG sanitizer to conform to SVG 1.1 - - Speed up push emails with multiple recipients by only generating the email once - - Updated search UI - - Added authentication service for Container Registry - - Display informative message when new milestone is created - - Sanitize milestones and labels titles - - Support multi-line tag messages. !3833 (Calin Seciu) - - Force users to reset their password after an admin changes it - - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - - Added button to toggle whitespaces changes on diff view - - Backport GitHub Enterprise import support from EE - - Create tags using Rugged for performance reasons. !3745 - - Allow guests to set notification level in projects - - API: Expose Issue#user_notes_count. !3126 (Anton Popov) - - Don't show forks button when user can't view forks - - Fix atom feed links and rendering - - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 - - Added multiple colors for labels in dropdowns when dups happen. - - Show commits in the same order as `git log` - - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) - - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) - - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - - Expire repository exists? and has_visible_content? caches after a push if necessary - - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi) - - Fix adding a todo for private group members (Ahmad Sherif) - - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 - - Total method execution timings are no longer tracked - - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) - - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) - - Hide left sidebar on phone screens to give more space for content - - Redesign navigation for profile and group pages - - Add counter metrics for rails cache - - Import pull requests from GitHub where the source or target branches were removed - - All Grape API helpers are now instrumented - - Improve Issue formatting for the Slack Service (Jeroen van Baarsen) - - Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine) - - Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs) - - When creating a .gitignore file a dropdown with templates will be provided - - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 - -v 8.7.9 - - Fix privilege escalation issue with OAuth external users. - - Ensure references to private repos aren't shown to logged-out users. - -v 8.7.8 - - Fix visibility of snippets when searching. - - Update omniauth-saml to 1.6.0 !4951 - -v 8.7.7 - - Fix import by `Any Git URL` broken if the URL contains a space - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - -v 8.7.6 - - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) - - Fix import from GitLab.com to a private instance failure. !4181 - - Fix external imports not finding the import data. !4106 - - Fix notification delay when changing status of an issue - - Bump Workhorse to 0.7.5 so it can serve raw diffs - -v 8.7.5 - - Fix relative links in wiki pages. !4050 - - Fix always showing build notification message when switching between merge requests !4086 - - Fix an issue when filtering merge requests with more than one label. !3886 - - Fix short note for the default scope on build page (Takuya Noguchi) - -v 8.7.4 - - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) - - Fix setting trusted proxies !3970 - - Fix BitBucket importer bug when throwing exceptions !3941 - - Use sign out path only if not empty !3989 - - Running rake gitlab:db:drop_tables now drops tables with cascade !4020 - - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100 - - Use a case-insensitive comparison in sanitizing URI schemes - -v 8.7.3 - - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented - - Merge request widget displays TeamCity build state and code coverage correctly again. - - Fix the line code when importing PR review comments from GitHub. !4010 - - Wikis are now initialized on legacy projects when checking repositories - - Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea) - -v 8.7.2 - - The "New Branch" button is now loaded asynchronously - - Fix error 500 when trying to create a wiki page - - Updated spacing between notification label and button - - Label titles in filters are now escaped properly - -v 8.7.1 - - Throttle the update of `project.last_activity_at` to 1 minute. !3848 - - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849 - - Fix license detection to detect all license files, not only known licenses. !3878 - - Use the `can?` helper instead of `current_user.can?`. !3882 - - Prevent users from deleting Webhooks via API they do not own - - Fix Error 500 due to stale cache when projects are renamed or transferred - - Update width of search box to fix Safari bug. !3900 (Jedidiah) - - Use the `can?` helper instead of `current_user.can?` - -v 8.7.0 - - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented - - Fix vulnerability that made it possible to gain access to private labels and milestones - - The number of InfluxDB points stored per UDP packet can now be configured - - Fix error when cross-project label reference used with non-existent project - - Transactions for /internal/allowed now have an "action" tag set - - Method instrumentation now uses Module#prepend instead of aliasing methods - - Repository.clean_old_archives is now instrumented - - Add support for environment variables on a job level in CI configuration file - - SQL query counts are now tracked per transaction - - The Projects::HousekeepingService class has extra instrumentation - - All service classes (those residing in app/services) are now instrumented - - Developers can now add custom tags to transactions - - Loading of an issue's referenced merge requests and related branches is now done asynchronously - - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) - - Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan) - - Project switcher uses new dropdown styling - - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) - - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) - - Restrict user profiles when public visibility level is restricted. - - Add ability set due date to issues, sort and filter issues by due date (Mehmet Beydogan) - - All images in discussions and wikis now link to their source files !3464 (Connor Shea). - - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) - - Add setting for customizing the list of trusted proxies !3524 - - Allow projects to be transfered to a lower visibility level group - - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 - - Improved Markdown rendering performance !3389 - - Make shared runners text in box configurable - - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) - - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) - - Expose project badges in project settings - - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 - - Preserve time notes/comments have been updated at when moving issue - - Make HTTP(s) label consistent on clone bar (Stan Hu) - - Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński) - - Expose label description in API (Mariusz Jachimowicz) - - API: Ability to update a group (Robert Schilling) - - API: Ability to move issues (Robert Schilling) - - Fix Error 500 after renaming a project path (Stan Hu) - - Fix a bug whith trailing slash in teamcity_url (Charles May) - - Allow back dating on issues when created or updated through the API - - Allow back dating on issue notes when created through the API - - Propose license template when creating a new LICENSE file - - API: Expose /licenses and /licenses/:key - - Fix avatar stretching by providing a cropping feature - - API: Expose `subscribed` for issues and merge requests (Robert Schilling) - - Allow SAML to handle external users based on user's information !3530 - - Allow Omniauth providers to be marked as `external` !3657 - - Add endpoints to archive or unarchive a project !3372 - - Fix a bug whith trailing slash in bamboo_url - - Add links to CI setup documentation from project settings and builds pages - - Display project members page to all members - - Handle nil descriptions in Slack issue messages (Stan Hu) - - Add automated repository integrity checks (OFF by default) - - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) - - API: Ability to star and unstar a project (Robert Schilling) - - Add default scope to projects to exclude projects pending deletion - - Allow to close merge requests which source projects(forks) are deleted. - - Ensure empty recipients are rejected in BuildsEmailService - - Use rugged to change HEAD in Project#change_head (P.S.V.R) - - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) - - API: Fix milestone filtering by `iid` (Robert Schilling) - - Make before_script and after_script overridable on per-job (Kamil Trzciński) - - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) - - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) - - Better errors handling when creating milestones inside groups - - Fix high CPU usage when PostReceive receives refs/merge-requests/ - - Hide `Create a group` help block when creating a new project in a group - - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - - Allow issues and merge requests to be assigned to the author !2765 - - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) - - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - - Decouple membership and notifications - - Fix creation of merge requests for orphaned branches (Stan Hu) - - API: Ability to retrieve a single tag (Robert Schilling) - - While signing up, don't persist the user password across form redisplays - - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) - - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) - - Fix admin/projects when using visibility levels on search (PotHix) - - Build status notifications - - Update email confirmation interface - - API: Expose user location (Robert Schilling) - - API: Do not leak group existence via return code (Robert Schilling) - - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 - - Update number of Todos in the sidebar when it's marked as "Done". !3600 - - Sanitize branch names created for confidential issues - - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) - - API: User can leave a project through the API when not master or owner. !3613 - - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) - - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) - - Improved markdown forms - - Diff design updates (colors, button styles, etc) - - Copying and pasting a diff no longer pastes the line numbers or +/- - - Add null check to formData when updating profile content to fix Firefox bug - - Disable spellcheck and autocorrect for username field in admin page - - Delete tags using Rugged for performance reasons (Robert Schilling) - - Add Slack notifications when Wiki is edited (Sebastian Klier) - - Diffs load at the correct point when linking from from number - - Selected diff rows highlight - - Fix emoji categories in the emoji picker - - API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling) - - Add encrypted credentials for imported projects and migrate old ones - - Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller) - - Author and participants are displayed first on users autocompletion - - Show number sign on external issue reference text (Florent Baldino) - - Updated print style for issues - - Use GitHub Issue/PR number as iid to keep references - - Import GitHub labels - - Add option to filter by "Owned projects" on dashboard page - - Import GitHub milestones - - Execute system web hooks on push to the project - - Allow enable/disable push events for system hooks - - Fix GitHub project's link in the import page when provider has a custom URL - - Add RAW build trace output and button on build page - - Add incremental build trace update into CI API - -v 8.6.9 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - - Only show notes through JSON on confidential issues that the user has access to - -v 8.6.8 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via Git branch and tag names - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent XSS via label drop-down - - Prevent information disclosure via milestone API - - Prevent information disclosure via snippet API - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.6.7 - - Fix persistent XSS vulnerability in `commit_person_link` helper - - Fix persistent XSS vulnerability in Label and Milestone dropdowns - - Fix vulnerability that made it possible to enumerate private projects belonging to group - -v 8.6.6 - - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 - - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 - - Fix revoking of authorized OAuth applications (Connor Shea). !3690 - - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) - - Issuable header is consistent between issues and merge requests - - Improved spacing in issuable header on mobile - -v 8.6.5 - - Fix importing from GitHub Enterprise. !3529 - - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533 - - Check permissions when user attempts to import members from another project. !3535 - - Only update repository language if it is not set to improve performance. !3556 - - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 - - Unblock user when active_directory is disabled and it can be found !3550 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.6.4 - - Don't attempt to fetch any tags from a forked repo (Stan Hu) - - Redesign the Labels page - -v 8.6.3 - - Mentions on confidential issues doesn't create todos for non-members. !3374 - - Destroy related todos when an Issue/MR is deleted. !3376 - - Fix error 500 when target is nil on todo list. !3376 - - Fix copying uploads when moving issue to another project. !3382 - - Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432 - - Fix raw/rendered diff producing different results on merge requests. !3450 - - Fix commit comment alignment (Stan Hu). !3466 - - Fix Error 500 when searching for a comment in a project snippet. !3468 - - Allow temporary email as notification email. !3477 - - Fix issue with dropdowns not selecting values. !3478 - - Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280 - -v 8.6.2 - - Fix dropdown alignment. !3298 - - Fix issuable sidebar overlaps on tablet. !3299 - - Make dropdowns pixel perfect. !3337 - - Fix order of steps to prevent PostgreSQL errors when running migration. !3355 - - Fix bold text in issuable sidebar. !3358 - - Fix error with anonymous token in applications settings. !3362 - - Fix the milestone 'upcoming' filter. !3364 + !3368 - - Fix comments on confidential issues showing up in activity feed to non-members. !3375 - - Fix `NoMethodError` when visiting CI root path at `/ci`. !3377 - - Add a tooltip to new branch button in issue page. !3380 - - Fix an issue hiding the password form when signed-in with a linked account. !3381 - - Add links to CI setup documentation from project settings and builds pages. !3384 - - Fix an issue with width of project select dropdown. !3386 - - Remove redundant `require`s from Banzai files. !3391 - - Fix error 500 with cancel button on issuable edit form. !3392 + !3417 - - Fix background when editing a highlighted note. !3423 - - Remove tabstop from the WIP toggle links. !3426 - - Ensure private project snippets are not viewable by unauthorized people. - - Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402 - - Fixed issue with notification settings not saving. !3452 - -v 8.6.1 - - Add option to reload the schema before restoring a database backup. !2807 - - Display navigation controls on mobile. !3214 - - Fixed bug where participants would not work correctly on merge requests. !3329 - - Fix sorting issues by votes on the groups issues page results in SQL errors. !3333 - - Restrict notifications for confidential issues. !3334 - - Do not allow to move issue if it has not been persisted. !3340 - - Add a confirmation step before deleting an issuable. !3341 - - Fixes issue with signin button overflowing on mobile. !3342 - - Auto collapses the navigation sidebar when resizing. !3343 - - Fix build dependencies, when the dependency is a string. !3344 - - Shows error messages when trying to create label in dropdown menu. !3345 - - Fixes issue with assign milestone not loading milestone list. !3346 - - Fix an issue causing the Dashboard/Milestones page to be blank. !3348 - -v 8.6.0 - - Add ability to move issue to another project - - Prevent tokens in the import URL to be showed by the UI - - Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu) - - Add confidential issues - - Bump gitlab_git to 9.0.3 (Stan Hu) - - Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu) - - Support Golang subpackage fetching (Stan Hu) - - Bump Capybara gem to 2.6.2 (Stan Hu) - - New branch button appears on issues where applicable - - Contributions to forked projects are included in calendar - - Improve the formatting for the user page bio (Connor Shea) - - Easily (un)mark merge request as WIP using link - - Use specialized system notes when MR is (un)marked as WIP - - Removed the default password from the initial admin account created during - setup. A password can be provided during setup (see installation docs), or - GitLab will ask the user to create a new one upon first visit. - - Fix issue when pushing to projects ending in .wiki - - Properly display YAML front matter in Markdown - - Add support for wiki with UTF-8 page names (Hiroyuki Sato) - - Fix wiki search results point to raw source (Hiroyuki Sato) - - Don't load all of GitLab in mail_room - - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) - - HTTP error pages work independently from location and config (Artem Sidorenko) - - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - - Memoize @group in Admin::GroupsController (Yatish Mehta) - - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - - Added omniauth-auth0 Gem (Daniel Carraro) - - Add label description in tooltip to labels in issue index and sidebar - - Strip leading and trailing spaces in URL validator (evuez) - - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez) - - Return empty array instead of 404 when commit has no statuses in commit status API - - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip) - - Rewrite logo to simplify SVG code (Sean Lang) - - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) - - Ignore jobs that start with `.` (hidden jobs) - - Hide builds from project's settings when the feature is disabled - - Allow to pass name of created artifacts archive in `.gitlab-ci.yml` - - Refactor and greatly improve search performance - - Add support for cross-project label references - - Ensure "new SSH key" email do not ends up as dead Sidekiq jobs - - Update documentation to reflect Guest role not being enforced on internal projects - - Allow search for logged out users - - Allow to define on which builds the current one depends on - - Allow user subscription to a label: get notified for issues/merge requests related to that label (Timothy Andrew) - - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) - - Don't show Issues/MRs from archived projects in Groups view - - Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs - - Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie) - - Increase the notes polling timeout over time (Roberto Dip) - - Add shortcut to toggle markdown preview (Florent Baldino) - - Show labels in dashboard and group milestone views - - Fix an issue when the target branch of a MR had been deleted - - Add main language of a project in the list of projects (Tiago Botelho) - - Add #upcoming filter to Milestone filter (Tiago Botelho) - - Add ability to show archived projects on dashboard, explore and group pages - - Remove fork link closes all merge requests opened on source project (Florent Baldino) - - Move group activity to separate page - - Create external users which are excluded of internal and private projects unless access was explicitly granted - - Continue parameters are checked to ensure redirection goes to the same instance - - User deletion is now done in the background so the request can not time out - - Canceled builds are now ignored in compound build status if marked as `allowed to fail` - - Trigger a todo for mentions on commits page - - Let project owners and admins soft delete issues and merge requests - -v 8.5.13 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.5.12 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via Git branch and tag names - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent information disclosure via snippet API - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.5.11 - - Fix persistent XSS vulnerability in `commit_person_link` helper - -v 8.5.10 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.5.9 - - Don't attempt to fetch any tags from a forked repo (Stan Hu). - -v 8.5.8 - - Bump Git version requirement to 2.7.4 - -v 8.5.7 - - Bump Git version requirement to 2.7.3 - -v 8.5.6 - - Obtain a lease before querying LDAP - -v 8.5.5 - - Ensure removing a project removes associated Todo entries - - Prevent a 500 error in Todos when author was removed - - Fix pagination for filtered dashboard and explore pages - - Fix "Show all" link behavior - -v 8.5.4 - - Do not cache requests for badges (including builds badge) - -v 8.5.3 - - Flush repository caches before renaming projects - - Sort starred projects on dashboard based on last activity by default - - Show commit message in JIRA mention comment - - Makes issue page and merge request page usable on mobile browsers. - - Improved UI for profile settings - -v 8.5.2 - - Fix sidebar overlapping content when screen width was below 1200px - - Don't repeat labels listed on Labels tab - - Bring the "branded appearance" feature from EE to CE - - Fix error 500 when commenting on a commit - - Show days remaining instead of elapsed time for Milestone - - Fix broken icons on installations with relative URL (Artem Sidorenko) - - Fix issue where tag list wasn't refreshed after deleting a tag - - Fix import from gitlab.com (KazSawada) - - Improve implementation to check read access to forks and add pagination - - Don't show any "2FA required" message if it's not actually required - - Fix help keyboard shortcut on relative URL setups (Artem Sidorenko) - - Update Rails to 4.2.5.2 - - Fix permissions for deprecated CI build status badge - - Don't show "Welcome to GitLab" when the search didn't return any projects - - Add Todos documentation - -v 8.5.1 - - Fix group projects styles - - Show Crowd login tab when sign in is disabled and Crowd is enabled (Peter Hudec) - - Fix a set of small UI glitches in project, profile, and wiki pages - - Restrict permissions on public/uploads - - Fix the merge request side-by-side view after loading diff results - - Fix the look of tooltip for the "Revert" button - - Add when the Builds & Runners API changes got introduced - - Fix error 500 on some merged merge requests - - Fix an issue causing the content of the issuable sidebar to disappear - - Fix error 500 when trying to mark an already done todo as "done" - - Fix an issue where MRs weren't sortable - - Issues can now be dragged & dropped into empty milestone lists. This is also - possible with MRs - - Changed padding & background color for highlighted notes - - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - - Update sentry-raven gem to 0.15.6 - - Add build coverage in project's builds page (Steffen Köhler) - - Changed # to ! for merge requests in activity view - -v 8.5.0 - - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - - Cache various Repository methods to improve performance - - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) - - Ensure rake tasks that don't need a DB connection can be run without one - - Update New Relic gem to 3.14.1.311 (Stan Hu) - - Add "visibility" flag to GET /projects api endpoint - - Add an option to supply root email through an environmental variable (Koichiro Mikami) - - Ignore binary files in code search to prevent Error 500 (Stan Hu) - - Render sanitized SVG images (Stan Hu) - - Support download access by PRIVATE-TOKEN header (Stan Hu) - - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - - Add option to include the sender name in body of Notify email (Jason Lee) - - New UI for pagination - - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - set it up - - API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger) - - Fix diff comments loaded by AJAX to load comment with diff in discussion tab - - Fix relative links in other markup formats (Ben Boeckel) - - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - - Fix label links for a merge request pointing to issues list - - Don't vendor minified JS - - Increase project import timeout to 15 minutes - - Be more permissive with email address validation: it only has to contain a single '@' - - 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 - - Allow limited Markdown in Broadcast Messages - - 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) - - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) - - Optimized performance of finding issues to be closed by a merge request - - Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace` - and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev) - - Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url` - in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev) - - Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev) - - API: Expose MergeRequest#merge_status (Andrei Dziahel) - - Revert "Add IP check against DNSBLs at account sign-up" - - Actually use the `skip_merges` option in Repository#commits (Tony Chu) - - 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 - - Discover branches for commit statuses ref-less when doing merge when succeeded - - 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) - - API: Allow to set or update a merge-request's milestone (Kirill Skachkov) - - Improve UI consistency between projects and groups lists - - Add sort dropdown to dashboard projects page - - Fixed logo animation on Safari (Roman Rott) - - Fix Merge When Succeeded when multiple stages - - 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 - - Don't process cross-reference notes from forks - - Fix: init.d script not working on OS X - - Faster snippet search - - Added API to download build artifacts - - Title for milestones should be unique (Zeger-Jan van de Weg) - - Validate correctness of maximum attachment size application setting - - Replaces "Create merge request" link with one to the "Merge Request" when one exists - - Fix CI builds badge, add a new link to builds badge, deprecate the old one - - Fix broken link to project in build notification emails - - Ability to see and sort on vote count from Issues and MR lists - - Fix builds scheduler when first build in stage was allowed to fail - - User project limit is reached notice is hidden if the projects limit is zero - - Add API support for managing runners and project's runners - - Allow SAML users to login with no previous account without having to allow - all Omniauth providers to do so. - - Allow existing users to auto link their SAML credentials by logging in via SAML - - Make it possible to erase a build (trace, artifacts) using UI and API - - Ability to revert changes from a Merge Request or Commit - - Emoji comment on diffs are not award emoji - - Add label description (Nuttanart Pornprasitsakul) - - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - - Add Todos - -v 8.4.11 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.4.10 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via Git branch and tag names - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent information disclosure via snippet API - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.4.9 - - Fix persistent XSS vulnerability in `commit_person_link` helper - -v 8.4.8 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.4.7 - - Don't attempt to fetch any tags from a forked repo (Stan Hu). - -v 8.4.6 - - Bump Git version requirement to 2.7.4 - -v 8.4.5 - - No CE-specific changes - -v 8.4.4 - - Update omniauth-saml gem to 1.4.2 - - Prevent long-running backup tasks from timing out the database connection - - Add a Project setting to allow guests to view build logs (defaults to true) - - Sort project milestones by due date including issue editor (Oliver Rogers / Orih) - -v 8.4.3 - - Increase lfs_objects size column to 8-byte integer to allow files larger - than 2.1GB - - Correctly highlight MR diff when MR has merge conflicts - - Fix highlighting in blame view - - Update sentry-raven gem to prevent "Not a git repository" console output - when running certain commands - - Add instrumentation to additional Gitlab::Git and Rugged methods for - performance monitoring - - Allow autosize textareas to also be manually resized - -v 8.4.2 - - Bump required gitlab-workhorse version to bring in a fix for missing - artifacts in the build artifacts browser - - Get rid of those ugly borders on the file tree view - - Fix updating the runner information when asking for builds - - Bump gitlab_git version to 7.2.24 in order to bring in a performance - improvement when checking if a repository was empty - - Add instrumentation for Gitlab::Git::Repository instance methods so we can - track them in Performance Monitoring. - - Increase contrast between highlighted code comments and inline diff marker - - Fix method undefined when using external commit status in builds - - Fix highlighting in blame view. - -v 8.4.1 - - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3), - and Nokogiri (1.6.7.2) - - Fix redirect loop during import - - Fix diff highlighting for all syntax themes - - Delete project and associations in a background worker - -v 8.4.0 - - Allow LDAP users to change their email if it was not set by the LDAP server - - Ensure Gravatar host looks like an actual host - - Consider re-assign as a mention from a notification point of view - - Add pagination headers to already paginated API resources - - Properly generate diff of orphan commits, like the first commit in a repository - - Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages - - Autocomplete data is now always loaded, instead of when focusing a comment text area - - Improved performance of finding issues for an entire group - - Added custom application performance measuring system powered by InfluxDB - - Add syntax highlighting to diffs - - Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu) - - Bump fog to 1.36.0 (Stan Hu) - - Add user's last used IP addresses to admin page (Stan Hu) - - Add housekeeping function to project settings page - - The default GitLab logo now acts as a loading indicator - - Fix caching issue where build status was not updating in project dashboard (Stan Hu) - - Accept 2xx status codes for successful Webhook triggers (Stan Hu) - - Fix missing date of month in network graph when commits span a month (Stan Hu) - - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) - - Don't notify users twice if they are both project watchers and subscribers (Stan Hu) - - Remove gray background from layout in UI - - Fix signup for OAuth providers that don't provide a name - - Implement new UI for group page - - Implement search inside emoji picker - - Let the CI runner know about builds that this build depends on - - Add API support for looking up a user by username (Stan Hu) - - Add project permissions to all project API endpoints (Stan Hu) - - Link to milestone in "Milestone changed" system note - - Only allow group/project members to mention `@all` - - Expose Git's version in the admin area (Trey Davis) - - Add "Frequently used" category to emoji picker - - Add CAS support (tduehr) - - Add link to merge request on build detail page - - Fix: Problem with projects ending with .keys (Jose Corcuera) - - Revert back upvote and downvote button to the issue and MR pages - - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) - - Add system hook messages for project rename and transfer (Steve Norman) - - Fix version check image in Safari - - Show 'All' tab by default in the builds page - - Add Open Graph and Twitter Card data to all pages - - Fix API project lookups when querying with a namespace with dots (Stan Hu) - - Enable forcing Two-factor authentication sitewide, with optional grace period - - Import GitHub Pull Requests into GitLab - - Change single user API endpoint to return more detailed data (Michael Potthoff) - - Update version check images to use SVG - - Validate README format before displaying - - Enable Microsoft Azure OAuth2 support (Janis Meybohm) - - Properly set task-list class on single item task lists - - Add file finder feature in tree view (Kyungchul Shin) - - Ajax filter by message for commits page - - API: Add support for deleting a tag via the API (Robert Schilling) - - Allow subsequent validations in CI Linter - - Show referenced MRs & Issues only when the current viewer can access them - - Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee) - - Add API support for managing project's builds - - Add API support for managing project's build triggers - - Add API support for managing project's build variables - - Allow broadcast messages to be edited - - Autosize Markdown textareas - - Import GitHub wiki into GitLab - - Add reporters ability to download and browse build artifacts (Andrew Johnson) - - Autofill referring url in message box when reporting user abuse. - - Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg) - - Add build artifacts browser - - Improve UX in builds artifacts browser - - Increase default size of `data` column in `events` table when using MySQL - - Expose button to CI Lint tool on project builds page - - Fix: Creator should be added as a master of the project on creation - - Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov) - - Add IP check against DNSBLs at account sign-up - - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching - -v 8.3.10 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.3.9 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via custom issue tracker URL - - Prevent XSS via `window.opener` - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.3.8 - - Fix persistent XSS vulnerability in `commit_person_link` helper - -v 8.3.7 - - Fix a 2FA authentication spoofing vulnerability. - -v 8.3.6 - - Don't attempt to fetch any tags from a forked repo (Stan Hu). - -v 8.3.5 - - Bump Git version requirement to 2.7.4 - -v 8.3.4 - - Use gitlab-workhorse 0.5.4 (fixes API routing bug) - -v 8.3.3 - - Preserve CE behavior with JIRA integration by only calling API if URL is set - - Fix duplicated branch creation/deletion events when using Web UI (Stan Hu) - - Add configurable LDAP server query timeout - - Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running - - Suppress e-mails on failed builds if allow_failure is set (Stan Hu) - - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) - - Better support for referencing and closing issues in Asana service (Mike Wyatt) - - Enable "Add key" button when user fills in a proper key (Stan Hu) - - Fix error in processing reply-by-email messages (Jason Lee) - - Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu) - - Use WOFF versions of SourceSansPro fonts - - Fix regression when builds were not generated for tags created through web/api interface - - Fix: maintain milestone filter between Open and Closed tabs (Greg Smethells) - - Fix missing artifacts and build traces for build created before 8.3 - -v 8.3.2 - - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) - - Add support for Google reCAPTCHA in user registration - -v 8.3.1 - - Fix Error 500 when global milestones have slashes (Stan Hu) - - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) - - Fix LDAP identity and user retrieval when special characters are used - - Move Sidekiq-cron configuration to gitlab.yml - -v 8.3.0 - - Bump rack-attack to 4.3.1 for security fix (Stan Hu) - - API support for starred projects for authorized user (Zeger-Jan van de Weg) - - Add open_issues_count to project API (Stan Hu) - - Expand character set of usernames created by Omniauth (Corey Hinshaw) - - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) - - Add unsubscribe link in the email footer (Zeger-Jan van de Weg) - - Provide better diagnostic message upon project creation errors (Stan Hu) - - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu) - - Remove api credentials from link to build_page - - Deprecate GitLabCiService making it to always be inactive - - Bump gollum-lib to 4.1.0 (Stan Hu) - - Fix broken group avatar upload under "New group" (Stan Hu) - - Update project repositorize size and commit count during import:repos task (Stan Hu) - - Fix API setting of 'public' attribute to false will make a project private (Stan Hu) - - Handle and report SSL errors in Webhook 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 - - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - - Recognize issue/MR/snippet/commit links as references - - Backport JIRA features from EE to CE - - Add ignore whitespace change option to commit view - - Fire update hook from GitLab - - Allow account unlock via email - - Style warning about mentioning many people in a comment - - Fix: sort milestones by due date once again (Greg Smethells) - - Migrate all CI::Services and CI::WebHooks to Services and WebHooks - - Don't show project fork event as "imported" - - Add API endpoint to fetch merge request commits list - - Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled - - Expose events API with comment information and author info - - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 - - Run custom Git hooks when branch is created or deleted. - - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch - - Add languages page to graphs - - Block LDAP user when they are no longer found in the LDAP server - - Improve wording on project visibility levels (Zeger-Jan van de Weg) - - Fix editing notes on a merge request diff - - Automatically select default clone protocol based on user preferences (Eirik Lygre) - - Make Network page as sub tab of Commits - - Add copy-to-clipboard button for Snippets - - Add indication to merge request list item that MR cannot be merged automatically - - Default target branch to patch-n when editing file in protected branch - - Add Builds tab to merge request detail page - - Allow milestones, issues and MRs to be created from dashboard and group indexes - - Use new style for wiki - - Use new style for milestone detail page - - Fix sidebar tooltips when collapsed - - Prevent possible XSS attack with award-emoji - - Upgraded Sidekiq to 4.x - - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg) - - Fix emoji aliases problem - - Fix award-emojis Flash alert's width - - Fix deleting notes on a merge request diff - - Display referenced merge request statuses in the issue description (Greg Smethells) - - Implement new sidebar for issue and merge request pages - - Emoji picker improvements - - Suppress warning about missing `.gitlab-ci.yml` if builds are disabled - - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present - - Persist runners registration token in database - - Fix online editor should not remove newlines at the end of the file - - Expose Git's version in the admin area - - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) - -v 8.2.6 - - Prevent unauthorized access to other projects build traces - - Forbid scripting for wiki files - -v 8.2.5 - - Prevent privilege escalation via "impersonate" feature - - Prevent privilege escalation via notes API - - Prevent privilege escalation via project webhook API - - Prevent XSS via `window.opener` - - Prevent information disclosure via project labels - - Prevent information disclosure via new merge request page - -v 8.2.4 - - Bump Git version requirement to 2.7.4 - -v 8.2.3 - - Fix application settings cache not expiring after changes (Stan Hu) - - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) - - Update documentation for "Guest" permissions - - Properly convert Emoji-only comments into Award Emojis - - Enable devise paranoid mode to prevent user enumeration attack - - Webhook payload has an added, modified and removed properties for each commit - - Fix 500 error when creating a merge request that removes a submodule - -v 8.2.2 - - Fix 404 in redirection after removing a project (Stan Hu) - - Ensure cached application settings are refreshed at startup (Stan Hu) - - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - - Fix: Raw private snippets access workflow - - Prevent "413 Request entity too large" errors when pushing large files with LFS - - Fix invalid links within projects dashboard header - - Make current user the first user in assignee dropdown in issues detail page (Stan Hu) - - Fix: duplicate email notifications on issue comments - -v 8.2.1 - - Forcefully update builds that didn't want to update with state machine - - Fix: saving GitLabCiService as Admin Template - -v 8.2.0 - - Improved performance of finding projects and groups in various places - - Improved performance of rendering user profile pages and Atom feeds - - Expose build artifacts path as config option - - Fix grouping of contributors by email in graph. - - Improved performance of finding issues with/without labels - - Fix Drone CI service template not saving properly (Stan Hu) - - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - - Improved performance of finding users by one of their Email addresses - - Add allow_failure field to commit status API (Stan Hu) - - Commits without .gitlab-ci.yml are marked as skipped - - Save detailed error when YAML syntax is invalid - - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml - - Added build artifacts - - Improved performance of replacing references in comments - - Show last project commit to default branch on project home page - - Highlight comment based on anchor in URL - - Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw) - - Improved performance of sorting milestone issues - - Allow users to select the Files view as default project view (Cristian Bica) - - Show "Empty Repository Page" for repository without branches (Artem V. Navrotskiy) - - Fix: Inability to reply to code comments in the MR view, if the MR comes from a fork - - Use git follow flag for commits page when retrieve history for file or directory - - Show merge request CI status on merge requests index page - - Send build name and stage in CI notification e-mail - - Extend yml syntax for only and except to support specifying repository path - - Enable shared runners to all new projects - - Bump GitLab-Workhorse to 0.4.1 - - Allow to define cache in `.gitlab-ci.yml` - - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - - Remove deprecated CI events from project settings page - - Use issue editor as cross reference comment author when issue is edited with a new mention. - - Add graphs of commits ahead and behind default branch (Jeff Stubler) - - Improve personal snippet access workflow (Douglas Alexandre) - - [API] Add ability to fetch the commit ID of the last commit that actually touched a file - - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) - - Add "New file" link to dropdown on project page - - Include commit logs in project search - - Add "added", "modified" and "removed" properties to commit object in webhook - - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - - Allow groups to appear in the search results if the group owner allows it - - Add email notification to former assignee upon unassignment (Adam Lieskovský) - - New design for project graphs page - - Remove deprecated dumped yaml file generated from previous job definitions - - Show specific runners from projects where user is master or owner - - MR target branch is now visible on a list view when it is different from project's default one - - Improve Continuous Integration graphs page - - Make color of "Accept Merge Request" button consistent with current build status - - Add ignore white space option in merge request diff and commit and compare view - - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) - - Relative links from a repositories README.md now link to the default branch - - Fix trailing whitespace issue in merge request/issue title - - Fix bug when milestone/label filter was empty for dashboard issues page - - Add ability to create milestone in group projects from single form - - Add option to create merge request when editing/creating a file (Dirceu Tiegs) - - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) - - Add Award Emoji to issue and merge request pages - -v 8.1.4 - - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) - - Prevent redirect loop when home_page_url is set to the root URL - - Fix incoming email config defaults - - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - -v 8.1.3 - - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) - - Spread out runner contacted_at updates - - Use issue editor as cross reference comment author when issue is edited with a new mention - - Add Facebook authentication - -v 8.1.2 - - Fix cloning Wiki repositories via HTTP (Stan Hu) - - Add migration to remove satellites directory - - Fix specific runners visibility - - Fix 500 when editing CI service - - Require CI jobs to be named - - Fix CSS for runner status - - Fix CI badge - - Allow developer to manage builds - -v 8.1.1 - - Removed, see 8.1.2 - -v 8.1.0 - - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu) - - Fix duplicate repositories in GitHub import page (Stan Hu) - - Redirect to a default path if HTTP_REFERER is not set (Stan Hu) - - Adds ability to create directories using the web editor (Ben Ford) - - Cleanup stuck CI builds - - Send an email to admin email when a user is reported for spam (Jonathan Rochkind) - - Show notifications button when user is member of group rather than project (Grzegorz Bizon) - - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge. - - Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu) - - Don't show "Add README" link in an empty repository if user doesn't have access to push (Stan Hu) - - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) - - Speed up load times of issue detail pages by roughly 1.5x - - Fix CI rendering regressions - - If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg) - - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) - - Make diff file view easier to use on mobile screens (Stan Hu) - - Improved performance of finding users by username or Email address - - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) - - Add support for creating directories from Files page (Stan Hu) - - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) - - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) - - Improved performance of the trending projects page - - Remove CI migration task - - Improved performance of finding projects by their namespace - - Add assignee data to Issuables' hook_data (Bram Daams) - - Fix bug where transferring a project would result in stale commit links (Stan Hu) - - Fix build trace updating - - Include full path of source and target branch names in New Merge Request page (Stan Hu) - - Add user preference to view activities as default dashboard (Stan Hu) - - Add option to admin area to sign in as a specific user (Pavel Forkert) - - Show CI status on all pages where commits list is rendered - - Automatically enable CI when push .gitlab-ci.yml file to repository - - Move CI charts to project graphs area - - Fix cases where Markdown did not render links in activity feed (Stan Hu) - - Add first and last to pagination (Zeger-Jan van de Weg) - - Added Commit Status API - - Added Builds View - - Added when to .gitlab-ci.yml - - Show CI status on commit page - - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds - - Show CI status on Your projects page and Starred projects page - - Remove "Continuous Integration" page from dashboard - - Add notes and SSL verification entries to hook APIs (Ben Boeckel) - - Fix grammar in admin area "labels" .nothing-here-block when no labels exist. - - Move CI runners page to project settings area - - Move CI variables page to project settings area - - Move CI triggers page to project settings area - - Move CI project settings page to CE project settings area - - Fix bug when removed file was not appearing in merge request diff - - Show warning when build cannot be served by any of the available CI runners - - Note the original location of a moved project when notifying users of the move - - Improve error message when merging fails - - Add support of multibyte characters in LDAP UID (Roman Petrov) - - Show additions/deletions stats on merge request diff - - Remove footer text in emails (Zeger-Jan van de Weg) - - Ensure code blocks are properly highlighted after a note is updated - - Fix wrong access level badge on MR comments - - Hide password in the service settings form - - Move CI webhooks page to project settings area - - Fix User Identities API. It now allows you to properly create or update user's identities. - - Add user preference to change layout width (Peter Göbel) - - Use commit status in merge request widget as preferred source of CI status - - Integrate CI commit and build pages into project pages - - Move CI services page to project settings area - - Add "Quick Submit" behavior to input fields throughout the application. Use - Cmd+Enter on Mac and Ctrl+Enter on Windows/Linux. - - Fix position of hamburger in header for smaller screens (Han Loong Liauw) - - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) - - Persist filters when sorting on admin user page (Jerry Lukins) - - Update style of snippets pages (Han Loong Liauw) - - Allow dashboard and group issues/MRs to be filtered by label - - Add spellcheck=false to certain input fields - - Invalidate stored service password if the endpoint URL is changed - - Project names are not fully shown if group name is too big, even on group page view - - Apply new design for Files page - - Add "New Page" button to Wiki Pages tab (Stan Hu) - - Only render 404 page from /public - - Hide passwords from services API (Alex Lossent) - - Fix: Images cannot show when projects' path was changed - - Let gitlab-git-http-server generate and serve 'git archive' downloads - - Optimize query when filtering on issuables (Zeger-Jan van de Weg) - - Fix padding of outdated discussion item. - - Animate the logo on hover - -v 8.0.5 - - Correct lookup-by-email for LDAP logins - - Fix loading spinner sometimes not being hidden on Merge Request tab switches - -v 8.0.4 - - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) - - Fix referrals for :back and relative URL installs - - Fix anchors to comments in diffs - - Remove CI token from build traces - - Fix "Assign All" button on Runner admin page - - Fix search in Files - - Add full project namespace to payload of system webhooks (Ricardo Band) - -v 8.0.3 - - Fix URL shown in Slack notifications - - Fix bug where projects would appear to be stuck in the forked import state (Stan Hu) - - Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu) - - Add work_in_progress key to MR webhooks (Ben Boeckel) - -v 8.0.2 - - Fix default avatar not rendering in network graph (Stan Hu) - - Skip check_initd_configured_correctly on omnibus installs - - Prevent double-prefixing of help page paths - - Clarify confirmation text on user deletion - - Make commit graphs responsive to window width changes (Stan Hu) - - Fix top margin for sign-in button on public pages - - Fix LDAP attribute mapping - - Remove git refs used internally by GitLab from network graph (Stan Hu) - - Use standard Markdown font in Markdown preview instead of fixed-width font (Stan Hu) - - Fix Reply by email for non-UTF-8 messages. - - Add option to use StartTLS with Reply by email IMAP server. - - Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie) - -v 8.0.1 - - Improve CI migration procedure and documentation - -v 8.0.0 - - Fix Markdown links not showing up in dashboard activity feed (Stan Hu) - - Remove milestones from merge requests when milestones are deleted (Stan Hu) - - Fix HTML link that was improperly escaped in new user e-mail (Stan Hu) - - Fix broken sort in merge request API (Stan Hu) - - Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu) - - Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu) - - Add "replace" and "upload" functionalities to allow user replace existing file and upload new file into current repository - - Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) - - Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu) - - Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU) - - Fix broken Wiki Page History (Stan Hu) - - Import forked repositories asynchronously to prevent large repositories from timing out (Stan Hu) - - Prevent anchors from being hidden by header (Stan Hu) - - Fix bug where only the first 15 Bitbucket issues would be imported (Stan Hu) - - Sort issues by creation date in Bitbucket importer (Stan Hu) - - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu) - - Improve dropdown positioning on the project home page (Hannes Rosenögger) - - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) - - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) - - Restrict users API endpoints to use integer IDs (Stan Hu) - - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) - - Remove satellites - - Better performance for web editor (switched from satellites to rugged) - - Faster merge - - Ability to fetch merge requests from refs/merge-requests/:id - - Allow displaying of archived projects in the admin interface (Artem Sidorenko) - - Allow configuration of import sources for new projects (Artem Sidorenko) - - Search for comments should be case insensetive - - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) - - Ability to search milestones - - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) - - Move dashboard activity to separate page (for your projects and starred projects) - - Improve performance of git blame - - Limit content width to 1200px for most of pages to improve readability on big screens - - Fix 500 error when submit project snippet without body - - Improve search page usability - - Bring more UI consistency in way how projects, snippets and groups lists are rendered - - Make all profiles and group public - - Fixed login failure when extern_uid changes (Joel Koglin) - - Don't notify users without access to the project when they are (accidentally) mentioned in a note. - - Retrieving oauth token with LDAP credentials - - Load Application settings from running database unless env var USE_DB=false - - Added Drone CI integration (Kirill Zaitsev) - - Allow developers to retry builds - - Hide advanced project options for non-admin users - - Fail builds if no .gitlab-ci.yml is found - - Refactored service API and added automatically service docs generator (Kirill Zaitsev) - - Added web_url key project hook_attrs (Kirill Zaitsev) - - Add ability to get user information by ID of an SSH key via the API - - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab - - Add support for Crowd - - Global Labels that are available to all projects - - Fix highlighting of deleted lines in diffs. - - Project notification level can be set on the project page itself - - Added service API endpoint to retrieve service parameters (Petheő Bence) - - Add FogBugz project import (Jared Szechy) - - Sort users autocomplete lists by user (Allister Antosik) - - Webhook for issue now contains repository field (Jungkook Park) - - Add ability to add custom text to the help page (Jeroen van Baarsen) - - Add pg_schema to backup config - - Fix references to target project issues in Merge Requests markdown preview and textareas (Francesco Levorato) - - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato) - - Removed API calls from CE to CI - -v 7.14.3 through 0.8.0 - - See changelogs/archive.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..46f718fc88a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2465 @@ +Please view this file on the master branch, on stable branches it's out of date. + +## 8.13.0 (2016-10-22) + + - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) + - Respond with 404 Not Found for non-existent tags (Linus Thiel) + - Truncate long labels with ellipsis in labels page + - Improve tabbing usability for sign in page (ClemMakesApps) + - Enforce TrailingSemicolon and EmptyLineBetweenBlocks in scss-lint + - Adding members no longer silently fails when there is extra whitespace + - Update runner version only when updating contacted_at + - Add link from system note to compare with previous version + - Use gitlab-shell v3.6.6 + - Add `/projects/visible` API endpoint (Ben Boeckel) + - Fix centering of custom header logos (Ashley Dumaine) + - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Updating verbiage on git basics to be more intuitive + - Clarify documentation for Runners API (Gennady Trafimenkov) + - The instrumentation for Banzai::Renderer has been restored + - Change user & group landing page routing from /u/:username to /:username + - Prevent running GfmAutocomplete setup for each diff note !6569 + - Added documentation for .gitattributes files + - AbstractReferenceFilter caches project_refs on RequestStore when active + - Replaced the check sign to arrow in the show build view. !6501 + - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data + - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) + - Speed-up group milestones show page + - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) + - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService + - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) + - Add tag shortcut from the Commit page. !6543 + - Keep refs for each deployment + - Allow browsing branches that end with '.atom' + - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) + - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) + - Add more tests for calendar contribution (ClemMakesApps) + - Update Gitlab Shell to fix some problems with moving projects between storages + - Cache rendered markdown in the database, rather than Redis + - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references + - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified + - Simplify Mentionable concern instance methods + - API: Ability to retrieve version information (Robert Schilling) + - Fix permission for setting an issue's due date + - API: Multi-file commit !6096 (mahcsig) + - Unicode emoji are now converted to images + - Revert "Label list shows all issues (opened or closed) with that label" + - Expose expires_at field when sharing project on API + - Fix VueJS template tags being rendered in code comments + - Added copy file path button to merge request diff files + - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) + - Add Issue Board API support (andrebsguedes) + - Allow the Koding integration to be configured through the API + - Add new issue button to each list on Issues Board + - Added soft wrap button to repository file/blob editor + - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) + - Show the time ago a merge request was deployed to an environment + - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) + - Fix todos page mobile viewport layout (ClemMakesApps) + - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) + - Remove redundant mixins (ClemMakesApps) + - Added 'Download' button to the Snippets page (Justin DiPierro) + - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) + - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) + - Fix that manual jobs would no longer block jobs in the next stage. !6604 + - Add configurable email subject suffix (Fu Xu) + - Use defined colour for a language when available !6748 (nilsding) + - Added tooltip to fork count on project show page. (Justin DiPierro) + - Use a ConnectionPool for Rails.cache on Sidekiq servers + - Replace `alias_method_chain` with `Module#prepend` + - Enable GitLab Import/Export for non-admin users. + - Preserve label filters when sorting !6136 (Joseph Frazier) + - MergeRequest#new form load diff asynchronously + - Only update issuable labels if they have been changed + - Take filters in account in issuable counters. !6496 + - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) + - Prevent flash alert text from being obscured when container is fluid + - Append issue template to existing description !6149 (Joseph Frazier) + - Trending projects now only show public projects and the list of projects is cached for a day + - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) + - Revoke button in Applications Settings underlines on hover. + - Use higher size on Gitlab::Redis connection pool on Sidekiq servers + - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) + - Fix Long commit messages overflow viewport in file tree + - Revert avoid touching file system on Build#artifacts? + - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created + - Add disabled delete button to protected branches (ClemMakesApps) + - Add broadcast messages and alerts below sub-nav + - Better empty state for Groups view + - API: New /users/:id/events endpoint + - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) + - Replace bootstrap caret with fontawesome caret (ClemMakesApps) + - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 + - Add organization field to user profile + - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages + - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) + - Fix deploy status responsiveness error !6633 + - Make searching for commits case insensitive + - Fix resolved discussion display in side-by-side diff view !6575 + - Optimize GitHub importing for speed and memory + - API: expose pipeline data in builds API (!6502, Guilherme Salazar) + - Notify the Merger about merge after successful build (Dimitris Karakasilis) + - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) + - Reduce queries needed to find users using their SSH keys when pushing commits + - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) + - Fix broken repository 500 errors in project list + - Fix Pipeline list commit column width should be adjusted + - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) + - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) + - Retouch environments list and deployments list + - Add multiple command support for all label related slash commands !6780 (barthc) + - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Allow empty merge requests !6384 (Artem Sidorenko) + - Grouped pipeline dropdown is a scrollable container + - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) + - Fixes padding in all clipboard icons that have .btn class + - Fix a typo in doc/api/labels.md + - API: all unknown routing will be handled with 404 Not Found + - Add docs for request profiling + - Make guests unable to view MRs on private projects + +## 8.12.7 + + - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + - Fix GFM autocomplete setup being called several times + +## 8.12.6 + + - Update mailroom to 0.8.1 in Gemfile.lock !6814 + +## 8.12.5 + + - Switch from request to env in ::API::Helpers. !6615 + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 + - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + +## 8.12.4 + + - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) + - Fix padding in build sidebar. !6506 + - Changed compare dropdowns to dropdowns with isolated search input. !6550 + - Fix race condition on LFS Token. !6592 + - Fix type mismatch bug when closing Jira issue. !6619 + - Fix lint-doc error. !6623 + - Skip wiki creation when GitHub project has wiki enabled. !6665 + - Fix issues importing services via Import/Export. !6667 + - Restrict failed login attempts for users with 2FA enabled. !6668 + - Fix failed project deletion when feature visibility set to private. !6688 + - Prevent claiming associated model IDs via import. + - Set GitLab project exported file permissions to owner only + - Improve the way merge request versions are compared with each other + +## 8.12.3 + + - Update Gitlab Shell to support low IO priority for storage moves + +## 8.12.2 + + - Fix Import/Export not recognising correctly the imported services. + - Fix snippets pagination + - Fix "Create project" button layout when visibility options are restricted + - Fix List-Unsubscribe header in emails + - Fix IssuesController#show degradation including project on loaded notes + - Fix an issue with the "Commits" section of the cycle analytics summary. !6513 + - Fix errors importing project feature and milestone models using GitLab project import + - Make JWT messages Docker-compatible + - Fix duplicate branch entry in the merge request version compare dropdown + - Respect the fork_project permission when forking projects + - Only update issuable labels if they have been changed + - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) + - Fix resolve discussion buttons endpoint path + - Refactor remnants of CoffeeScript destructured opts and super !6261 + +## 8.12.1 + + - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST + - Fix issue with search filter labels not displaying + +## 8.12.0 (2016-09-22) + + - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 + - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 + - Only check :can_resolve permission if the note is resolvable + - Bump fog-aws to v0.11.0 to support ap-south-1 region + - Add ability to fork to a specific namespace using API. (ritave) + - Allow to set request_access_enabled for groups and projects + - Cleanup misalignments in Issue list view !6206 + - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist + - Add Pipelines for Commit + - Prune events older than 12 months. (ritave) + - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) + - Fix issues/merge-request templates dropdown for forked projects + - Filter tags by name !6121 + - Update gitlab shell secret file also when it is empty. !3774 (glensc) + - Give project selection dropdowns responsive width, make non-wrapping. + - Fix note form hint showing slash commands supported for commits. + - Make push events have equal vertical spacing. + - API: Ensure invitees are not returned in Members API. + - Preserve applied filters on issues search. + - Add two-factor recovery endpoint to internal API !5510 + - Pass the "Remember me" value to the U2F authentication form + - Display stages in valid order in stages dropdown on build page + - Only update projects.last_activity_at once per hour when creating a new event + - Cycle analytics (first iteration) !5986 + - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) + - Move pushes_since_gc from the database to Redis + - Limit number of shown environments on Merge Request: show only environments for target_branch, source_branch and tags + - Add font color contrast to external label in admin area (ClemMakesApps) + - Fix find file navigation links (ClemMakesApps) + - Change logo animation to CSS (ClemMakesApps) + - Instructions for enabling Git packfile bitmaps !6104 + - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint + - Fix long comments in diffs messing with table width + - Add spec covering 'Gitlab::Git::committer_hash' !6433 (dandunckelman) + - Fix pagination on user snippets page + - Honor "fixed layout" preference in more places !6422 + - Run CI builds with the permissions of users !5735 + - Fix sorting of issues in API + - Fix download artifacts button links !6407 + - Sort project variables by key. !6275 (Diego Souza) + - Ensure specs on sorting of issues in API are deterministic on MySQL + - Added ability to use predefined CI variables for environment name + - Added ability to specify URL in environment configuration in gitlab-ci.yml + - Escape search term before passing it to Regexp.new !6241 (winniehell) + - Fix pinned sidebar behavior in smaller viewports !6169 + - Fix file permissions change when updating a file on the Gitlab UI !5979 + - Added horizontal padding on build page sidebar on code coverage block. !6196 (Vitaly Baev) + - Change merge_error column from string to text type + - Fix issue with search filter labels not displaying + - Reduce contributions calendar data payload (ClemMakesApps) + - Show all pipelines for merge requests even from discarded commits !6414 + - Replace contributions calendar timezone payload with dates (ClemMakesApps) + - Changed MR widget build status to pipeline status !6335 + - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Enable pipeline events by default !6278 + - Move parsing of sidekiq ps into helper !6245 (pascalbetz) + - Added go to issue boards keyboard shortcut + - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) + - Emoji can be awarded on Snippets !4456 + - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Fix blame table layout width + - Spec testing if issue authors can read issues on private projects + - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) + - Request only the LDAP attributes we need !6187 + - Center build stage columns in pipeline overview (ClemMakesApps) + - Fix bug with tooltip not hiding on discussion toggle button + - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) + - Fix bug stopping issue description being scrollable after selecting issue template + - Remove suggested colors hover underline (ClemMakesApps) + - Fix jump to discussion button being displayed on commit notes + - Shorten task status phrase (ClemMakesApps) + - Fix project visibility level fields on settings + - Add hover color to emoji icon (ClemMakesApps) + - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files + - Add textarea autoresize after comment (ClemMakesApps) + - Do not write SSH public key 'comments' to authorized_keys !6381 + - Add due date to issue todos + - Refresh todos count cache when an Issue/MR is deleted + - Fix branches page dropdown sort alignment (ClemMakesApps) + - Hides merge request button on branches page is user doesn't have permissions + - Add white background for no readme container (ClemMakesApps) + - API: Expose issue confidentiality flag. (Robert Schilling) + - Fix markdown anchor icon interaction (ClemMakesApps) + - Test migration paths from 8.5 until current release !4874 + - Replace animateEmoji timeout with eventListener (ClemMakesApps) + - Show badges in Milestone tabs. !5946 (Dan Rowden) + - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) + - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) + - Add `wiki_page_events` to project hook APIs (Ben Boeckel) + - Remove Gitorious import + - Loads GFM autocomplete source only when required + - Fix issue with slash commands not loading on new issue page + - Fix inconsistent background color for filter input field (ClemMakesApps) + - Remove prefixes from transition CSS property (ClemMakesApps) + - Add Sentry logging to API calls + - Add BroadcastMessage API + - Merge request tabs are fixed when scrolling page + - Use 'git update-ref' for safer web commits !6130 + - Sort pipelines requested through the API + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Fix issue boards loading on large screens + - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 + - Show queued time when showing a pipeline !6084 + - Remove unused mixins (ClemMakesApps) + - Fix issue board label filtering appending already filtered labels + - Add search to all issue board lists + - Scroll active tab into view on mobile + - Fix groups sort dropdown alignment (ClemMakesApps) + - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) + - Use JavaScript tooltips for mentions !5301 (winniehell) + - Add hover state to todos !5361 (winniehell) + - Fix icon alignment of star and fork buttons !5451 (winniehell) + - Fix alignment of icon buttons !5887 (winniehell) + - Added Ubuntu 16.04 support for packager.io (JonTheNiceGuy) + - Fix markdown help references (ClemMakesApps) + - Add last commit time to repo view (ClemMakesApps) + - Fix accessibility and visibility of project list dropdown button !6140 + - Fix missing flash messages on service edit page (airatshigapov) + - Added project-specific enable/disable setting for LFS !5997 + - Added group-specific enable/disable setting for LFS !6164 + - Add optional 'author' param when making commits. !5822 (dandunckelman) + - Don't expose a user's token in the `/api/v3/user` API (!6047) + - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) + - Ability to manage project issues, snippets, wiki, merge requests and builds access level + - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) + - Align add button on repository view (ClemMakesApps) + - Fix contributions calendar month label truncation (ClemMakesApps) + - Import release note descriptions from GitHub (EspadaV8) + - Added tests for diff notes + - Add pipeline events to Slack integration !5525 + - Add a button to download latest successful artifacts for branches and tags !5142 + - Remove redundant pipeline tooltips (ClemMakesApps) + - Expire commit info views after one day, instead of two weeks, to allow for user email updates + - Add delimiter to project stars and forks count (ClemMakesApps) + - Fix badge count alignment (ClemMakesApps) + - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) + - Fix repo title alignment (ClemMakesApps) + - Change update interval of contacted_at + - Add LFS support to SSH !6043 + - Fix branch title trailing space on hover (ClemMakesApps) + - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) + - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) + - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) + - Order award emoji tooltips in order they were added (EspadaV8) + - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) + - Update merge_requests.md with a simpler way to check out a merge request. !5944 + - Fix button missing type (ClemMakesApps) + - Gitlab::Checks is now instrumented + - Move to project dropdown with infinite scroll for better performance + - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) + - Load branches asynchronously in Cherry Pick and Revert dialogs. + - Convert datetime coffeescript spec to ES6 (ClemMakesApps) + - Add merge request versions !5467 + - Change using size to use count and caching it for number of group members. !5935 + - Replace play icon font with svg (ClemMakesApps) + - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) + - Reduce number of database queries on builds tab + - Wrap text in commit message containers + - Capitalize mentioned issue timeline notes (ClemMakesApps) + - Fix inconsistent checkbox alignment (ClemMakesApps) + - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) + - Adds response mime type to transaction metric action when it's not HTML + - Fix hover leading space bug in pipeline graph !5980 + - Avoid conflict with admin labels when importing GitHub labels + - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 + - Fix repository page ui issues + - Avoid protected branches checks when verifying access without branch name + - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) + - Fixed invisible scroll controls on build page on iPhone + - Fix error on raw build trace download for old builds stored in database !4822 + - Refactor the triggers page and documentation !6217 + - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) + - Use default clone protocol on "check out, review, and merge locally" help page URL + - Let the user choose a namespace and name on GitHub imports + - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) + - Allow bulk update merge requests from merge requests index page + - Ensure validation messages are shown within the milestone form + - Add notification_settings API calls !5632 (mahcsig) + - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) + - Fix URLs with anchors in wiki !6300 (houqp) + - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) + - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 + - Fix Gitlab::Popen.popen thread-safety issue + - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) + - Clean environment variables when running git hooks + - Fix Import/Export issues importing protected branches and some specific models + - Fix non-master branch readme display in tree view + - Add UX improvements for merge request version diffs + +## 8.11.9 + + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + +## 8.11.8 + + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + +## 8.11.7 + + - Avoid conflict with admin labels when importing GitHub labels. !6158 + - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 + - Allow the Rails cookie to be used for API authentication. + - Login/Register UX upgrade !6328 + +## 8.11.6 + + - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 + - Make merge conflict file size limit 200 KB, to match the docs. !6052 + - Fix an error where we were unable to create a CommitStatus for running state. !6107 + - Optimize discussion notes resolving and unresolving. !6141 + - Fix GitLab import button. !6167 + - Restore SSH Key title auto-population behavior. !6186 + - Fix DB schema to match latest migration. !6256 + - Exclude some pending or inactivated rows in Member scopes. + +## 8.11.5 + + - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 + - Fix member expiration date picker after update. !6184 + - Fix suggested colors options for new labels in the admin area. !6138 + - Optimize discussion notes resolving and unresolving + - Fix GitLab import button + - Fix confidential issues being exposed as public using gitlab.com export + - Remove gitorious from import_sources. !6180 + - Scope webhooks/services that will run for confidential issues + - Remove gitorious from import_sources + - Fix confidential issues being exposed as public using gitlab.com export + - Use oj gem for faster JSON processing + +## 8.11.4 + + - Fix resolving conflicts on forks. !6082 + - Fix diff commenting on merge requests created prior to 8.10. !6029 + - Fix pipelines tab layout regression. !5952 + - Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057 + - Do not enforce using hash with hidden key in CI configuration. !6079 + - Fix hover leading space bug in pipeline graph !5980 + - Fix sorting issues by "last updated" doesn't work after import from GitHub + - GitHub importer use default project visibility for non-private projects + - Creating an issue through our API now emails label subscribers !5720 + - Block concurrent updates for Pipeline + - Don't create groups for unallowed users when importing projects + - Fix issue boards leak private label names and descriptions + - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) + - Remove gitorious. !5866 + - Allow compare merge request versions + +## 8.11.3 + + - Allow system info page to handle case where info is unavailable + - Label list shows all issues (opened or closed) with that label + - Don't show resolve conflicts link before MR status is updated + - Fix IE11 fork button bug !5982 + - Don't prevent viewing the MR when git refs for conflicts can't be found on disk + - Fix external issue tracker "Issues" link leading to 404s + - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters + - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Issues filters reset button + +## 8.11.2 + + - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 + - Use gitlab-workhorse 0.7.11 !5983 + - Does not halt the GitHub import process when an error occurs. !5763 + - Fix file links on project page when default view is Files !5933 + - Fixed enter key in search input not working !5888 + +## 8.11.1 + + - Pulled due to packaging error. + +## 8.11.0 (2016-08-22) + + - Use test coverage value from the latest successful pipeline in badge. !5862 + - Add test coverage report badge. !5708 + - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar) + - Add Koding (online IDE) integration + - Ability to specify branches for Pivotal Tracker integration (Egor Lynko) + - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) + - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) + - Fix adding line comments on the initial commit to a repo !5900 + - Fix the title of the toggle dropdown button. !5515 (herminiotorres) + - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz) + - Update to Ruby 2.3.1. !4948 + - Add Issues Board !5548 + - Allow resolving merge conflicts in the UI !5479 + - Improve diff performance by eliminating redundant checks for text blobs + - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi) + - Convert switch icon into icon font (ClemMakesApps) + - API: Endpoints for enabling and disabling deploy keys + - API: List access requests, request access, approve, and deny access requests to a project or a group. !4833 + - Use long options for curl examples in documentation !5703 (winniehell) + - Added tooltip listing label names to the labels value in the collapsed issuable sidebar + - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) + - GitLab Performance Monitoring can now track custom events such as the number of tags pushed to a repository + - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) + - Allow naming U2F devices !5833 + - Ignore URLs starting with // in Markdown links !5677 (winniehell) + - Fix CI status icon link underline (ClemMakesApps) + - The Repository class is now instrumented + - Fix commit mention font inconsistency (ClemMakesApps) + - Do not escape URI when extracting path !5878 (winniehell) + - Fix filter label tooltip HTML rendering (ClemMakesApps) + - Cache the commit author in RequestStore to avoid extra lookups in PostReceive + - Expand commit message width in repo view (ClemMakesApps) + - Cache highlighted diff lines for merge requests + - Pre-create all builds for a Pipeline when the new Pipeline is created !5295 + - Allow merge request diff notes and discussions to be explicitly marked as resolved + - API: Add deployment endpoints + - API: Add Play endpoint on Builds + - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' + - Show wall clock time when showing a pipeline. !5734 + - Show member roles to all users on members page + - Project.visible_to_user is instrumented again + - Fix awardable button mutuality loading spinners (ClemMakesApps) + - Sort todos by date and priority + - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable + - Optimize maximum user access level lookup in loading of notes + - Send notification emails to users newly mentioned in issue and MR edits !5800 + - Add "No one can push" as an option for protected branches. !5081 + - Improve performance of AutolinkFilter#text_parse by using XPath + - Add experimental Redis Sentinel support !1877 + - Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB + - Fix branches page dropdown sort initial state (ClemMakesApps) + - Environments have an url to link to + - Various redundant database indexes have been removed + - Update `timeago` plugin to use multiple string/locale settings + - Remove unused images (ClemMakesApps) + - Get issue and merge request description templates from repositories + - Enforce 2FA restrictions on API authentication endpoints !5820 + - Limit git rev-list output count to one in forced push check + - Show deployment status on merge requests with external URLs + - Clean up unused routes (Josef Strzibny) + - Fix issue on empty project to allow developers to only push to protected branches if given permission + - API: Add enpoints for pipelines + - Add green outline to New Branch button. !5447 (winniehell) + - Optimize generating of cache keys for issues and notes + - Fix repository push email formatting in Outlook + - Improve performance of syntax highlighting Markdown code blocks + - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects + - Remove delay when hitting "Reply..." button on page with a lot of discussions + - Retrieve rendered HTML from cache in one request + - Fix renaming repository when name contains invalid chararacters under project settings + - Upgrade Grape from 0.13.0 to 0.15.0. !4601 + - Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries + - Fix devise deprecation warnings. + - Check for 2FA when using Git over HTTP and only allow PersonalAccessTokens as password in that case !5764 + - Update version_sorter and use new interface for faster tag sorting + - Optimize checking if a user has read access to a list of issues !5370 + - Store all DB secrets in secrets.yml, under descriptive names !5274 + - Fix syntax highlighting in file editor + - Support slash commands in issue and merge request descriptions as well as comments. !5021 + - Nokogiri's various parsing methods are now instrumented + - Add archived badge to project list !5798 + - Add simple identifier to public SSH keys (muteor) + - Admin page now references docs instead of a specific file !5600 (AnAverageHuman) + - Fix filter input alignment (ClemMakesApps) + - Include old revision in merge request update hooks (Ben Boeckel) + - Add build event color in HipChat messages (David Eisner) + - Make fork counter always clickable. !5463 (winniehell) + - Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon) + - Gitlab::Highlight is now instrumented + - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 + - Allow users to import cross-repository pull requests from GitHub + - The overhead of instrumented method calls has been reduced + - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) + - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` + - Add pipeline events hook + - Bump gitlab_git to speedup DiffCollection iterations + - Rewrite description of a blocked user in admin settings. (Elias Werberich) + - Make branches sortable without push permission !5462 (winniehell) + - Check for Ci::Build artifacts at database level on pipeline partial + - Convert image diff background image to CSS (ClemMakesApps) + - Remove unnecessary index_projects_on_builds_enabled index from the projects table + - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) + - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration + - Fix search for notes which belongs to deleted objects + - Allow Akismet to be trained by submitting issues as spam or ham !5538 + - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) + - Allow branch names ending with .json for graph and network page !5579 (winniehell) + - Add the `sprockets-es6` gem + - Improve OAuth2 client documentation (muteor) + - Fix diff comments inverted toggle bug (ClemMakesApps) + - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) + - Profile requests when a header is passed + - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. + - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible + - Add commit stats in commit api. !5517 (dixpac) + - Add CI configuration button on project page + - Fix merge request new view not changing code view rendering style + - edit_blob_link will use blob passed onto the options parameter + - Make error pages responsive (Takuya Noguchi) + - The performance of the project dropdown used for moving issues has been improved + - Fix skip_repo parameter being ignored when destroying a namespace + - Add all builds into stage/job dropdowns on builds page + - Change requests_profiles resource constraint to catch virtually any file + - Bump gitlab_git to lazy load compare commits + - Reduce number of queries made for merge_requests/:id/diffs + - Add the option to set the expiration date for the project membership when giving a user access to a project. !5599 (Adam Niedzielski) + - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) + - Fix bug where destroying a namespace would not always destroy projects + - Fix RequestProfiler::Middleware error when code is reloaded in development + - Allow horizontal scrolling of code blocks in issue body + - Catch what warden might throw when profiling requests to re-throw it + - Avoid commit lookup on diff_helper passing existing local variable to the helper method + - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac) + - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker + - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) + - Adds support for pending invitation project members importing projects + - Add pipeline visualization/graph on pipeline page + - Update devise initializer to turn on changed password notification emails. !5648 (tombell) + - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro) + - Fix importing GitLab projects with an invalid MR source project + - Sort folders with submodules in Files view !5521 + - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0 + - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska) + - Add pipelines tab to merge requests + - Fix notification_service argument error of declined invitation emails + - Fix a memory leak caused by Banzai::Filter::SanitizationFilter + - Speed up todos queries by limiting the projects set we join with + - Ensure file editing in UI does not overwrite commited changes without warning user + - Eliminate unneeded calls to Repository#blob_at when listing commits with no path + - Update gitlab_git gem to 10.4.7 + - Simplify SQL queries of marking a todo as done + +## 8.10.12 + + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + +## 8.10.11 + + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + +## 8.10.10 + + - Allow the Rails cookie to be used for API authentication. + +## 8.10.9 + + - Exclude some pending or inactivated rows in Member scopes + +## 8.10.8 + + - Fix information disclosure in issue boards. + - Fix privilege escalation in project import. + +## 8.10.7 + + - Upgrade Hamlit to 2.6.1. !5873 + - Upgrade Doorkeeper to 4.2.0. !5881 + +## 8.10.6 + + - Upgrade Rails to 4.2.7.1 for security fixes. !5781 + - Restore "Largest repository" sort option on Admin > Projects page. !5797 + - Fix privilege escalation via project export. + - Require administrator privileges to perform a project import. + +## 8.10.5 + + - Add a data migration to fix some missing timestamps in the members table. !5670 + - Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706 + - Cache project count for 5 minutes to reduce DB load. !5746 & !5754 + +## 8.10.4 + + - Don't close referenced upstream issues from a forked project. + - Fixes issue with dropdowns `enter` key not working correctly. !5544 + - Fix Import/Export project import not working in HA mode. !5618 + - Fix Import/Export error checking versions. !5638 + +## 8.10.3 + + - Fix Import/Export issue importing milestones and labels not associated properly. !5426 + - Fix timing problems running imports on production. !5523 + - Add a log message when a project is scheduled for destruction for debugging. !5540 + - Fix hooks missing on imported GitLab projects. !5549 + - Properly abort a merge when merge conflicts occur. !5569 + - Fix importer for GitHub Pull Requests when a branch was removed. !5573 + - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584 + - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 + - Fix label already exist error message in the right sidebar. + +## 8.10.2 + + - User can now search branches by name. !5144 + - Page is now properly rendered after committing the first file and creating the first branch. !5399 + - Add branch or tag icon to ref in builds page. !5434 + - Fix backup restore. !5459 + - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 + - Fix issue with autocomplete search not working with enter key. !5466 + - Add iid to MR API response. !5468 + - Disable MySQL foreign key checks before dropping all tables. !5472 + - Ensure relative paths for video are rewritten as we do for images. !5474 + - Ensure current user can retry a build before showing the 'Retry' button. !5476 + - Add ENV variable to skip repository storages validations. !5478 + - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486 + - Don't show comment button in gutter of diffs on MR discussion tab. !5493 + - Rescue Rugged::OSError (lock exists) when creating references. !5497 + - Fix expand all diffs button in compare view. !5500 + - Show release notes in tags list. !5503 + - Fix a bug where forking a project from a repository storage to another would fail. !5509 + - Fix missing schema update for `20160722221922`. !5512 + - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516 + +## 8.10.1 + + - Refactor repository storages documentation. !5428 + - Gracefully handle case when keep-around references are corrupted or exist already. !5430 + - Add detailed info on storage path mountpoints. !5437 + - Fix Error 500 when creating Wiki pages with hyphens or spaces. !5444 + - Fix bug where replies to commit notes displayed in the MR discussion tab wouldn't show up on the commit page. !5446 + - Ignore invalid trusted proxies in X-Forwarded-For header. !5454 + - Add links to the real markdown.md file for all GFM examples. !5458 + +## 8.10.0 (2016-07-22) + + - Fix profile activity heatmap to show correct day name (eanplatter) + - Speed up ExternalWikiHelper#get_project_wiki_path + - Expose {should,force}_remove_source_branch (Ben Boeckel) + - Add the functionality to be able to rename a file. !5049 + - Disable PostgreSQL statement timeout during migrations + - Fix projects dropdown loading performance with a simplified api cal. !5113 + - Fix commit builds API, return all builds for all pipelines for given commit. !4849 + - Replace Haml with Hamlit to make view rendering faster. !3666 + - Refresh the branch cache after `git gc` runs + - Allow to disable request access button on projects/groups + - Refactor repository paths handling to allow multiple git mount points + - Optimize system note visibility checking by memoizing the visible reference count. !5070 + - Add Application Setting to configure default Repository Path for new projects + - Delete award emoji when deleting a user + - Remove pinTo from Flash and make inline flash messages look nicer. !4854 (winniehell) + - Add an API for downloading latest successful build from a particular branch or tag. !5347 + - Avoid data-integrity issue when cleaning up repository archive cache. + - Add link to profile to commit avatar. !5163 (winniehell) + - Wrap code blocks on Activies and Todos page. !4783 (winniehell) + - Align flash messages with left side of page content. !4959 (winniehell) + - Display tooltip for "Copy to Clipboard" button. !5164 (winniehell) + - Use default cursor for table header of project files. !5165 (winniehell) + - Store when and yaml variables in builds table + - Display last commit of deleted branch in push events. !4699 (winniehell) + - Escape file extension when parsing search results. !5141 (winniehell) + - Add "passing with warnings" to the merge request pipeline possible statuses, this happens when builds that allow failures have failed. !5004 + - Add image border in Markdown preview. !5162 (winniehell) + - Apply the trusted_proxies config to the rack request object for use with rack_attack + - Added the ability to block sign ups using a domain blacklist. !5259 + - Upgrade to Rails 4.2.7. !5236 + - Extend exposed environment variables for CI builds + - Deprecate APIs "projects/:id/keys/...". Use "projects/:id/deploy_keys/..." instead + - Add API "deploy_keys" for admins to get all deploy keys + - Allow to pull code with deploy key from public projects + - Use limit parameter rather than hardcoded value in `ldap:check` rake task (Mike Ricketts) + - Add Sidekiq queue duration to transaction metrics. + - Add a new column `artifacts_size` to table `ci_builds`. !4964 + - Let Workhorse serve format-patch diffs + - Display tooltip for mentioned users and groups. !5261 (winniehell) + - Allow build email service to be tested + - Added day name to contribution calendar tooltips + - Refactor user authorization check for a single project to avoid querying all user projects + - Make images fit to the size of the viewport. !4810 + - Fix check for New Branch button on Issue page. !4630 (winniehell) + - Fix GFM autocomplete not working on wiki pages + - Fixed enter key not triggering click on first row when searching in a dropdown + - Updated dropdowns in issuable form to use new GitLab dropdown style + - Make images fit to the size of the viewport !4810 + - Fix check for New Branch button on Issue page !4630 (winniehell) + - Fix MR-auto-close text added to description. !4836 + - Support U2F devices in Firefox. !5177 + - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) + - Add Spring EmojiOne updates. + - Added Rake task for tracking deployments. !5320 + - Fix fetching LFS objects for private CI projects + - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237 + - Add syntax for multiline blockquote using `>>>` fence. !3954 + - Fix viewing notification settings when a project is pending deletion + - Updated compare dropdown menus to use GL dropdown + - Redirects back to issue after clicking login link + - Eager load award emoji on notes + - Allow to define manual actions/builds on Pipelines and Environments + - Fix pagination when sorting by columns with lots of ties (like priority) + - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 + - Updated project header design + - Issuable collapsed assignee tooltip is now the users name + - Fix compare view not changing code view rendering style + - Exclude email check from the standard health check + - Updated layout for Projects, Groups, Users on Admin area. !4424 + - Fix changing issue state columns in milestone view + - Update health_check gem to version 2.1.0 + - Add notification settings dropdown for groups + - Render inline diffs for multiple changed lines following eachother + - Wildcards for protected branches. !4665 + - Allow importing from Github using Personal Access Tokens. (Eric K Idema) + - API: Expose `due_date` for issues (Robert Schilling) + - API: Todos. !3188 (Robert Schilling) + - API: Expose shared groups for projects and shared projects for groups. !5050 (Robert Schilling) + - API: Expose `developers_can_push` and `developers_can_merge` for branches. !5208 (Robert Schilling) + - Add "Enabled Git access protocols" to Application Settings + - Diffs will create button/diff form on demand no on server side + - Reduce size of HTML used by diff comment forms + - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard) + - Fix user creation with stronger minimum password requirements. !4054 (nathan-pmt) + - Only show New Snippet button to users that can create snippets. + - PipelinesFinder uses git cache data + - Track a user who created a pipeline + - Actually render old and new sections of parallel diff next to each other + - Throttle the update of `project.pushes_since_gc` to 1 minute. + - Allow expanding and collapsing files in diff view. !4990 + - Collapse large diffs by default (!4990) + - Fix mentioned users list on diff notes + - Add support for inline videos in GitLab Flavored Markdown. !5215 (original implementation by Eric Hayes) + - Fix creation of deployment on build that is retried, redeployed or rollback + - Don't parse Rinku returned value to DocFragment when it didn't change the original html string. + - Check for conflicts with existing Project's wiki path when creating a new project. + - Show last push widget in upstream after push to fork + - Fix stage status shown for pipelines + - Cache todos pending/done dashboard query counts. + - Don't instantiate a git tree on Projects show default view + - Bump Rinku to 2.0.0 + - Remove unused front-end variable -> default_issues_tracker + - ObjectRenderer retrieve renderer content using Rails.cache.read_multi + - Better caching of git calls on ProjectsController#show. + - Avoid to retrieve MR closes_issues as much as possible. + - Hide project name in project activities. !5068 (winniehell) + - Add API endpoint for a group issues. !4520 (mahcsig) + - Add Bugzilla integration. !4930 (iamtjg) + - Fix new snippet style bug (elliotec) + - Instrument Rinku usage + - Be explicit to define merge request discussion variables + - Use cache for todos counter calling TodoService + - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab + - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info. + - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w) + - Made project list visibility icon fixed width + - Set import_url validation to be more strict + - Memoize MR merged/closed events retrieval + - Don't render discussion notes when requesting diff tab through AJAX + - Add basic system information like memory and disk usage to the admin panel + - Don't garbage collect commits that have related DB records like comments + - Allow to setup event by channel on slack service + - More descriptive message for git hooks and file locks + - Aliases of award emoji should be stored as original name. !5060 (dixpac) + - Handle custom Git hook result in GitLab UI + - Allow to access Container Registry for Public and Internal projects + - Allow '?', or '&' for label names + - Support redirected blobs for Container Registry integration + - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests + - Add date when user joined the team on the member page + - Fix 404 redirect after validation fails importing a GitLab project + - Added setting to set new users by default as external. !4545 (Dravere) + - Add min value for project limit field on user's form. !3622 (jastkand) + - Reset project pushes_since_gc when we enqueue the git gc call + - Add reminder to not paste private SSH keys. !4399 (Ingo Blechschmidt) + - Collapsed diffs lines/size don't acumulate to overflow diffs. + - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel) + - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay) + - Fix GitHub client requests when rate limit is disabled + - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention) + - Redesign Builds and Pipelines pages + - Change status color and icon for running builds + - Fix commenting issue in side by side diff view for unchanged lines + - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.` + - Project export filename now includes the project and namespace path + - Fix last update timestamp on issues not preserved on gitlab.com and project imports + - Fix issues importing projects from EE to CE + - Fix creating group with space in group path + - Improve cron_jobs loading error messages. !5318 / !5360 + - Prevent toggling sidebar when clipboard icon clicked + - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) + - Limit the number of retries on error to 3 for exporting projects + - Allow empty repositories on project import/export + - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska) + - Allow bulk (un)subscription from issues in issue index + - Fix MR diff encoding issues exporting GitLab projects + - Move builds settings out of project settings and rename Pipelines + - Add builds badge to Pipelines settings page + - Export and import avatar as part of project import/export + - Fix migration corrupting import data for old version upgrades + - Show tooltip on GitLab export link in new project page + - Fix import_data wrongly saved as a result of an invalid import_url !5206 + +## 8.9.11 + + - Respect the fork_project permission when forking projects + - Set a restrictive CORS policy on the API for credentialed requests + - API: disable rails session auth for non-GET/HEAD requests + - Escape HTML nodes in builds commands in CI linter + +## 8.9.10 + + - Allow the Rails cookie to be used for API authentication. + +## 8.9.9 + + - Exclude some pending or inactivated rows in Member scopes + +## 8.9.8 + + - Upgrade Doorkeeper to 4.2.0. !5881 + +## 8.9.7 + + - Upgrade Rails to 4.2.7.1 for security fixes. !5781 + - Require administrator privileges to perform a project import. + +## 8.9.6 + + - Fix importing of events under notes for GitLab projects. !5154 + - Fix log statements in import/export. !5129 + - Fix commit avatar alignment in compare view. !5128 + - Fix broken migration in MySQL. !5005 + - Overwrite Host and X-Forwarded-Host headers in NGINX !5213 + - Keeps issue number when importing from Gitlab.com + - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) + +## 8.9.5 + + - Add more debug info to import/export and memory killer. !5108 + - Fixed avatar alignment in new MR view. !5095 + - Fix diff comments not showing up in activity feed. !5069 + - Add index on both Award Emoji user and name. !5061 + - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056 + - Re-enable import button when import process fails due to namespace already being taken. !5053 + - Fix snippets comments not displayed. !5045 + - Fix emoji paths in relative root configurations. !5027 + - Fix issues importing events in Import/Export. !4987 + - Fixed 'use shortcuts' button on docs. !4979 + - Admin should be able to turn shared runners into specific ones. !4961 + - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi) + - Improve the request / withdraw access button. !4860 + +## 8.9.4 + + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + - Fixed search field blur not removing focus. !4704 + - Resolve "Sub nav isn't showing on file view". !4890 + - Fixes middle click and double request when navigating through the file browser. !4891 + - Fixed URL on label button when filtering. !4897 + - Fixed commit avatar alignment. !4933 + - Do not show build retry link when build is active. !4967 + - Fix restore Rake task warning message output. !4980 + - Handle external issues in IssueReferenceFilter. !4988 + - Expiry date on pinned nav cookie. !5009 + - Updated breakpoint for sidebar pinning. !5019 + +## 8.9.3 + + - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963 + - Fix rendering of commit notes. !4953 + - Resolve "Pin should show up at 1280px min". !4947 + - Switched mobile button icons to ellipsis and angle. !4944 + - Correctly returns todo ID after creating todo. !4941 + - Better debugging for memory killer middleware. !4936 + - Remove duplicate new page btn from edit wiki. !4904 + - Use clock_gettime for all performance timestamps. !4899 + - Use memorized tags array when searching tags by name. !4859 + - Fixed avatar alignment in new MR view. !4901 + - Removed fade when filtering results. !4932 + - Fix missing avatar on system notes. !4954 + - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973 + - Use update_columns to bypass all the dirty code on active_record. !4985 + - Fix restore Rake task warning message output !4980 + +## 8.9.2 + + - Fix visibility of snippets when searching. + - Fix an information disclosure when requesting access to a group containing private projects. + - Update omniauth-saml to 1.6.0 !4951 + +## 8.9.1 + + - Refactor labels documentation. !3347 + - Eager load award emoji on notes. !4628 + - Fix some CI wording in documentation. !4660 + - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720 + - Add documentation for the export & import features. !4732 + - Add some docs for Docker Registry configuration. !4738 + - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744 + - Display group/project access requesters separately in the admin area. !4798 + - Add documentation and examples for configuring cloud storage for registry images. !4812 + - Clarifies documentation about artifact expiry. !4831 + - Fix the Network graph links. !4832 + - Fix MR-auto-close text added to description. !4836 + - Add documentation for award emoji now that comments can be awarded with emojis. !4839 + - Fix typo in export failure email. !4847 + - Fix header vertical centering. !4170 + - Fix subsequent SAML sign ins. !4718 + - Set button label when picking an option from status dropdown. !4771 + - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775 + - Handle external issues in IssueReferenceFilter. !4789 + - Support for rendering/redacting multiple documents. !4828 + - Update Todos documentation and screenshots to include new functionality. !4840 + - Hide nav arrows by default. !4843 + - Added bottom padding to label color suggestion link. !4845 + - Use jQuery objects in ref dropdown. !4850 + - Fix GitLab project import issues related to notes and builds. !4855 + - Restrict header logo to 36px so it doesn't overflow. !4861 + - Fix unwanted label unassignment. !4863 + - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869 + - Restore old behavior around diff notes to outdated discussions. !4870 + - Fix merge requests project settings help link anchor. !4873 + - Fix 404 when accessing pipelines as guest user on public projects. !4881 + - Remove width restriction for logo on sign-in page. !4888 + - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884 + - Apply selected value as label. !4886 + - Change Retry to Re-deploy on Deployments page + - Fix temp file being deleted after the request while importing a GitLab project. !4894 + - Fix pagination when sorting by columns with lots of ties (like priority) + - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise. + - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) + - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912 + - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915 + - Remove duplicate 'New Page' button on edit wiki page + +## 8.9.0 (2016-06-22) + + - Fix group visibility form layout in application settings + - Fix builds API response not including commit data + - Fix error when CI job variables key specified but not defined + - Fix pipeline status when there are no builds in pipeline + - Fix Error 500 when using closes_issues API with an external issue tracker + - Add more information into RSS feed for issues (Alexander Matyushentsev) + - Bulk assign/unassign labels to issues. + - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) + - Show Star and Fork buttons on mobile. + - Performance improvements on RelativeLinkFilter + - Fix endless redirections when accessing user OAuth applications when they are disabled + - Allow enabling wiki page events from Webhook management UI + - Bump rouge to 1.11.0 + - Fix issue with arrow keys not working in search autocomplete dropdown + - Fix an issue where note polling stopped working if a window was in the + background during a refresh. + - Pre-processing Markdown now only happens when needed + - Make EmailsOnPushWorker use Sidekiq mailers queue + - Redesign all Devise emails. !4297 + - Don't show 'Leave Project' to group members + - Fix wiki page events' webhook to point to the wiki repository + - Add a border around images to differentiate them from the background. + - Don't show tags for revert and cherry-pick operations + - Show image ID on registry page + - Fix issue todo not remove when leave project !4150 (Long Nguyen) + - Allow customisable text on the 'nearly there' page after a user signs up + - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support + - Fix SVG sanitizer to allow more elements + - Allow forking projects with restricted visibility level + - Added descriptions to notification settings dropdown + - Improve note validation to prevent errors when creating invalid note via API + - Reduce number of fog gem dependencies + - Add number of merge requests for a given milestone to the milestones view. + - Implement a fair usage of shared runners + - Remove project notification settings associated with deleted projects + - Fix 404 page when viewing TODOs that contain milestones or labels in different projects + - Add a metric for the number of new Redis connections created by a transaction + - Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark + - Redesign navigation for project pages + - Fix images in sign-up confirmation email + - Added shortcut 'y' for copying a files content hash URL #14470 + - Fix groups API to list only user's accessible projects + - Fix horizontal scrollbar for long commit message. + - GitLab Performance Monitoring now tracks the total method execution time and call count per method + - Add Environments and Deployments + - Redesign account and email confirmation emails + - Don't fail builds for projects that are deleted + - Support Docker Registry manifest v1 + - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix + - Bump nokogiri to 1.6.8 + - Use gitlab-shell v3.0.0 + - Fixed alignment of download dropdown in merge requests + - Upgrade to jQuery 2 + - Adds selected branch name to the dropdown toggle + - Add API endpoint for Sidekiq Metrics !4653 + - Refactoring Award Emoji with API support for Issues and MergeRequests + - Use Knapsack to evenly distribute tests across multiple nodes + - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged + - Don't allow MRs to be merged when commits were added since the last review / page load + - Add DB index on users.state + - Limit email on push diff size to 30 files / 150 KB + - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database + - Changed the Slack build message to use the singular duration if necessary (Aran Koning) + - Fix race condition on merge when build succeeds + - Added shortcut to focus filter search fields and added documentation #18120 + - Links from a wiki page to other wiki pages should be rewritten as expected + - Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos) + - Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393 + - Fix issues filter when ordering by milestone + - Disable SAML account unlink feature + - Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3 + - Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid) + - TeamCity Service: Fix URL handling when base URL contains a path + - Todos will display target state if issuable target is 'Closed' or 'Merged' + - Validate only and except regexp + - Fix bug when sorting issues by milestone due date and filtering by two or more labels + - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project + - Add support for using Yubikeys (U2F) for two-factor authentication + - Link to blank group icon doesn't throw a 404 anymore + - Remove 'main language' feature + - Toggle whitespace button now available for compare branches diffs #17881 + - Pipelines can be canceled only when there are running builds + - Allow authentication using personal access tokens + - Use downcased path to container repository as this is expected path by Docker + - Allow to use CI token to fetch LFS objects + - Custom notification settings + - Projects pending deletion will render a 404 page + - Measure queue duration between gitlab-workhorse and Rails + - Added Gfm autocomplete for labels + - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114 + - Make Omniauth providers specs to not modify global configuration + - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) + - Make authentication service for Container Registry to be compatible with < Docker 1.11 + - Make it possible to lock a runner from being enabled for other projects + - Add Application Setting to configure Container Registry token expire delay (default 5min) + - Cache assigned issue and merge request counts in sidebar nav + - Use Knapsack only in CI environment + - Updated project creation page to match new UI #2542 + - Cache project build count in sidebar nav + - Add milestone expire date to the right sidebar + - Manually mark a issue or merge request as a todo + - Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing + - Reduce number of queries needed to render issue labels in the sidebar + - Improve error handling importing projects + - Remove duplicated notification settings + - Put project Files and Commits tabs under Code tab + - Decouple global notification level from user model + - Replace Colorize with Rainbow for coloring console output in Rake tasks. + - Add workhorse controller and API helpers + - An indicator is now displayed at the top of the comment field for confidential issues. + - Show categorised search queries in the search autocomplete + - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented + - Dropdown for `.gitlab-ci.yml` templates + - Improve issuables APIs performance when accessing notes !4471 + - Add sorting dropdown to tags page !4423 + - External links now open in a new tab + - Prevent default actions of disabled buttons and links + - Markdown editor now correctly resets the input value on edit cancellation !4175 + - Toggling a task list item in a issue/mr description does not creates a Todo for mentions + - Improved UX of date pickers on issue & milestone forms + - Cache on the database if a project has an active external issue tracker. + - Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav + - GitLab project import and export functionality + - All classes in the Banzai::ReferenceParser namespace are now instrumented + - Remove deprecated issues_tracker and issues_tracker_id from project model + - Allow users to create confidential issues in private projects + - Measure CPU time for instrumented methods + - Instrument private methods and private instance methods by default instead just public methods + - Only show notes through JSON on confidential issues that the user has access to + - Updated the allocations Gem to version 1.0.5 + - The background sampler now ignores classes without names + - Update design for `Close` buttons + - New custom icons for navigation + - Horizontally scrolling navigation on project, group, and profile settings pages + - Hide global side navigation by default + - Fix project Star/Unstar project button tooltip + - Remove tanuki logo from side navigation; center on top nav + - Include user relationships when retrieving award_emoji + - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed + - Set inverse_of for Project/Service association to reduce the number of queries + - Update tanuki logo highlight/loading colors + - Remove explicit Gitlab::Metrics.action assignments, are already automatic. + - Use Git cached counters for branches and tags on project page + - Cache participable participants in an instance variable. + - Filter parameters for request_uri value on instrumented transactions. + - Remove duplicated keys add UNIQUE index to keys fingerprint column + - ExtractsPath get ref_names from repository cache, if not there access git. + - Show a flash warning about the error detail of XHR requests which failed with status code 404 and 500 + - Cache user todo counts from TodoService + - Ensure Todos counters doesn't count Todos for projects pending delete + - Add left/right arrows horizontal navigation + - Add tooltip to pin/unpin navbar + - Add new sub nav style to Wiki and Graphs sub navigation + +## 8.8.9 + + - Upgrade Doorkeeper to 4.2.0. !5881 + +## 8.8.8 + + - Upgrade Rails to 4.2.7.1 for security fixes. !5781 + +## 8.8.7 + + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +## 8.8.6 + + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + +## 8.8.5 + + - Import GitHub repositories respecting the API rate limit !4166 + - Fix todos page throwing errors when you have a project pending deletion !4300 + - Disable Webhooks before proceeding with the GitHub import !4470 + - Fix importer for GitHub comments on diff !4488 + - Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498 + - Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541 + - Prevent unauthorized access for projects build traces + - Forbid scripting for wiki files + - Only show notes through JSON on confidential issues that the user has access to + - Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions + - Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions + +## 8.8.4 + + - Fix LDAP-based login for users with 2FA enabled. !4493 + - Added descriptions to notification settings dropdown + - Due date can be removed from milestones + +## 8.8.3 + + - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 + - Fixed JS error when trying to remove discussion form. !4303 + - Fixed issue with button color when no CI enabled. !4287 + - Fixed potential issue with 2 CI status polling events happening. !3869 + - Improve design of Pipeline view. !4230 + - Fix gitlab importer failing to import new projects due to missing credentials. !4301 + - Fix import URL migration not rescuing with the correct Error. !4321 + - Fix health check access token changing due to old application settings being used. !4332 + - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363 + - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364 + - Pass the "Remember me" value to the 2FA token form. !4369 + - Fix incorrect links on pipeline page when merge request created from fork. !4376 + - Use downcased path to container repository as this is expected path by Docker. !4420 + - Fix wiki project clone address error (chujinjin). !4429 + - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392 + - Fix missing number on generated ordered list element. !4437 + - Prevent disclosure of notes on confidential issues in search results. + +## 8.8.2 + + - Added remove due date button. !4209 + - Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources. !4242 + - Fix Error 500 in CI charts by gracefully handling commits with no durations. !4245 + - Fix table UI on CI builds page. !4249 + - Fix backups if registry is disabled. !4263 + - Fixed issue with merge button color. !4211 + - Fixed issue with enter key selecting wrong option in dropdown. !4210 + - When creating a .gitignore file a dropdown with templates will be provided. !4075 + - Fix concurrent request when updating build log in browser. !4183 + +## 8.8.1 + + - Add documentation for the "Health Check" feature + - Allow anonymous users to access a public project's pipelines !4233 + - Fix MySQL compatibility in zero downtime migrations helpers + - Fix the CI login to Container Registry (the gitlab-ci-token user) + +## 8.8.0 (2016-05-22) + + - Implement GFM references for milestones (Alejandro Rodríguez) + - Snippets tab under user profile. !4001 (Long Nguyen) + - Fix error when using link to uploads in global snippets + - Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref + - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) + - Use a case-insensitive comparison in sanitizing URI schemes + - Toggle sign-up confirmation emails in application settings + - Make it possible to prevent tagged runner from picking untagged jobs + - Added `InlineDiffFilter` to the markdown parser. (Adam Butler) + - Added inline diff styling for `change_title` system notes. (Adam Butler) + - Project#open_branches has been cleaned up and no longer loads entire records into memory. + - Escape HTML in commit titles in system note messages + - Improve design of Pipeline View + - Fix scope used when accessing container registry + - Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios + - Improve multiple branch push performance by memoizing permission checking + - Log to application.log when an admin starts and stops impersonating a user + - Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi) + - Updated gitlab_git to 10.1.0 + - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists + - Reduce delay in destroying a project from 1-minute to immediately + - Make build status canceled if any of the jobs was canceled and none failed + - Upgrade Sidekiq to 4.1.2 + - Added /health_check endpoint for checking service status + - Make 'upcoming' filter for milestones work better across projects + - Sanitize repo paths in new project error message + - Bump mail_room to 0.7.0 to fix stuck IDLE connections + - Remove future dates from contribution calendar graph. + - Support e-mail notifications for comments on project snippets + - Fix API leak of notes of unauthorized issues, snippets and merge requests + - Use ActionDispatch Remote IP for Akismet checking + - Fix error when visiting commit builds page before build was updated + - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project + - Update SVG sanitizer to conform to SVG 1.1 + - Speed up push emails with multiple recipients by only generating the email once + - Updated search UI + - Added authentication service for Container Registry + - Display informative message when new milestone is created + - Sanitize milestones and labels titles + - Support multi-line tag messages. !3833 (Calin Seciu) + - Force users to reset their password after an admin changes it + - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) + - Added button to toggle whitespaces changes on diff view + - Backport GitHub Enterprise import support from EE + - Create tags using Rugged for performance reasons. !3745 + - Allow guests to set notification level in projects + - API: Expose Issue#user_notes_count. !3126 (Anton Popov) + - Don't show forks button when user can't view forks + - Fix atom feed links and rendering + - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 + - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) + - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 + - Added multiple colors for labels in dropdowns when dups happen. + - Show commits in the same order as `git log` + - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea) + - API support for the 'since' and 'until' operators on commit requests (Paco Guzman) + - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) + - Expire repository exists? and has_visible_content? caches after a push if necessary + - Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi) + - Fix adding a todo for private group members (Ahmad Sherif) + - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 + - Total method execution timings are no longer tracked + - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) + - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) + - Hide left sidebar on phone screens to give more space for content + - Redesign navigation for profile and group pages + - Add counter metrics for rails cache + - Import pull requests from GitHub where the source or target branches were removed + - All Grape API helpers are now instrumented + - Improve Issue formatting for the Slack Service (Jeroen van Baarsen) + - Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine) + - Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs) + - When creating a .gitignore file a dropdown with templates will be provided + - Shows the issue/MR list search/filter form and corrects the mobile styling for guest users. #17562 + +## 8.7.9 + + - Fix privilege escalation issue with OAuth external users. + - Ensure references to private repos aren't shown to logged-out users. + +## 8.7.8 + + - Fix visibility of snippets when searching. + - Update omniauth-saml to 1.6.0 !4951 + +## 8.7.7 + + - Fix import by `Any Git URL` broken if the URL contains a space + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + - Only show notes through JSON on confidential issues that the user has access to + +## 8.7.6 + + - Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko) + - Fix import from GitLab.com to a private instance failure. !4181 + - Fix external imports not finding the import data. !4106 + - Fix notification delay when changing status of an issue + - Bump Workhorse to 0.7.5 so it can serve raw diffs + +## 8.7.5 + + - Fix relative links in wiki pages. !4050 + - Fix always showing build notification message when switching between merge requests !4086 + - Fix an issue when filtering merge requests with more than one label. !3886 + - Fix short note for the default scope on build page (Takuya Noguchi) + +## 8.7.4 + + - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) + - Fix setting trusted proxies !3970 + - Fix BitBucket importer bug when throwing exceptions !3941 + - Use sign out path only if not empty !3989 + - Running rake gitlab:db:drop_tables now drops tables with cascade !4020 + - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100 + - Use a case-insensitive comparison in sanitizing URI schemes + +## 8.7.3 + + - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented + - Merge request widget displays TeamCity build state and code coverage correctly again. + - Fix the line code when importing PR review comments from GitHub. !4010 + - Wikis are now initialized on legacy projects when checking repositories + - Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea) + +## 8.7.2 + + - The "New Branch" button is now loaded asynchronously + - Fix error 500 when trying to create a wiki page + - Updated spacing between notification label and button + - Label titles in filters are now escaped properly + +## 8.7.1 + + - Throttle the update of `project.last_activity_at` to 1 minute. !3848 + - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849 + - Fix license detection to detect all license files, not only known licenses. !3878 + - Use the `can?` helper instead of `current_user.can?`. !3882 + - Prevent users from deleting Webhooks via API they do not own + - Fix Error 500 due to stale cache when projects are renamed or transferred + - Update width of search box to fix Safari bug. !3900 (Jedidiah) + - Use the `can?` helper instead of `current_user.can?` + +## 8.7.0 (2016-04-22) + + - Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented + - Fix vulnerability that made it possible to gain access to private labels and milestones + - The number of InfluxDB points stored per UDP packet can now be configured + - Fix error when cross-project label reference used with non-existent project + - Transactions for /internal/allowed now have an "action" tag set + - Method instrumentation now uses Module#prepend instead of aliasing methods + - Repository.clean_old_archives is now instrumented + - Add support for environment variables on a job level in CI configuration file + - SQL query counts are now tracked per transaction + - The Projects::HousekeepingService class has extra instrumentation + - All service classes (those residing in app/services) are now instrumented + - Developers can now add custom tags to transactions + - Loading of an issue's referenced merge requests and related branches is now done asynchronously + - Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea) + - Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan) + - Project switcher uses new dropdown styling + - Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea) + - Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles) + - Restrict user profiles when public visibility level is restricted. + - Add ability set due date to issues, sort and filter issues by due date (Mehmet Beydogan) + - All images in discussions and wikis now link to their source files !3464 (Connor Shea). + - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu) + - Add setting for customizing the list of trusted proxies !3524 + - Allow projects to be transfered to a lower visibility level group + - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524 + - Improved Markdown rendering performance !3389 + - Make shared runners text in box configurable + - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu) + - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling) + - Expose project badges in project settings + - Make /profile/keys/new redirect to /profile/keys for back-compat. !3717 + - Preserve time notes/comments have been updated at when moving issue + - Make HTTP(s) label consistent on clone bar (Stan Hu) + - Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński) + - Expose label description in API (Mariusz Jachimowicz) + - API: Ability to update a group (Robert Schilling) + - API: Ability to move issues (Robert Schilling) + - Fix Error 500 after renaming a project path (Stan Hu) + - Fix a bug whith trailing slash in teamcity_url (Charles May) + - Allow back dating on issues when created or updated through the API + - Allow back dating on issue notes when created through the API + - Propose license template when creating a new LICENSE file + - API: Expose /licenses and /licenses/:key + - Fix avatar stretching by providing a cropping feature + - API: Expose `subscribed` for issues and merge requests (Robert Schilling) + - Allow SAML to handle external users based on user's information !3530 + - Allow Omniauth providers to be marked as `external` !3657 + - Add endpoints to archive or unarchive a project !3372 + - Fix a bug whith trailing slash in bamboo_url + - Add links to CI setup documentation from project settings and builds pages + - Display project members page to all members + - Handle nil descriptions in Slack issue messages (Stan Hu) + - Add automated repository integrity checks (OFF by default) + - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) + - API: Ability to star and unstar a project (Robert Schilling) + - Add default scope to projects to exclude projects pending deletion + - Allow to close merge requests which source projects(forks) are deleted. + - Ensure empty recipients are rejected in BuildsEmailService + - Use rugged to change HEAD in Project#change_head (P.S.V.R) + - API: Ability to filter milestones by state `active` and `closed` (Robert Schilling) + - API: Fix milestone filtering by `iid` (Robert Schilling) + - Make before_script and after_script overridable on per-job (Kamil Trzciński) + - API: Delete notes of issues, snippets, and merge requests (Robert Schilling) + - Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.) + - Better errors handling when creating milestones inside groups + - Fix high CPU usage when PostReceive receives refs/merge-requests/ + - Hide `Create a group` help block when creating a new project in a group + - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) + - Allow issues and merge requests to be assigned to the author !2765 + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) + - Gracefully handle notes on deleted commits in merge requests (Stan Hu) + - Decouple membership and notifications + - Fix creation of merge requests for orphaned branches (Stan Hu) + - API: Ability to retrieve a single tag (Robert Schilling) + - While signing up, don't persist the user password across form redisplays + - Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla) + - Remove "Congratulations!" tweet button on newly-created project. (Connor Shea) + - Fix admin/projects when using visibility levels on search (PotHix) + - Build status notifications + - Update email confirmation interface + - API: Expose user location (Robert Schilling) + - API: Do not leak group existence via return code (Robert Schilling) + - ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591 + - Update number of Todos in the sidebar when it's marked as "Done". !3600 + - Sanitize branch names created for confidential issues + - API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling) + - API: User can leave a project through the API when not master or owner. !3613 + - Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu) + - Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld) + - Improved markdown forms + - Diff design updates (colors, button styles, etc) + - Copying and pasting a diff no longer pastes the line numbers or +/- + - Add null check to formData when updating profile content to fix Firefox bug + - Disable spellcheck and autocorrect for username field in admin page + - Delete tags using Rugged for performance reasons (Robert Schilling) + - Add Slack notifications when Wiki is edited (Sebastian Klier) + - Diffs load at the correct point when linking from from number + - Selected diff rows highlight + - Fix emoji categories in the emoji picker + - API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling) + - Add encrypted credentials for imported projects and migrate old ones + - Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller) + - Author and participants are displayed first on users autocompletion + - Show number sign on external issue reference text (Florent Baldino) + - Updated print style for issues + - Use GitHub Issue/PR number as iid to keep references + - Import GitHub labels + - Add option to filter by "Owned projects" on dashboard page + - Import GitHub milestones + - Execute system web hooks on push to the project + - Allow enable/disable push events for system hooks + - Fix GitHub project's link in the import page when provider has a custom URL + - Add RAW build trace output and button on build page + - Add incremental build trace update into CI API + +## 8.6.9 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + - Only show notes through JSON on confidential issues that the user has access to + +## 8.6.8 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent XSS via label drop-down + - Prevent information disclosure via milestone API + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.6.7 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + - Fix persistent XSS vulnerability in Label and Milestone dropdowns + - Fix vulnerability that made it possible to enumerate private projects belonging to group + +## 8.6.6 + + - Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413 + - Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654 + - Fix revoking of authorized OAuth applications (Connor Shea). !3690 + - Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk) + - Issuable header is consistent between issues and merge requests + - Improved spacing in issuable header on mobile + +## 8.6.5 + + - Fix importing from GitHub Enterprise. !3529 + - Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533 + - Check permissions when user attempts to import members from another project. !3535 + - Only update repository language if it is not set to improve performance. !3556 + - Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583 + - Unblock user when active_directory is disabled and it can be found !3550 + - Fix a 2FA authentication spoofing vulnerability. + +## 8.6.4 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu) + - Redesign the Labels page + +## 8.6.3 + + - Mentions on confidential issues doesn't create todos for non-members. !3374 + - Destroy related todos when an Issue/MR is deleted. !3376 + - Fix error 500 when target is nil on todo list. !3376 + - Fix copying uploads when moving issue to another project. !3382 + - Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432 + - Fix raw/rendered diff producing different results on merge requests. !3450 + - Fix commit comment alignment (Stan Hu). !3466 + - Fix Error 500 when searching for a comment in a project snippet. !3468 + - Allow temporary email as notification email. !3477 + - Fix issue with dropdowns not selecting values. !3478 + - Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280 + +## 8.6.2 + + - Fix dropdown alignment. !3298 + - Fix issuable sidebar overlaps on tablet. !3299 + - Make dropdowns pixel perfect. !3337 + - Fix order of steps to prevent PostgreSQL errors when running migration. !3355 + - Fix bold text in issuable sidebar. !3358 + - Fix error with anonymous token in applications settings. !3362 + - Fix the milestone 'upcoming' filter. !3364 + !3368 + - Fix comments on confidential issues showing up in activity feed to non-members. !3375 + - Fix `NoMethodError` when visiting CI root path at `/ci`. !3377 + - Add a tooltip to new branch button in issue page. !3380 + - Fix an issue hiding the password form when signed-in with a linked account. !3381 + - Add links to CI setup documentation from project settings and builds pages. !3384 + - Fix an issue with width of project select dropdown. !3386 + - Remove redundant `require`s from Banzai files. !3391 + - Fix error 500 with cancel button on issuable edit form. !3392 + !3417 + - Fix background when editing a highlighted note. !3423 + - Remove tabstop from the WIP toggle links. !3426 + - Ensure private project snippets are not viewable by unauthorized people. + - Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402 + - Fixed issue with notification settings not saving. !3452 + +## 8.6.1 + + - Add option to reload the schema before restoring a database backup. !2807 + - Display navigation controls on mobile. !3214 + - Fixed bug where participants would not work correctly on merge requests. !3329 + - Fix sorting issues by votes on the groups issues page results in SQL errors. !3333 + - Restrict notifications for confidential issues. !3334 + - Do not allow to move issue if it has not been persisted. !3340 + - Add a confirmation step before deleting an issuable. !3341 + - Fixes issue with signin button overflowing on mobile. !3342 + - Auto collapses the navigation sidebar when resizing. !3343 + - Fix build dependencies, when the dependency is a string. !3344 + - Shows error messages when trying to create label in dropdown menu. !3345 + - Fixes issue with assign milestone not loading milestone list. !3346 + - Fix an issue causing the Dashboard/Milestones page to be blank. !3348 + +## 8.6.0 (2016-03-22) + + - Add ability to move issue to another project + - Prevent tokens in the import URL to be showed by the UI + - Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu) + - Add confidential issues + - Bump gitlab_git to 9.0.3 (Stan Hu) + - Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu) + - Support Golang subpackage fetching (Stan Hu) + - Bump Capybara gem to 2.6.2 (Stan Hu) + - New branch button appears on issues where applicable + - Contributions to forked projects are included in calendar + - Improve the formatting for the user page bio (Connor Shea) + - Easily (un)mark merge request as WIP using link + - Use specialized system notes when MR is (un)marked as WIP + - Removed the default password from the initial admin account created during + setup. A password can be provided during setup (see installation docs), or + GitLab will ask the user to create a new one upon first visit. + - Fix issue when pushing to projects ending in .wiki + - Properly display YAML front matter in Markdown + - Add support for wiki with UTF-8 page names (Hiroyuki Sato) + - Fix wiki search results point to raw source (Hiroyuki Sato) + - Don't load all of GitLab in mail_room + - Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner) + - HTTP error pages work independently from location and config (Artem Sidorenko) + - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set + - Memoize @group in Admin::GroupsController (Yatish Mehta) + - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) + - Added omniauth-auth0 Gem (Daniel Carraro) + - Add label description in tooltip to labels in issue index and sidebar + - Strip leading and trailing spaces in URL validator (evuez) + - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez) + - Return empty array instead of 404 when commit has no statuses in commit status API + - Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip) + - Rewrite logo to simplify SVG code (Sean Lang) + - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) + - Ignore jobs that start with `.` (hidden jobs) + - Hide builds from project's settings when the feature is disabled + - Allow to pass name of created artifacts archive in `.gitlab-ci.yml` + - Refactor and greatly improve search performance + - Add support for cross-project label references + - Ensure "new SSH key" email do not ends up as dead Sidekiq jobs + - Update documentation to reflect Guest role not being enforced on internal projects + - Allow search for logged out users + - Allow to define on which builds the current one depends on + - Allow user subscription to a label: get notified for issues/merge requests related to that label (Timothy Andrew) + - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) + - Don't show Issues/MRs from archived projects in Groups view + - Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs + - Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie) + - Increase the notes polling timeout over time (Roberto Dip) + - Add shortcut to toggle markdown preview (Florent Baldino) + - Show labels in dashboard and group milestone views + - Fix an issue when the target branch of a MR had been deleted + - Add main language of a project in the list of projects (Tiago Botelho) + - Add #upcoming filter to Milestone filter (Tiago Botelho) + - Add ability to show archived projects on dashboard, explore and group pages + - Remove fork link closes all merge requests opened on source project (Florent Baldino) + - Move group activity to separate page + - Create external users which are excluded of internal and private projects unless access was explicitly granted + - Continue parameters are checked to ensure redirection goes to the same instance + - User deletion is now done in the background so the request can not time out + - Canceled builds are now ignored in compound build status if marked as `allowed to fail` + - Trigger a todo for mentions on commits page + - Let project owners and admins soft delete issues and merge requests + +## 8.5.13 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.5.12 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.5.11 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + +## 8.5.10 + + - Fix a 2FA authentication spoofing vulnerability. + +## 8.5.9 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu). + +## 8.5.8 + + - Bump Git version requirement to 2.7.4 + +## 8.5.7 + + - Bump Git version requirement to 2.7.3 + +## 8.5.6 + + - Obtain a lease before querying LDAP + +## 8.5.5 + + - Ensure removing a project removes associated Todo entries + - Prevent a 500 error in Todos when author was removed + - Fix pagination for filtered dashboard and explore pages + - Fix "Show all" link behavior + +## 8.5.4 + + - Do not cache requests for badges (including builds badge) + +## 8.5.3 + + - Flush repository caches before renaming projects + - Sort starred projects on dashboard based on last activity by default + - Show commit message in JIRA mention comment + - Makes issue page and merge request page usable on mobile browsers. + - Improved UI for profile settings + +## 8.5.2 + + - Fix sidebar overlapping content when screen width was below 1200px + - Don't repeat labels listed on Labels tab + - Bring the "branded appearance" feature from EE to CE + - Fix error 500 when commenting on a commit + - Show days remaining instead of elapsed time for Milestone + - Fix broken icons on installations with relative URL (Artem Sidorenko) + - Fix issue where tag list wasn't refreshed after deleting a tag + - Fix import from gitlab.com (KazSawada) + - Improve implementation to check read access to forks and add pagination + - Don't show any "2FA required" message if it's not actually required + - Fix help keyboard shortcut on relative URL setups (Artem Sidorenko) + - Update Rails to 4.2.5.2 + - Fix permissions for deprecated CI build status badge + - Don't show "Welcome to GitLab" when the search didn't return any projects + - Add Todos documentation + +## 8.5.1 + + - Fix group projects styles + - Show Crowd login tab when sign in is disabled and Crowd is enabled (Peter Hudec) + - Fix a set of small UI glitches in project, profile, and wiki pages + - Restrict permissions on public/uploads + - Fix the merge request side-by-side view after loading diff results + - Fix the look of tooltip for the "Revert" button + - Add when the Builds & Runners API changes got introduced + - Fix error 500 on some merged merge requests + - Fix an issue causing the content of the issuable sidebar to disappear + - Fix error 500 when trying to mark an already done todo as "done" + - Fix an issue where MRs weren't sortable + - Issues can now be dragged & dropped into empty milestone lists. This is also + possible with MRs + - Changed padding & background color for highlighted notes + - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) + - Update sentry-raven gem to 0.15.6 + - Add build coverage in project's builds page (Steffen Köhler) + - Changed # to ! for merge requests in activity view + +## 8.5.0 (2016-02-22) + + - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) + - Cache various Repository methods to improve performance + - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) + - Ensure rake tasks that don't need a DB connection can be run without one + - Update New Relic gem to 3.14.1.311 (Stan Hu) + - Add "visibility" flag to GET /projects api endpoint + - Add an option to supply root email through an environmental variable (Koichiro Mikami) + - Ignore binary files in code search to prevent Error 500 (Stan Hu) + - Render sanitized SVG images (Stan Hu) + - Support download access by PRIVATE-TOKEN header (Stan Hu) + - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push + - Add option to include the sender name in body of Notify email (Jason Lee) + - New UI for pagination + - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet + set it up + - API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger) + - Fix diff comments loaded by AJAX to load comment with diff in discussion tab + - Fix relative links in other markup formats (Ben Boeckel) + - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) + - Fix label links for a merge request pointing to issues list + - Don't vendor minified JS + - Increase project import timeout to 15 minutes + - Be more permissive with email address validation: it only has to contain a single '@' + - 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 + - Allow limited Markdown in Broadcast Messages + - 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) + - Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson) + - Optimized performance of finding issues to be closed by a merge request + - Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace` + and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev) + - Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url` + in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev) + - Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev) + - API: Expose MergeRequest#merge_status (Andrei Dziahel) + - Revert "Add IP check against DNSBLs at account sign-up" + - Actually use the `skip_merges` option in Repository#commits (Tony Chu) + - 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 + - Discover branches for commit statuses ref-less when doing merge when succeeded + - 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) + - API: Allow to set or update a merge-request's milestone (Kirill Skachkov) + - Improve UI consistency between projects and groups lists + - Add sort dropdown to dashboard projects page + - Fixed logo animation on Safari (Roman Rott) + - Fix Merge When Succeeded when multiple stages + - 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 + - Don't process cross-reference notes from forks + - Fix: init.d script not working on OS X + - Faster snippet search + - Added API to download build artifacts + - Title for milestones should be unique (Zeger-Jan van de Weg) + - Validate correctness of maximum attachment size application setting + - Replaces "Create merge request" link with one to the "Merge Request" when one exists + - Fix CI builds badge, add a new link to builds badge, deprecate the old one + - Fix broken link to project in build notification emails + - Ability to see and sort on vote count from Issues and MR lists + - Fix builds scheduler when first build in stage was allowed to fail + - User project limit is reached notice is hidden if the projects limit is zero + - Add API support for managing runners and project's runners + - Allow SAML users to login with no previous account without having to allow + all Omniauth providers to do so. + - Allow existing users to auto link their SAML credentials by logging in via SAML + - Make it possible to erase a build (trace, artifacts) using UI and API + - Ability to revert changes from a Merge Request or Commit + - Emoji comment on diffs are not award emoji + - Add label description (Nuttanart Pornprasitsakul) + - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) + - Add Todos + +## 8.4.11 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.4.10 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via Git branch and tag names + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via snippet API + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.4.9 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + +## 8.4.8 + + - Fix a 2FA authentication spoofing vulnerability. + +## 8.4.7 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu). + +## 8.4.6 + + - Bump Git version requirement to 2.7.4 + +## 8.4.5 + + - No CE-specific changes + +## 8.4.4 + + - Update omniauth-saml gem to 1.4.2 + - Prevent long-running backup tasks from timing out the database connection + - Add a Project setting to allow guests to view build logs (defaults to true) + - Sort project milestones by due date including issue editor (Oliver Rogers / Orih) + +## 8.4.3 + + - Increase lfs_objects size column to 8-byte integer to allow files larger + than 2.1GB + - Correctly highlight MR diff when MR has merge conflicts + - Fix highlighting in blame view + - Update sentry-raven gem to prevent "Not a git repository" console output + when running certain commands + - Add instrumentation to additional Gitlab::Git and Rugged methods for + performance monitoring + - Allow autosize textareas to also be manually resized + +## 8.4.2 + + - Bump required gitlab-workhorse version to bring in a fix for missing + artifacts in the build artifacts browser + - Get rid of those ugly borders on the file tree view + - Fix updating the runner information when asking for builds + - Bump gitlab_git version to 7.2.24 in order to bring in a performance + improvement when checking if a repository was empty + - Add instrumentation for Gitlab::Git::Repository instance methods so we can + track them in Performance Monitoring. + - Increase contrast between highlighted code comments and inline diff marker + - Fix method undefined when using external commit status in builds + - Fix highlighting in blame view. + +## 8.4.1 + + - Apply security updates for Rails (4.2.5.1), rails-html-sanitizer (1.0.3), + and Nokogiri (1.6.7.2) + - Fix redirect loop during import + - Fix diff highlighting for all syntax themes + - Delete project and associations in a background worker + +## 8.4.0 (2016-01-22) + + - Allow LDAP users to change their email if it was not set by the LDAP server + - Ensure Gravatar host looks like an actual host + - Consider re-assign as a mention from a notification point of view + - Add pagination headers to already paginated API resources + - Properly generate diff of orphan commits, like the first commit in a repository + - Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages + - Autocomplete data is now always loaded, instead of when focusing a comment text area + - Improved performance of finding issues for an entire group + - Added custom application performance measuring system powered by InfluxDB + - Add syntax highlighting to diffs + - Gracefully handle invalid UTF-8 sequences in Markdown links (Stan Hu) + - Bump fog to 1.36.0 (Stan Hu) + - Add user's last used IP addresses to admin page (Stan Hu) + - Add housekeeping function to project settings page + - The default GitLab logo now acts as a loading indicator + - Fix caching issue where build status was not updating in project dashboard (Stan Hu) + - Accept 2xx status codes for successful Webhook triggers (Stan Hu) + - Fix missing date of month in network graph when commits span a month (Stan Hu) + - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu) + - Don't notify users twice if they are both project watchers and subscribers (Stan Hu) + - Remove gray background from layout in UI + - Fix signup for OAuth providers that don't provide a name + - Implement new UI for group page + - Implement search inside emoji picker + - Let the CI runner know about builds that this build depends on + - Add API support for looking up a user by username (Stan Hu) + - Add project permissions to all project API endpoints (Stan Hu) + - Link to milestone in "Milestone changed" system note + - Only allow group/project members to mention `@all` + - Expose Git's version in the admin area (Trey Davis) + - Add "Frequently used" category to emoji picker + - Add CAS support (tduehr) + - Add link to merge request on build detail page + - Fix: Problem with projects ending with .keys (Jose Corcuera) + - Revert back upvote and downvote button to the issue and MR pages + - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg) + - Add system hook messages for project rename and transfer (Steve Norman) + - Fix version check image in Safari + - Show 'All' tab by default in the builds page + - Add Open Graph and Twitter Card data to all pages + - Fix API project lookups when querying with a namespace with dots (Stan Hu) + - Enable forcing Two-factor authentication sitewide, with optional grace period + - Import GitHub Pull Requests into GitLab + - Change single user API endpoint to return more detailed data (Michael Potthoff) + - Update version check images to use SVG + - Validate README format before displaying + - Enable Microsoft Azure OAuth2 support (Janis Meybohm) + - Properly set task-list class on single item task lists + - Add file finder feature in tree view (Kyungchul Shin) + - Ajax filter by message for commits page + - API: Add support for deleting a tag via the API (Robert Schilling) + - Allow subsequent validations in CI Linter + - Show referenced MRs & Issues only when the current viewer can access them + - Fix Encoding::CompatibilityError bug when markdown content has some complex URL (Jason Lee) + - Add API support for managing project's builds + - Add API support for managing project's build triggers + - Add API support for managing project's build variables + - Allow broadcast messages to be edited + - Autosize Markdown textareas + - Import GitHub wiki into GitLab + - Add reporters ability to download and browse build artifacts (Andrew Johnson) + - Autofill referring url in message box when reporting user abuse. + - Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg) + - Add build artifacts browser + - Improve UX in builds artifacts browser + - Increase default size of `data` column in `events` table when using MySQL + - Expose button to CI Lint tool on project builds page + - Fix: Creator should be added as a master of the project on creation + - Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov) + - Add IP check against DNSBLs at account sign-up + - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching + +## 8.3.10 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.3.9 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via custom issue tracker URL + - Prevent XSS via `window.opener` + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.3.8 + + - Fix persistent XSS vulnerability in `commit_person_link` helper + +## 8.3.7 + + - Fix a 2FA authentication spoofing vulnerability. + +## 8.3.6 + + - Don't attempt to fetch any tags from a forked repo (Stan Hu). + +## 8.3.5 + + - Bump Git version requirement to 2.7.4 + +## 8.3.4 + + - Use gitlab-workhorse 0.5.4 (fixes API routing bug) + +## 8.3.3 + + - Preserve CE behavior with JIRA integration by only calling API if URL is set + - Fix duplicated branch creation/deletion events when using Web UI (Stan Hu) + - Add configurable LDAP server query timeout + - Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running + - Suppress e-mails on failed builds if allow_failure is set (Stan Hu) + - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) + - Better support for referencing and closing issues in Asana service (Mike Wyatt) + - Enable "Add key" button when user fills in a proper key (Stan Hu) + - Fix error in processing reply-by-email messages (Jason Lee) + - Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu) + - Use WOFF versions of SourceSansPro fonts + - Fix regression when builds were not generated for tags created through web/api interface + - Fix: maintain milestone filter between Open and Closed tabs (Greg Smethells) + - Fix missing artifacts and build traces for build created before 8.3 + +## 8.3.2 + + - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu) + - Add support for Google reCAPTCHA in user registration + +## 8.3.1 + + - Fix Error 500 when global milestones have slashes (Stan Hu) + - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu) + - Fix LDAP identity and user retrieval when special characters are used + - Move Sidekiq-cron configuration to gitlab.yml + +## 8.3.0 (2015-12-22) + + - Bump rack-attack to 4.3.1 for security fix (Stan Hu) + - API support for starred projects for authorized user (Zeger-Jan van de Weg) + - Add open_issues_count to project API (Stan Hu) + - Expand character set of usernames created by Omniauth (Corey Hinshaw) + - Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg) + - Add unsubscribe link in the email footer (Zeger-Jan van de Weg) + - Provide better diagnostic message upon project creation errors (Stan Hu) + - Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu) + - Remove api credentials from link to build_page + - Deprecate GitLabCiService making it to always be inactive + - Bump gollum-lib to 4.1.0 (Stan Hu) + - Fix broken group avatar upload under "New group" (Stan Hu) + - Update project repositorize size and commit count during import:repos task (Stan Hu) + - Fix API setting of 'public' attribute to false will make a project private (Stan Hu) + - Handle and report SSL errors in Webhook 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 + - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) + - Recognize issue/MR/snippet/commit links as references + - Backport JIRA features from EE to CE + - Add ignore whitespace change option to commit view + - Fire update hook from GitLab + - Allow account unlock via email + - Style warning about mentioning many people in a comment + - Fix: sort milestones by due date once again (Greg Smethells) + - Migrate all CI::Services and CI::WebHooks to Services and WebHooks + - Don't show project fork event as "imported" + - Add API endpoint to fetch merge request commits list + - Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled + - Expose events API with comment information and author info + - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 + - Run custom Git hooks when branch is created or deleted. + - Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch + - Add languages page to graphs + - Block LDAP user when they are no longer found in the LDAP server + - Improve wording on project visibility levels (Zeger-Jan van de Weg) + - Fix editing notes on a merge request diff + - Automatically select default clone protocol based on user preferences (Eirik Lygre) + - Make Network page as sub tab of Commits + - Add copy-to-clipboard button for Snippets + - Add indication to merge request list item that MR cannot be merged automatically + - Default target branch to patch-n when editing file in protected branch + - Add Builds tab to merge request detail page + - Allow milestones, issues and MRs to be created from dashboard and group indexes + - Use new style for wiki + - Use new style for milestone detail page + - Fix sidebar tooltips when collapsed + - Prevent possible XSS attack with award-emoji + - Upgraded Sidekiq to 4.x + - Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg) + - Fix emoji aliases problem + - Fix award-emojis Flash alert's width + - Fix deleting notes on a merge request diff + - Display referenced merge request statuses in the issue description (Greg Smethells) + - Implement new sidebar for issue and merge request pages + - Emoji picker improvements + - Suppress warning about missing `.gitlab-ci.yml` if builds are disabled + - Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present + - Persist runners registration token in database + - Fix online editor should not remove newlines at the end of the file + - Expose Git's version in the admin area + - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) + +## 8.2.6 + + - Prevent unauthorized access to other projects build traces + - Forbid scripting for wiki files + +## 8.2.5 + + - Prevent privilege escalation via "impersonate" feature + - Prevent privilege escalation via notes API + - Prevent privilege escalation via project webhook API + - Prevent XSS via `window.opener` + - Prevent information disclosure via project labels + - Prevent information disclosure via new merge request page + +## 8.2.4 + + - Bump Git version requirement to 2.7.4 + +## 8.2.3 + + - Fix application settings cache not expiring after changes (Stan Hu) + - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) + - Update documentation for "Guest" permissions + - Properly convert Emoji-only comments into Award Emojis + - Enable devise paranoid mode to prevent user enumeration attack + - Webhook payload has an added, modified and removed properties for each commit + - Fix 500 error when creating a merge request that removes a submodule + +## 8.2.2 + + - Fix 404 in redirection after removing a project (Stan Hu) + - Ensure cached application settings are refreshed at startup (Stan Hu) + - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) + - Fix: Raw private snippets access workflow + - Prevent "413 Request entity too large" errors when pushing large files with LFS + - Fix invalid links within projects dashboard header + - Make current user the first user in assignee dropdown in issues detail page (Stan Hu) + - Fix: duplicate email notifications on issue comments + +## 8.2.1 + + - Forcefully update builds that didn't want to update with state machine + - Fix: saving GitLabCiService as Admin Template + +## 8.2.0 (2015-11-22) + + - Improved performance of finding projects and groups in various places + - Improved performance of rendering user profile pages and Atom feeds + - Expose build artifacts path as config option + - Fix grouping of contributors by email in graph. + - Improved performance of finding issues with/without labels + - Fix Drone CI service template not saving properly (Stan Hu) + - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) + - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) + - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) + - Improved performance of finding users by one of their Email addresses + - Add allow_failure field to commit status API (Stan Hu) + - Commits without .gitlab-ci.yml are marked as skipped + - Save detailed error when YAML syntax is invalid + - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml + - Added build artifacts + - Improved performance of replacing references in comments + - Show last project commit to default branch on project home page + - Highlight comment based on anchor in URL + - Adds ability to remove the forked relationship from project settings screen. (Han Loong Liauw) + - Improved performance of sorting milestone issues + - Allow users to select the Files view as default project view (Cristian Bica) + - Show "Empty Repository Page" for repository without branches (Artem V. Navrotskiy) + - Fix: Inability to reply to code comments in the MR view, if the MR comes from a fork + - Use git follow flag for commits page when retrieve history for file or directory + - Show merge request CI status on merge requests index page + - Send build name and stage in CI notification e-mail + - Extend yml syntax for only and except to support specifying repository path + - Enable shared runners to all new projects + - Bump GitLab-Workhorse to 0.4.1 + - Allow to define cache in `.gitlab-ci.yml` + - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) + - Remove deprecated CI events from project settings page + - Use issue editor as cross reference comment author when issue is edited with a new mention. + - Add graphs of commits ahead and behind default branch (Jeff Stubler) + - Improve personal snippet access workflow (Douglas Alexandre) + - [API] Add ability to fetch the commit ID of the last commit that actually touched a file + - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) + - Add "New file" link to dropdown on project page + - Include commit logs in project search + - Add "added", "modified" and "removed" properties to commit object in webhook + - Rename "Back to" links to "Go to" because its not always a case it point to place user come from + - Allow groups to appear in the search results if the group owner allows it + - Add email notification to former assignee upon unassignment (Adam Lieskovský) + - New design for project graphs page + - Remove deprecated dumped yaml file generated from previous job definitions + - Show specific runners from projects where user is master or owner + - MR target branch is now visible on a list view when it is different from project's default one + - Improve Continuous Integration graphs page + - Make color of "Accept Merge Request" button consistent with current build status + - Add ignore white space option in merge request diff and commit and compare view + - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) + - Relative links from a repositories README.md now link to the default branch + - Fix trailing whitespace issue in merge request/issue title + - Fix bug when milestone/label filter was empty for dashboard issues page + - Add ability to create milestone in group projects from single form + - Add option to create merge request when editing/creating a file (Dirceu Tiegs) + - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) + - Add Award Emoji to issue and merge request pages + +## 8.1.4 + + - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) + - Prevent redirect loop when home_page_url is set to the root URL + - Fix incoming email config defaults + - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) + +## 8.1.3 + + - Force update refs/merge-requests/X/head upon a push to the source branch of a merge request (Stan Hu) + - Spread out runner contacted_at updates + - Use issue editor as cross reference comment author when issue is edited with a new mention + - Add Facebook authentication + +## 8.1.2 + + - Fix cloning Wiki repositories via HTTP (Stan Hu) + - Add migration to remove satellites directory + - Fix specific runners visibility + - Fix 500 when editing CI service + - Require CI jobs to be named + - Fix CSS for runner status + - Fix CI badge + - Allow developer to manage builds + +## 8.1.1 + + - Removed, see 8.1.2 + +## 8.1.0 (2015-10-22) + + - Ensure MySQL CI limits DB migrations occur after the fields have been created (Stan Hu) + - Fix duplicate repositories in GitHub import page (Stan Hu) + - Redirect to a default path if HTTP_REFERER is not set (Stan Hu) + - Adds ability to create directories using the web editor (Ben Ford) + - Cleanup stuck CI builds + - Send an email to admin email when a user is reported for spam (Jonathan Rochkind) + - Show notifications button when user is member of group rather than project (Grzegorz Bizon) + - Fix bug preventing mentioned issued from being closed when MR is merged using fast-forward merge. + - Fix nonatomic database update potentially causing project star counts to go negative (Stan Hu) + - Don't show "Add README" link in an empty repository if user doesn't have access to push (Stan Hu) + - Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu) + - Speed up load times of issue detail pages by roughly 1.5x + - Fix CI rendering regressions + - If a merge request is to close an issue, show this on the issue page (Zeger-Jan van de Weg) + - Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu) + - Make diff file view easier to use on mobile screens (Stan Hu) + - Improved performance of finding users by username or Email address + - Fix bug where merge request comments created by API would not trigger notifications (Stan Hu) + - Add support for creating directories from Files page (Stan Hu) + - Allow removing of project without confirmation when JavaScript is disabled (Stan Hu) + - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu) + - Improved performance of the trending projects page + - Remove CI migration task + - Improved performance of finding projects by their namespace + - Add assignee data to Issuables' hook_data (Bram Daams) + - Fix bug where transferring a project would result in stale commit links (Stan Hu) + - Fix build trace updating + - Include full path of source and target branch names in New Merge Request page (Stan Hu) + - Add user preference to view activities as default dashboard (Stan Hu) + - Add option to admin area to sign in as a specific user (Pavel Forkert) + - Show CI status on all pages where commits list is rendered + - Automatically enable CI when push .gitlab-ci.yml file to repository + - Move CI charts to project graphs area + - Fix cases where Markdown did not render links in activity feed (Stan Hu) + - Add first and last to pagination (Zeger-Jan van de Weg) + - Added Commit Status API + - Added Builds View + - Added when to .gitlab-ci.yml + - Show CI status on commit page + - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds + - Show CI status on Your projects page and Starred projects page + - Remove "Continuous Integration" page from dashboard + - Add notes and SSL verification entries to hook APIs (Ben Boeckel) + - Fix grammar in admin area "labels" .nothing-here-block when no labels exist. + - Move CI runners page to project settings area + - Move CI variables page to project settings area + - Move CI triggers page to project settings area + - Move CI project settings page to CE project settings area + - Fix bug when removed file was not appearing in merge request diff + - Show warning when build cannot be served by any of the available CI runners + - Note the original location of a moved project when notifying users of the move + - Improve error message when merging fails + - Add support of multibyte characters in LDAP UID (Roman Petrov) + - Show additions/deletions stats on merge request diff + - Remove footer text in emails (Zeger-Jan van de Weg) + - Ensure code blocks are properly highlighted after a note is updated + - Fix wrong access level badge on MR comments + - Hide password in the service settings form + - Move CI webhooks page to project settings area + - Fix User Identities API. It now allows you to properly create or update user's identities. + - Add user preference to change layout width (Peter Göbel) + - Use commit status in merge request widget as preferred source of CI status + - Integrate CI commit and build pages into project pages + - Move CI services page to project settings area + - Add "Quick Submit" behavior to input fields throughout the application. Use + Cmd+Enter on Mac and Ctrl+Enter on Windows/Linux. + - Fix position of hamburger in header for smaller screens (Han Loong Liauw) + - Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji) + - Persist filters when sorting on admin user page (Jerry Lukins) + - Update style of snippets pages (Han Loong Liauw) + - Allow dashboard and group issues/MRs to be filtered by label + - Add spellcheck=false to certain input fields + - Invalidate stored service password if the endpoint URL is changed + - Project names are not fully shown if group name is too big, even on group page view + - Apply new design for Files page + - Add "New Page" button to Wiki Pages tab (Stan Hu) + - Only render 404 page from /public + - Hide passwords from services API (Alex Lossent) + - Fix: Images cannot show when projects' path was changed + - Let gitlab-git-http-server generate and serve 'git archive' downloads + - Optimize query when filtering on issuables (Zeger-Jan van de Weg) + - Fix padding of outdated discussion item. + - Animate the logo on hover + +## 8.0.5 + + - Correct lookup-by-email for LDAP logins + - Fix loading spinner sometimes not being hidden on Merge Request tab switches + +## 8.0.4 + + - Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu) + - Fix referrals for :back and relative URL installs + - Fix anchors to comments in diffs + - Remove CI token from build traces + - Fix "Assign All" button on Runner admin page + - Fix search in Files + - Add full project namespace to payload of system webhooks (Ricardo Band) + +## 8.0.3 + + - Fix URL shown in Slack notifications + - Fix bug where projects would appear to be stuck in the forked import state (Stan Hu) + - Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu) + - Add work_in_progress key to MR webhooks (Ben Boeckel) + +## 8.0.2 + + - Fix default avatar not rendering in network graph (Stan Hu) + - Skip check_initd_configured_correctly on omnibus installs + - Prevent double-prefixing of help page paths + - Clarify confirmation text on user deletion + - Make commit graphs responsive to window width changes (Stan Hu) + - Fix top margin for sign-in button on public pages + - Fix LDAP attribute mapping + - Remove git refs used internally by GitLab from network graph (Stan Hu) + - Use standard Markdown font in Markdown preview instead of fixed-width font (Stan Hu) + - Fix Reply by email for non-UTF-8 messages. + - Add option to use StartTLS with Reply by email IMAP server. + - Allow AWS S3 Server-Side Encryption with Amazon S3-Managed Keys for backups (Paul Beattie) + +## 8.0.1 + + - Improve CI migration procedure and documentation + +## 8.0.0 (2015-09-22) + + - Fix Markdown links not showing up in dashboard activity feed (Stan Hu) + - Remove milestones from merge requests when milestones are deleted (Stan Hu) + - Fix HTML link that was improperly escaped in new user e-mail (Stan Hu) + - Fix broken sort in merge request API (Stan Hu) + - Bump rouge to 1.10.1 to remove warning noise and fix other syntax highlighting bugs (Stan Hu) + - Gracefully handle errors in syntax highlighting by leaving the block unformatted (Stan Hu) + - Add "replace" and "upload" functionalities to allow user replace existing file and upload new file into current repository + - Fix URL construction for merge requests, issues, notes, and commits for relative URL config (Stan Hu) + - Fix emoji URLs in Markdown when relative_url_root is used (Stan Hu) + - Omit filename in Content-Disposition header in raw file download to avoid RFC 6266 encoding issues (Stan HU) + - Fix broken Wiki Page History (Stan Hu) + - Import forked repositories asynchronously to prevent large repositories from timing out (Stan Hu) + - Prevent anchors from being hidden by header (Stan Hu) + - Fix bug where only the first 15 Bitbucket issues would be imported (Stan Hu) + - Sort issues by creation date in Bitbucket importer (Stan Hu) + - Prevent too many redirects upon login when home page URL is set to external_url (Stan Hu) + - Improve dropdown positioning on the project home page (Hannes Rosenögger) + - Upgrade browser gem to 1.0.0 to avoid warning in IE11 compatibilty mode (Stan Hu) + - Remove user OAuth tokens from the database and request new tokens each session (Stan Hu) + - Restrict users API endpoints to use integer IDs (Stan Hu) + - Only show recent push event if the branch still exists or a recent merge request has not been created (Stan Hu) + - Remove satellites + - Better performance for web editor (switched from satellites to rugged) + - Faster merge + - Ability to fetch merge requests from refs/merge-requests/:id + - Allow displaying of archived projects in the admin interface (Artem Sidorenko) + - Allow configuration of import sources for new projects (Artem Sidorenko) + - Search for comments should be case insensetive + - Create cross-reference for closing references on commits pushed to non-default branches (Maël Valais) + - Ability to search milestones + - Gracefully handle SMTP user input errors (e.g. incorrect email addresses) to prevent Sidekiq retries (Stan Hu) + - Move dashboard activity to separate page (for your projects and starred projects) + - Improve performance of git blame + - Limit content width to 1200px for most of pages to improve readability on big screens + - Fix 500 error when submit project snippet without body + - Improve search page usability + - Bring more UI consistency in way how projects, snippets and groups lists are rendered + - Make all profiles and group public + - Fixed login failure when extern_uid changes (Joel Koglin) + - Don't notify users without access to the project when they are (accidentally) mentioned in a note. + - Retrieving oauth token with LDAP credentials + - Load Application settings from running database unless env var USE_DB=false + - Added Drone CI integration (Kirill Zaitsev) + - Allow developers to retry builds + - Hide advanced project options for non-admin users + - Fail builds if no .gitlab-ci.yml is found + - Refactored service API and added automatically service docs generator (Kirill Zaitsev) + - Added web_url key project hook_attrs (Kirill Zaitsev) + - Add ability to get user information by ID of an SSH key via the API + - Fix bug which IE cannot show image at markdown when the image is raw file of gitlab + - Add support for Crowd + - Global Labels that are available to all projects + - Fix highlighting of deleted lines in diffs. + - Project notification level can be set on the project page itself + - Added service API endpoint to retrieve service parameters (Petheő Bence) + - Add FogBugz project import (Jared Szechy) + - Sort users autocomplete lists by user (Allister Antosik) + - Webhook for issue now contains repository field (Jungkook Park) + - Add ability to add custom text to the help page (Jeroen van Baarsen) + - Add pg_schema to backup config + - Fix references to target project issues in Merge Requests markdown preview and textareas (Francesco Levorato) + - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato) + - Removed API calls from CE to CI + +## 7.14.3 through 0.8.0 + +- See [changelogs/archive.md](changelogs/archive.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cdcb54b0ae..b4635e50c28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -247,7 +247,7 @@ request is as follows: 1. Fork the project into your personal space on GitLab.com 1. Create a feature branch, branch away from `master` 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code -1. Add your changes to the [CHANGELOG](CHANGELOG): +1. Add your changes to the [CHANGELOG.md](CHANGELOG.md): 1. If you are fixing a ~regression issue, you can add your entry to the next patch release (e.g. `8.12.5` if current version is `8.12.4`) 1. Otherwise, add your entry to the next minor release (e.g. `8.13.0` if diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 7c0eb90d540..2215f37b81a 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -228,7 +228,7 @@ We'll discuss the three reasons to merge in master: leveraging code, merge confl If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit. If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this. You can prevent some merge conflicts by using [gitattributes](http://git-scm.com/docs/gitattributes) for files that can be in a random order. -For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG merge=union` so that there are fewer merge conflicts in it. +For example in GitLab our changelog file is specified in .gitattributes as `CHANGELOG.md merge=union` so that there are fewer merge conflicts in it. The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project. Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index fb4d8463981..7c4e8276902 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -10,11 +10,11 @@ then exit 1 fi -# Ensure that the CHANGELOG does not contain duplicate versions -DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^v [0-9.]+' CHANGELOG | sed 's| (unreleased)||' | sort | uniq -d) +# Ensure that the CHANGELOG.md does not contain duplicate versions +DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^## .+' CHANGELOG.md | sed -E 's| \(.+\)||' | sort -r | uniq -d) if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ] then - echo '✖ ERROR: Duplicate versions in CHANGELOG:' >&2 + echo '✖ ERROR: Duplicate versions in CHANGELOG.md:' >&2 echo "${DUPLICATE_CHANGELOG_VERSIONS}" >&2 exit 1 fi -- cgit v1.2.1 From d78c667d581dca6b75895f70f0ae6ebdd0f5d815 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 14 Oct 2016 16:47:55 -0700 Subject: Fix broken Spinach tests caused by changes in !6550 Partial fix to #23378 --- features/steps/project/commits/commits.rb | 20 ++++++++++++++------ spec/features/compare_spec.rb | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index b8264f97687..fd7b1debd68 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -42,15 +42,16 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I fill compare fields with branches' do - fill_in 'from', with: 'feature' - fill_in 'to', with: 'master' + select_using_dropdown('from', 'feature') + select_using_dropdown('to', 'master') click_button 'Compare' end step 'I fill compare fields with refs' do - fill_in "from", with: sample_commit.parent_id - fill_in "to", with: sample_commit.id + select_using_dropdown('from', sample_commit.parent_id) + select_using_dropdown('to', sample_commit.id) + click_button "Compare" end @@ -97,8 +98,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I fill compare fields with branches' do - fill_in 'from', with: 'master' - fill_in 'to', with: 'feature' + select_using_dropdown('from', 'master') + select_using_dropdown('to', 'feature') click_button 'Compare' end @@ -182,4 +183,11 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).to have_content "More submodules" expect(page).not_to have_content "Change some files" end + + def select_using_dropdown(dropdown_type, selection) + dropdown = find(".js-compare-#{dropdown_type}-dropdown") + dropdown.find(".compare-dropdown-toggle").click + dropdown.fill_in("Filter by branch/tag", with: selection) + find_link(selection, visible: true).click + end end diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb index 33dfd0d5b62..c22109d19b6 100644 --- a/spec/features/compare_spec.rb +++ b/spec/features/compare_spec.rb @@ -45,6 +45,6 @@ describe "Compare", js: true do dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click dropdown.fill_in("Filter by branch/tag", with: selection) - click_link selection + find_link(selection, visible: true).click end end -- cgit v1.2.1 From 93e464f454ec997743048e34db5c848b4146d452 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 03:30:31 +0100 Subject: Added logic to handle a revision input that does not exist in the menu --- app/assets/javascripts/compare_autocomplete.js | 52 ------------------ app/assets/javascripts/compare_autocomplete.js.es6 | 63 ++++++++++++++++++++++ app/views/projects/compare/_ref_dropdown.html.haml | 4 +- features/steps/project/commits/commits.rb | 14 +++-- 4 files changed, 74 insertions(+), 59 deletions(-) delete mode 100644 app/assets/javascripts/compare_autocomplete.js create mode 100644 app/assets/javascripts/compare_autocomplete.js.es6 diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js deleted file mode 100644 index 294d2c9052c..00000000000 --- a/app/assets/javascripts/compare_autocomplete.js +++ /dev/null @@ -1,52 +0,0 @@ -(function() { - this.CompareAutocomplete = (function() { - function CompareAutocomplete() { - this.initDropdown(); - } - - CompareAutocomplete.prototype.initDropdown = function() { - return $('.js-compare-dropdown').each(function() { - var $dropdown, selected; - $dropdown = $(this); - selected = $dropdown.data('selected'); - return $dropdown.glDropdown({ - data: function(term, callback) { - return $.ajax({ - url: $dropdown.data('refs-url'), - data: { - ref: $dropdown.data('ref') - } - }).done(function(refs) { - return callback(refs); - }); - }, - selectable: true, - filterable: true, - filterByText: true, - toggleLabel: true, - fieldName: $dropdown.data('field-name'), - filterInput: 'input[type="search"]', - renderRow: function(ref) { - var link; - if (ref.header != null) { - return $('
  • ').addClass('dropdown-header').text(ref.header); - } else { - link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); - return $('
  • ').append(link); - } - }, - id: function(obj, $el) { - return $el.attr('data-ref'); - }, - toggleLabel: function(obj, $el) { - return $el.text().trim(); - } - }); - }); - }; - - return CompareAutocomplete; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6 new file mode 100644 index 00000000000..9a2082d97e0 --- /dev/null +++ b/app/assets/javascripts/compare_autocomplete.js.es6 @@ -0,0 +1,63 @@ +(function() { + this.CompareAutocomplete = (function() { + function CompareAutocomplete() { + this.initDropdown(); + } + + CompareAutocomplete.prototype.initDropdown = function() { + return $('.js-compare-dropdown').each(function() { + var $dropdown, selected; + $dropdown = $(this); + selected = $dropdown.data('selected'); + const $dropdownContainer = $dropdown.closest('.dropdown'); + const $fieldInput = $(`input[name="${$dropdown.data('field-name')}"]`, $dropdownContainer); + const $filterInput = $('input[type="search"]', $dropdownContainer); + $dropdown.glDropdown({ + data: function(term, callback) { + return $.ajax({ + url: $dropdown.data('refs-url'), + data: { + ref: $dropdown.data('ref') + } + }).done(function(refs) { + return callback(refs); + }); + }, + selectable: true, + filterable: true, + filterByText: true, + toggleLabel: true, + fieldName: $dropdown.data('field-name'), + filterInput: 'input[type="search"]', + renderRow: function(ref) { + var link; + if (ref.header != null) { + return $('
  • ').addClass('dropdown-header').text(ref.header); + } else { + link = $('').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', escape(ref)); + return $('
  • ').append(link); + } + }, + id: function(obj, $el) { + return $el.attr('data-ref'); + }, + toggleLabel: function(obj, $el) { + return $el.text().trim(); + } + }); + $filterInput.on('keyup', (e) => { + const keyCode = e.keyCode || e.which; + if (keyCode !== 13) return; + const text = $filterInput.val(); + $fieldInput.val(text); + $('.dropdown-toggle-text', $dropdown).text(text); + $dropdownContainer.removeClass('open'); + }); + }); + }; + + return CompareAutocomplete; + + })(); + +}).call(this); diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml index 27d928c87a0..05fb37cdc0f 100644 --- a/app/views/projects/compare/_ref_dropdown.html.haml +++ b/app/views/projects/compare/_ref_dropdown.html.haml @@ -1,5 +1,5 @@ .dropdown-menu.dropdown-menu-selectable - = dropdown_title "Select branch/tag" - = dropdown_filter "Filter by branch/tag" + = dropdown_title "Select Git revision" + = dropdown_filter "Filter by Git revision" = dropdown_content = dropdown_loading diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index fd7b1debd68..b08912de25f 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -49,8 +49,8 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps end step 'I fill compare fields with refs' do - select_using_dropdown('from', sample_commit.parent_id) - select_using_dropdown('to', sample_commit.id) + select_using_dropdown('from', sample_commit.parent_id, true) + select_using_dropdown('to', sample_commit.id, true) click_button "Compare" end @@ -184,10 +184,14 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(page).not_to have_content "Change some files" end - def select_using_dropdown(dropdown_type, selection) + def select_using_dropdown(dropdown_type, selection, is_commit = false) dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click - dropdown.fill_in("Filter by branch/tag", with: selection) - find_link(selection, visible: true).click + dropdown.fill_in("Filter by Git revision", with: selection) + if is_commit + dropdown.find('input[type="search"]').send_keys(:return) + else + find_link(selection, visible: true).click + end end end -- cgit v1.2.1 From 913c1cd87aee7450fa0f8b6571302cff9b5f1dc1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 16 Oct 2016 21:09:38 -0700 Subject: Fix broken rspec in compare text !6910 changed the filter text from "Filter by branch/tag" to "Filter by Git revision" --- spec/features/compare_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb index c22109d19b6..43eb4000e58 100644 --- a/spec/features/compare_spec.rb +++ b/spec/features/compare_spec.rb @@ -44,7 +44,7 @@ describe "Compare", js: true do def select_using_dropdown(dropdown_type, selection) dropdown = find(".js-compare-#{dropdown_type}-dropdown") dropdown.find(".compare-dropdown-toggle").click - dropdown.fill_in("Filter by branch/tag", with: selection) + dropdown.fill_in("Filter by Git revision", with: selection) find_link(selection, visible: true).click end end -- cgit v1.2.1 From 9c6c5c79f8d3a555ded0539e06a922bc058d5c20 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 14 Oct 2016 19:08:48 +0200 Subject: Add Pipeline metrics worker --- CHANGELOG.md | 1 + app/models/ci/pipeline.rb | 10 ++++++---- app/workers/pipeline_metrics_worker.rb | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 app/workers/pipeline_metrics_worker.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f718fc88a..06269b79f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 - Added documentation for .gitattributes files + - Move Pipeline Metrics to separate worker - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4fdb5fef4fb..c7b9d6cc223 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -49,6 +49,10 @@ module Ci transition any => :canceled end + # IMPORTANT + # Do not add any operations to this state_machine + # Create a separate worker for each new operation + before_transition [:created, :pending] => :running do |pipeline| pipeline.started_at = Time.now end @@ -62,13 +66,11 @@ module Ci end after_transition [:created, :pending] => :running do |pipeline| - MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). - update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) + pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end after_transition any => [:success] do |pipeline| - MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). - update_all(latest_build_finished_at: pipeline.finished_at) + pipeline.run_after_commit { PipelineMetricsWorker.perform_async(id) } end after_transition [:created, :pending, :running] => :success do |pipeline| diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb new file mode 100644 index 00000000000..3d1cd770515 --- /dev/null +++ b/app/workers/pipeline_metrics_worker.rb @@ -0,0 +1,15 @@ +class PipelineMetricsWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + merge_requests = pipeline.merge_requests.map(&:id) + + metrics = MergeRequest::Metrics.where(merge_request_id: merge_requests) + metrics.update_all(latest_build_started_at: pipeline.started_at) if pipeline.active? + metrics.update_all(latest_build_finished_at: pipeline.finished_at) if pipeline.success? + end + end +end -- cgit v1.2.1 From a497803072edb4c8edbf9f4daf3832c122ee50e2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 09:55:47 +0200 Subject: Improve spec for pipeline metrics worker --- app/workers/pipeline_metrics_worker.rb | 25 ++++++++++++--- spec/models/ci/pipeline_spec.rb | 27 ++++++---------- spec/workers/pipeline_metrics_worker_spec.rb | 46 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 spec/workers/pipeline_metrics_worker_spec.rb diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb index 3d1cd770515..7bb92df3bbd 100644 --- a/app/workers/pipeline_metrics_worker.rb +++ b/app/workers/pipeline_metrics_worker.rb @@ -5,11 +5,26 @@ class PipelineMetricsWorker def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - merge_requests = pipeline.merge_requests.map(&:id) - - metrics = MergeRequest::Metrics.where(merge_request_id: merge_requests) - metrics.update_all(latest_build_started_at: pipeline.started_at) if pipeline.active? - metrics.update_all(latest_build_finished_at: pipeline.finished_at) if pipeline.success? + update_metrics_for_active_pipeline(pipeline) if pipeline.active? + update_metrics_for_succeeded_pipeline(pipeline) if pipeline.success? end end + + private + + def update_metrics_for_active_pipeline(pipeline) + metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) + end + + def update_metrics_for_succeeded_pipeline(pipeline) + metrics(pipeline).update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: pipeline.finished_at) + end + + def metrics(pipeline) + MergeRequest::Metrics.where(merge_request_id: merge_requests(pipeline)) + end + + def merge_requests(pipeline) + pipeline.merge_requests.map(&:id) + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 550a890797e..163c0b5c516 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -187,33 +187,24 @@ describe Ci::Pipeline, models: true do end end - describe "merge request metrics" do + describe 'merge request metrics' do let(:project) { FactoryGirl.create :project } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } - context 'when transitioning to running' do - it 'records the build start time' do - time = Time.now - Timecop.freeze(time) { build.run } - - expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(time) - end - - it 'clears the build end time' do - build.run + before do + expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id) + end - expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil + context 'when transitioning to running' do + it 'schedules metrics workers' do + pipeline.run end end context 'when transitioning to success' do - it 'records the build end time' do - build.run - time = Time.now - Timecop.freeze(time) { build.success } - - expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(time) + it 'schedules metrics workers' do + pipeline.succeed end end end diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb new file mode 100644 index 00000000000..f58df3f0d6e --- /dev/null +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe PipelineMetricsWorker do + let(:project) { create(:project) } + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } + + let(:pipeline) do + create(:ci_empty_pipeline, + status: status, + project: project, + ref: 'master', + sha: project.repository.commit('master').id, + started_at: 1.hour.ago, + finished_at: Time.now) + end + + describe '#perform' do + subject { described_class.new.perform(pipeline.id) } + + context 'when pipeline is running' do + let(:status) { 'running' } + + it 'records the build start time' do + subject + + expect(merge_request.reload.metrics.latest_build_started_at).to eq(pipeline.started_at) + end + + it 'clears the build end time' do + subject + + expect(merge_request.reload.metrics.latest_build_finished_at).to be_nil + end + end + + context 'when pipeline succeeded' do + let(:status) { 'success' } + + it 'records the build end time' do + subject + + expect(merge_request.reload.metrics.latest_build_finished_at).to eq(pipeline.finished_at) + end + end + end +end -- cgit v1.2.1 From 89bb889c4541a477aff4202fd6dad23ad3fe38d6 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 10:18:36 +0200 Subject: Stop directly parsing due_date with Date.parse, prefer parsing implicitly. --- app/assets/javascripts/due_date_select.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index bf68b7e3a9b..0fcf0823d4e 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -43,7 +43,7 @@ // Create the post date value = $("input[name='" + fieldName + "']").val(); if (value !== '') { - date = new Date(value.replace(new RegExp('-', 'g'), ',')); + date = new Date(value); mediumDate = $.datepicker.formatDate('M d, yy', date); } else { mediumDate = 'No due date'; @@ -64,7 +64,7 @@ $selectbox.hide(); } $value.css('display', ''); - cssClass = Date.parse(mediumDate) ? 'bold' : 'no-value'; + cssClass = new Date(mediumDate) ? 'bold' : 'no-value'; $valueContent.html("" + mediumDate + ""); $sidebarValue.html(mediumDate); if (value !== '') { -- cgit v1.2.1 From 039ccc169a4114a53acb16c0b031f704748c3cae Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 11 Oct 2016 10:19:18 +0200 Subject: Convert due_date_select.js filetype to es6. --- CHANGELOG.md | 1 + app/assets/javascripts/dispatcher.js.es6 | 2 +- app/assets/javascripts/due_date_select.js | 107 ----------------- app/assets/javascripts/due_date_select.js.es6 | 161 ++++++++++++++++++++++++++ app/views/shared/issuable/_sidebar.html.haml | 2 +- 5 files changed, 164 insertions(+), 109 deletions(-) delete mode 100644 app/assets/javascripts/due_date_select.js create mode 100644 app/assets/javascripts/due_date_select.js.es6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f718fc88a..671bb01c003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,7 @@ Please view this file on the master branch, on stable branches it's out of date. - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fixes padding in all clipboard icons that have .btn class + - Fix due date being displayed as NaN in Safari - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index f3957ed374b..5be35cf4b41 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -50,7 +50,7 @@ case 'projects:milestones:new': case 'projects:milestones:edit': new ZenMode(); - new DueDateSelect(); + new gl.DueDateSelectors(); new GLForm($('.milestone-form')); break; case 'groups:milestones:new': diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js deleted file mode 100644 index 0fcf0823d4e..00000000000 --- a/app/assets/javascripts/due_date_select.js +++ /dev/null @@ -1,107 +0,0 @@ -(function() { - this.DueDateSelect = (function() { - function DueDateSelect() { - var $datePicker, $dueDate, $loading; - // Milestone edit/new form - $datePicker = $('.datepicker'); - if ($datePicker.length) { - $dueDate = $('#milestone_due_date'); - $datePicker.datepicker({ - dateFormat: 'yy-mm-dd', - onSelect: function(dateText, inst) { - return $dueDate.val(dateText); - } - }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val())); - } - $('.js-clear-due-date').on('click', function(e) { - e.preventDefault(); - return $.datepicker._clearDate($datePicker); - }); - // Issuable sidebar - $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); - $('.js-due-date-select').each(function(i, dropdown) { - var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL; - $dropdown = $(dropdown); - $dropdownParent = $dropdown.closest('.dropdown'); - $datePicker = $dropdownParent.find('.js-due-date-calendar'); - $block = $dropdown.closest('.block'); - $selectbox = $dropdown.closest('.selectbox'); - $value = $block.find('.value'); - $valueContent = $block.find('.value-content'); - $sidebarValue = $('.js-due-date-sidebar-value', $block); - fieldName = $dropdown.data('field-name'); - abilityName = $dropdown.data('ability-name'); - issueUpdateURL = $dropdown.data('issue-update'); - $dropdown.glDropdown({ - hidden: function() { - $selectbox.hide(); - return $value.css('display', ''); - } - }); - addDueDate = function(isDropdown) { - var data, date, mediumDate, value; - // Create the post date - value = $("input[name='" + fieldName + "']").val(); - if (value !== '') { - date = new Date(value); - mediumDate = $.datepicker.formatDate('M d, yy', date); - } else { - mediumDate = 'No due date'; - } - data = {}; - data[abilityName] = {}; - data[abilityName].due_date = value; - return $.ajax({ - type: 'PUT', - url: issueUpdateURL, - data: data, - dataType: 'json', - beforeSend: function() { - var cssClass; - $loading.fadeIn(); - if (isDropdown) { - $dropdown.trigger('loading.gl.dropdown'); - $selectbox.hide(); - } - $value.css('display', ''); - cssClass = new Date(mediumDate) ? 'bold' : 'no-value'; - $valueContent.html("" + mediumDate + ""); - $sidebarValue.html(mediumDate); - if (value !== '') { - return $('.js-remove-due-date-holder').removeClass('hidden'); - } else { - return $('.js-remove-due-date-holder').addClass('hidden'); - } - } - }).done(function(data) { - if (isDropdown) { - $dropdown.trigger('loaded.gl.dropdown'); - $dropdown.dropdown('toggle'); - } - return $loading.fadeOut(); - }); - }; - $block.on('click', '.js-remove-due-date', function(e) { - e.preventDefault(); - $("input[name='" + fieldName + "']").val(''); - return addDueDate(false); - }); - return $datePicker.datepicker({ - dateFormat: 'yy-mm-dd', - defaultDate: $("input[name='" + fieldName + "']").val(), - altField: "input[name='" + fieldName + "']", - onSelect: function() { - return addDueDate(true); - } - }); - }); - $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', function(e) { - return e.stopImmediatePropagation(); - }); - } - - return DueDateSelect; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6 new file mode 100644 index 00000000000..41925fcc8e3 --- /dev/null +++ b/app/assets/javascripts/due_date_select.js.es6 @@ -0,0 +1,161 @@ +(function(global) { + class DueDateSelect { + constructor({ $dropdown, $loading } = {}) { + const $dropdownParent = $dropdown.closest('.dropdown'); + const $block = $dropdown.closest('.block'); + this.$loading = $loading; + this.$dropdown = $dropdown; + this.$dropdownParent = $dropdownParent; + this.$datePicker = $dropdownParent.find('.js-due-date-calendar'); + this.$block = $block; + this.$selectbox = $dropdown.closest('.selectbox'); + this.$value = $block.find('.value'); + this.$valueContent = $block.find('.value-content'); + this.$sidebarValue = $('.js-due-date-sidebar-value', $block); + this.fieldName = $dropdown.data('field-name'), + this.abilityName = $dropdown.data('ability-name'), + this.issueUpdateURL = $dropdown.data('issue-update') + + this.rawSelectedDate = null; + this.displayedDate = null; + this.datePayload = null; + + this.initGlDropdown(); + this.initRemoveDueDate(); + this.initDatePicker(); + this.initStopPropagation(); + } + + initGlDropdown() { + this.$dropdown.glDropdown({ + hidden: () => { + this.$selectbox.hide(); + this.$value.css('display', ''); + } + }); + } + + initDatePicker() { + this.$datePicker.datepicker({ + dateFormat: 'yy-mm-dd', + defaultDate: $("input[name='" + this.fieldName + "']").val(), + altField: "input[name='" + this.fieldName + "']", + onSelect: () => { + return this.saveDueDate(true); + } + }); + } + + initRemoveDueDate() { + this.$block.on('click', '.js-remove-due-date', (e) => { + e.preventDefault(); + $("input[name='" + this.fieldName + "']").val(''); + return this.saveDueDate(false); + }); + } + + initStopPropagation() { + $(document).off('click', '.ui-datepicker-header a').on('click', '.ui-datepicker-header a', (e) => { + return e.stopImmediatePropagation(); + }); + } + + saveDueDate(isDropdown) { + this.parseSelectedDate(); + this.prepSelectedDate(); + this.submitSelectedDate(isDropdown); + } + + parseSelectedDate() { + this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val(); + if (this.rawSelectedDate.length) { + let dateObj = new Date(this.rawSelectedDate); + this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj); + } else { + this.displayedDate = 'No due date'; + } + } + + prepSelectedDate() { + const datePayload = {}; + datePayload[this.abilityName] = {}; + datePayload[this.abilityName].due_date = this.rawSelectedDate; + this.datePayload = datePayload; + } + + submitSelectedDate(isDropdown) { + return $.ajax({ + type: 'PUT', + url: this.issueUpdateURL, + data: this.datePayload, + dataType: 'json', + beforeSend: () => { + const selectedDateValue = this.datePayload[this.abilityName].due_date; + const displayedDateStyle = this.displayedDate !== 'No due date' ? 'bold' : 'no-value'; + + this.$loading.fadeIn(); + + if (isDropdown) { + this.$dropdown.trigger('loading.gl.dropdown'); + this.$selectbox.hide(); + } + + this.$value.css('display', ''); + this.$valueContent.html(`${this.displayedDate}`); + this.$sidebarValue.html(this.displayedDate); + + return selectedDateValue.length ? + $('.js-remove-due-date-holder').removeClass('hidden') : + $('.js-remove-due-date-holder').addClass('hidden'); + + } + }).done((data) => { + if (isDropdown) { + this.$dropdown.trigger('loaded.gl.dropdown'); + this.$dropdown.dropdown('toggle'); + } + return this.$loading.fadeOut(); + }); + } + } + + class DueDateSelectors { + constructor() { + this.initMilestoneDueDate(); + this.initIssuableSelect(); + } + + initMilestoneDueDate() { + const $datePicker = $('.datepicker'); + + if ($datePicker.length) { + const $dueDate = $('#milestone_due_date'); + $datePicker.datepicker({ + dateFormat: 'yy-mm-dd', + onSelect: (dateText, inst) => { + $dueDate.val(dateText); + } + }).datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val())); + } + $('.js-clear-due-date').on('click', (e) => { + e.preventDefault(); + $.datepicker._clearDate($datePicker); + }); + } + + initIssuableSelect() { + const $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); + + $('.js-due-date-select').each((i, dropdown) => { + const $dropdown = $(dropdown); + new DueDateSelect({ + $dropdown, + $loading + }); + }); + } + } + + global.DueDateSelectors = DueDateSelectors; + +})(window.gl || (window.gl = {})); diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f8059988038..ba9f0c27661 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -171,5 +171,5 @@ new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') - new DueDateSelect(); + new gl.DueDateSelectors(); sidebar = new Sidebar(); -- cgit v1.2.1 From aa76fa55f7ab2dfd1ecc3df5737d6b5f48ed5759 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 18:38:35 +0100 Subject: Remove carriage returns from commit description as summary is on a newline and will always include carriage returns --- features/steps/project/commits/commits.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index b08912de25f..c2a15c1a19a 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -21,7 +21,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(response_headers['Content-Type']).to have_content("application/atom+xml") expect(body).to have_selector("title", text: "#{@project.name}:master commits") expect(body).to have_selector("author email", text: commit.author_email) - expect(body).to have_selector("entry summary", text: commit.description[0..10]) + expect(body).to have_selector("entry summary", text: commit.description[0..10].gsub("\r", "")) end step 'I click on tag link' do -- cgit v1.2.1 From 7df7a92b0a4a5d9731086177f0cb3f618578d122 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 17 Oct 2016 11:57:33 +0300 Subject: [Great spinach fix] Replace gsub with delete --- features/steps/project/commits/commits.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb index c2a15c1a19a..244306e8464 100644 --- a/features/steps/project/commits/commits.rb +++ b/features/steps/project/commits/commits.rb @@ -21,7 +21,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps expect(response_headers['Content-Type']).to have_content("application/atom+xml") expect(body).to have_selector("title", text: "#{@project.name}:master commits") expect(body).to have_selector("author email", text: commit.author_email) - expect(body).to have_selector("entry summary", text: commit.description[0..10].gsub("\r", "")) + expect(body).to have_selector("entry summary", text: commit.description[0..10].delete("\r")) end step 'I click on tag link' do -- cgit v1.2.1 From 77bbd9857fefde0d2a89acd23463732240585f23 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 17 Oct 2016 13:59:59 +0300 Subject: Fix randomly crashing spinach test for merge request Signed-off-by: Dmitriy Zaporozhets --- features/steps/project/merge_requests.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index de065dffbc2..44346d99f44 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -512,6 +512,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see new target branch changes' do expect(page).to have_content 'Request to merge fix into feature' expect(page).to have_content 'Target branch changed from merge-test to feature' + wait_for_ajax end step 'I click on "Email Patches"' do -- cgit v1.2.1 From 7762d8f5a350c92295fc11cdffa703b3cb498974 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 17 Oct 2016 14:10:04 +0300 Subject: Fix Test Env (proper error handling when gitlab-shell is not clonned) --- spec/support/test_env.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index ad8ae763f6d..725299031d0 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -98,7 +98,9 @@ module TestEnv def setup_gitlab_shell unless File.directory?(Gitlab.config.gitlab_shell.path) - `rake gitlab:shell:install` + unless system('rake', 'gitlab:shell:install') + raise 'Can`t clone gitlab-shell' + end end end -- cgit v1.2.1 From 009da2fc80924778ba61c6363b6e12d7daf1fa75 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 14:31:43 +0200 Subject: Fix broken specs on MySQL after https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6896 --- spec/workers/pipeline_metrics_worker_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index f58df3f0d6e..232478c9735 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -23,7 +23,7 @@ describe PipelineMetricsWorker do it 'records the build start time' do subject - expect(merge_request.reload.metrics.latest_build_started_at).to eq(pipeline.started_at) + expect(merge_request.reload.metrics.latest_build_started_at).to be_within(1.second).of(pipeline.started_at) end it 'clears the build end time' do @@ -39,7 +39,7 @@ describe PipelineMetricsWorker do it 'records the build end time' do subject - expect(merge_request.reload.metrics.latest_build_finished_at).to eq(pipeline.finished_at) + expect(merge_request.reload.metrics.latest_build_finished_at).to be_within(1.second).of(pipeline.finished_at) end end end -- cgit v1.2.1 From 3fe36c5b5356ee76edc7010e4d91073b2063495a Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Mon, 17 Oct 2016 10:55:35 -0400 Subject: Updated logo from @luke --- doc/raketasks/backup_hrz.png | Bin 8907 -> 31784 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png index 42084717ebe..287587609a1 100644 Binary files a/doc/raketasks/backup_hrz.png and b/doc/raketasks/backup_hrz.png differ -- cgit v1.2.1 From 855e8524b208e60612976aed0d66c52a51d965a6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 17 Oct 2016 12:52:16 +0200 Subject: Enable activerecord_sane_schema_dumper for test --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 5f754c1b66f..05166b6a828 100644 --- a/Gemfile +++ b/Gemfile @@ -262,8 +262,6 @@ group :development do # thin instead webrick gem 'thin', '~> 1.7.0' - - gem 'activerecord_sane_schema_dumper', '0.2' end group :development, :test do @@ -310,6 +308,8 @@ group :development, :test do gem 'license_finder', '~> 2.1.0', require: false gem 'knapsack', '~> 1.11.0' + + gem 'activerecord_sane_schema_dumper', '0.2' end group :test do -- cgit v1.2.1 From 6aca8b570c929e10f08a599f7bd4f3c7004f96d3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 16 Oct 2016 14:01:58 +0100 Subject: Added download-button class and applied button margin --- app/assets/stylesheets/pages/tree.scss | 4 ++++ app/views/projects/buttons/_download.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 99c0f6362d0..6ea7a2b5498 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -169,4 +169,8 @@ margin-top: 11px; position: relative; z-index: 2; + + .download-button { + margin-left: $btn-side-margin; + } } diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 9089586a89d..7e83a88913a 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,5 +1,5 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - %span{class: 'hidden-xs hidden-sm'} + %span{class: 'hidden-xs hidden-sm download-button'} .dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') -- cgit v1.2.1 From 0ff28c706d33c889348115cef6de6f779e1349da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 17 Oct 2016 17:32:50 +0200 Subject: Update CHANGELOG for 8.12.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 447b7ffdfdf..fd37d9bcde6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Clarify documentation for Runners API (Gennady Trafimenkov) - The instrumentation for Banzai::Renderer has been restored - Change user & group landing page routing from /u/:username to /:username - - Prevent running GfmAutocomplete setup for each diff note !6569 - Added documentation for .gitattributes files - Move Pipeline Metrics to separate worker - AbstractReferenceFilter caches project_refs on RequestStore when active @@ -40,7 +39,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - Simplify Mentionable concern instance methods - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date @@ -77,14 +75,12 @@ Please view this file on the master branch, on stable branches it's out of date. - Only update issuable labels if they have been changed - Take filters in account in issuable counters. !6496 - Use custom Ruby images to test builds (registry.dev.gitlab.org/gitlab/gitlab-build-images:*) - - Prevent flash alert text from being obscured when container is fluid - Append issue template to existing description !6149 (Joseph Frazier) - Trending projects now only show public projects and the list of projects is cached for a day - Memoize Gitlab Shell's secret token (!6599, Justin DiPierro) - Revoke button in Applications Settings underlines on hover. - Use higher size on Gitlab::Redis connection pool on Sidekiq servers - Add missing values to linter !6276 (Katarzyna Kobierska Ula Budziszewska) - - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Add disabled delete button to protected branches (ClemMakesApps) @@ -118,7 +114,6 @@ Please view this file on the master branch, on stable branches it's out of date. - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fixes padding in all clipboard icons that have .btn class - - Fix due date being displayed as NaN in Safari - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Add docs for request profiling @@ -126,8 +121,15 @@ Please view this file on the master branch, on stable branches it's out of date. ## 8.12.7 - - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 - - Fix GFM autocomplete setup being called several times + - Prevent running `GfmAutocomplete` setup for each diff note. !6569 + - Fix long commit messages overflow viewport in file tree. !6573 + - Use `gitlab-markup` gem instead of `github-markup` to fix `.rst` file rendering. !6659 + - Prevent flash alert text from being obscured when container is fluid. !6694 + - Fix due date being displayed as `NaN` in Safari. !6797 + - Fix JS bug with select2 because of missing `data-field` attribute in select box. !6812 + - Do not alter `force_remove_source_branch` options on MergeRequest unless specified. !6817 + - Fix GFM autocomplete setup being called several times. !6840 + - Handle case where deployment ref no longer exists. !6855 ## 8.12.6 -- cgit v1.2.1 From f91df171c53f230937b538ffdec04d12f7726ffc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 17 Oct 2016 16:55:34 +0100 Subject: Fixed find file keyboard navigation Closes #23423 --- app/assets/javascripts/project_find_file.js | 9 +++++ .../projects/files/find_file_keyboard_spec.rb | 42 ++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 spec/features/projects/files/find_file_keyboard_spec.rb diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 8e38ccf7e44..b8347367717 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -7,6 +7,7 @@ function ProjectFindFile(element1, options) { this.element = element1; this.options = options; + this.goToBlob = bind(this.goToBlob, this); this.goToTree = bind(this.goToTree, this); this.selectRowDown = bind(this.selectRowDown, this); this.selectRowUp = bind(this.selectRowUp, this); @@ -154,6 +155,14 @@ return location.href = this.options.treeUrl; }; + ProjectFindFile.prototype.goToBlob = function() { + var $link = this.element.find(".tree-item.selected .tree-item-file-name a"); + + if ($link.length) { + $link.get(0).click(); + } + }; + return ProjectFindFile; })(); diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb new file mode 100644 index 00000000000..fc88fd74af8 --- /dev/null +++ b/spec/features/projects/files/find_file_keyboard_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +feature 'Find file keyboard shortcuts', feature: true, js: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + project.team << [user, :master] + login_as user + + visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref) + + wait_for_ajax + end + + it 'opens file when pressing enter key' do + fill_in 'file_find', with: 'CHANGELOG' + + find('#file_find').native.send_keys(:enter) + + expect(page).to have_selector('.blob-content-holder') + + page.within('.file-title') do + expect(page).to have_content('CHANGELOG') + end + end + + it 'navigates files with arrow keys' do + fill_in 'file_find', with: 'application.' + + find('#file_find').native.send_keys(:down) + find('#file_find').native.send_keys(:enter) + + expect(page).to have_selector('.blob-content-holder') + + page.within('.file-title') do + expect(page).to have_content('application.js') + end + end +end -- cgit v1.2.1 From 9ff64b299bdcca767464f26e25c7e0017ec0fc38 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Oct 2016 18:13:03 +0100 Subject: Swapped button text manipulation outcomes for the toggle query --- app/assets/javascripts/pipelines.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index 6bf63ee6979..a7624de6089 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -15,7 +15,7 @@ $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + graphCollapsed ? $btnText.text('Hide') : $btnText.text('Expand') } addMarginToBuildColumns() { -- cgit v1.2.1 From 501d1485edf321d9af242af4721dec604b8dfbcc Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Fri, 14 Oct 2016 13:55:10 -0700 Subject: Apply better hierarchy to markdown headers and issue/mr titles --- app/assets/stylesheets/framework/typography.scss | 22 ++++++++++------------ app/assets/stylesheets/pages/detail_page.scss | 6 ++++-- app/assets/stylesheets/pages/merge_requests.scss | 7 ------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 287653beac5..8f8ad728269 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -45,40 +45,38 @@ } h1 { - font-size: 2em; + font-size: 1.75em; font-weight: 600; - margin: 1em 0 10px; + margin: 16px 0 10px; padding: 0 0 0.3em; border-bottom: 1px solid $btn-default-border; color: $gl-gray-dark; } h2 { - font-size: 1.6em; + font-size: 1.5em; font-weight: 600; - margin: 1em 0 10px; - padding-bottom: 0.3em; - border-bottom: 1px solid $btn-default-border; + margin: 16px 0 10px; color: $gl-gray-dark; } h3 { - margin: 1em 0 10px; - font-size: 1.4em; + margin: 16px 0 10px; + font-size: 1.3em; } h4 { - margin: 1em 0 10px; - font-size: 1.25em; + margin: 16px 0 10px; + font-size: 1.2em; } h5 { - margin: 1em 0 10px; + margin: 16px 0 10px; font-size: 1em; } h6 { - margin: 1em 0 10px; + margin: 16px 0 10px; font-size: 0.95em; } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 4d9c73c6840..7aa156bc47a 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -20,9 +20,11 @@ .detail-page-description { .title { - margin: 0; - font-size: 23px; + margin: 0 0 16px; + font-size: 2em; color: $gl-gray-dark; + padding: 0 0 0.3em; + border-bottom: 1px solid #e7e9ed; } .description { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index afc4e517fde..101472278e2 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -429,13 +429,6 @@ } } -.merge-request-details { - - .title { - margin-bottom: 20px; - } -} - .merge-request-tabs { background-color: #fff; -- cgit v1.2.1 From 28dbfd5c4c7793be4147ba217e7d9e162905088b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Oct 2016 12:43:17 -0500 Subject: Provide better error message to the user --- .../javascripts/merge_conflicts/components/diff_file_editor.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 index 3379414343f..5012bdfe997 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 @@ -62,7 +62,7 @@ this.saveDiffResolution(); }) .fail(() => { - console.log('error'); + new Flash('Failed to load the file, please try again.'); }) .always(() => { this.loading = false; -- cgit v1.2.1 From a5f5c02598d189428c583572d42f38e478669771 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 13 Oct 2016 16:07:16 +0300 Subject: Add todo for deprecated user routes and more information about deprecation to changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 +- config/routes/user.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2cbf5bc3277..1e887de6867 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -78,6 +78,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Change user pages routing from /u/:username/PATH to /users/:username/PATH. Old routes will redirect to the new ones for the time being. - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Make searching for commits case insensitive @@ -102,7 +103,6 @@ v 8.13.0 (unreleased) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects - - Change user pages routing from /u/:username/PATH to /users/:username/PATH v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/config/routes/user.rb b/config/routes/user.rb index ae15b9d02a3..53f9aafc107 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -34,7 +34,9 @@ scope(path: 'users/:username', end # Compatibility with old routing +# TODO (dzaporozhets): remove in 10.0 get '/u/:username', to: redirect('/%{username}'), constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Mon, 17 Oct 2016 13:23:28 -0700 Subject: change border color to variable --- app/assets/stylesheets/framework/typography.scss | 6 +++--- app/assets/stylesheets/pages/detail_page.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 8f8ad728269..55de9053be5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -49,7 +49,7 @@ font-weight: 600; margin: 16px 0 10px; padding: 0 0 0.3em; - border-bottom: 1px solid $btn-default-border; + border-bottom: 1px solid $white-dark; color: $gl-gray-dark; } @@ -85,12 +85,12 @@ font-size: inherit; padding: 8px 21px; margin: 12px 0; - border-left: 3px solid #e7e9ed; + border-left: 3px solid $white-dark; } blockquote:dir(rtl) { border-left: 0; - border-right: 3px solid #e7e9ed; + border-right: 3px solid $white-dark; } blockquote p { diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 7aa156bc47a..2357671c2ae 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -24,7 +24,7 @@ font-size: 2em; color: $gl-gray-dark; padding: 0 0 0.3em; - border-bottom: 1px solid #e7e9ed; + border-bottom: 1px solid $white-dark; } .description { -- cgit v1.2.1 From 73988dd994d6c0d7f5cec24ca5c4c20137cc3844 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 17 Oct 2016 17:47:07 -0500 Subject: Update endpoint to username validator --- app/assets/javascripts/username_validator.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js.es6 index 2517f778365..bf4b2e320cd 100644 --- a/app/assets/javascripts/username_validator.js.es6 +++ b/app/assets/javascripts/username_validator.js.es6 @@ -76,7 +76,7 @@ this.renderState(); return $.ajax({ type: 'GET', - url: `/u/${username}/exists`, + url: `/users/${username}/exists`, dataType: 'json', success: (res) => this.setAvailabilityState(res.exists) }); -- cgit v1.2.1