diff --git a/CHANGELOG b/CHANGELOG index 6cf459754f9..7cca0835afa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -49,6 +49,9 @@ v 7.10.0 (unreleased) - Improve file icons rendering on tree (Sullivan Sénéchal) - API: Add pagination to project events - Get issue links in notification mail to work again. + - Don't show commit comment button when user is not signed in. + - Fix admin user projects lists. + - Don't leak private group existence by redirecting from namespace controller to group controller. v 7.9.0 - Send EmailsOnPush email when branch or tag is created or deleted. diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 821712f7512..330ebac6f75 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -100,6 +100,8 @@ class Dispatcher when 'users:show' new User() new Activities() + when 'admin:users:show' + new ProjectsList() switch path.first() when 'admin' diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index bf71c144eaf..0aaa62d2b82 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -9,12 +9,8 @@ class @Issue if $("a.btn-close").length $("li.task-list-item input:checkbox").prop("disabled", false) - $(".task-list-item input:checkbox").on( - "click" - null - "issue" - updateTaskState - ) + $('.task-list-item input:checkbox').off('change') + $('.task-list-item input:checkbox').change('issue', updateTaskState) $('.issue-details').waitForImages -> $('.issuable-affix').affix offset: diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 6127d2bb480..2cbe03b986a 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -81,12 +81,8 @@ class @MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() - $(".task-list-item input:checkbox").on( - "click" - null - "merge_request" - updateTaskState - ) + $('.task-list-item input:checkbox').off('change') + $('.task-list-item input:checkbox').change('merge_request', updateTaskState) activateTab: (action) -> this.$('.merge-request-tabs li').removeClass 'active' diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index db393e08819..7c3021989a8 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -246,7 +246,7 @@ li.note { .milestone { &.milestone-closed { - background: #eee; + background: #f9f9f9; } .progress { margin-bottom: 0; diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb index b7a9d8c1291..386d103ee5a 100644 --- a/app/controllers/namespaces_controller.rb +++ b/app/controllers/namespaces_controller.rb @@ -4,14 +4,22 @@ class NamespacesController < ApplicationController def show namespace = Namespace.find_by(path: params[:id]) - unless namespace - return render_404 + if namespace + if namespace.is_a?(Group) + group = namespace + else + user = namespace.owner + end end - if namespace.type == "Group" - redirect_to group_path(namespace) + 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 - redirect_to user_path(namespace.owner) + render_404 end end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 3386fac8657..9703c8d9e9c 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -29,6 +29,10 @@ module GitlabRoutingHelper namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) end + def milestone_path(entity, *args) + namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args) + end + def project_url(project, *args) namespace_project_url(project.namespace, project, *args) end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 74900d4675d..d96e07034ec 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -52,7 +52,7 @@ module Mentionable if identifier == "all" users.push(*project.team.members.flatten) elsif namespace = Namespace.find_by(path: identifier) - if namespace.type == "Group" + if namespace.is_a?(Group) users.push(*namespace.users) else users << namespace.owner diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml new file mode 100644 index 00000000000..21e730bb7ff --- /dev/null +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -0,0 +1,20 @@ +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + %h4 + = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' +   + = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + + .col-sm-6 + = milestone_progress_bar(milestone) + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name_with_namespace diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index caf3b685864..9944c0df815 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -16,23 +16,5 @@ .nothing-here-block No milestones to show - else - @dashboard_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - %h4 - = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) - %div - %div - = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' -   - = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' -   - %span.light #{milestone.percent_complete}% complete - = milestone_progress_bar(milestone) - %div - %br - - milestone.milestones.each do |milestone| - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do - %span.label.label-default - = milestone.project.name_with_namespace + = render 'milestone', milestone: milestone = paginate @dashboard_milestones, theme: "gitlab" diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml new file mode 100644 index 00000000000..94fc43a581e --- /dev/null +++ b/app/views/groups/milestones/_milestone.html.haml @@ -0,0 +1,25 @@ +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + .pull-right + - if can?(current_user, :manage_group, @group) + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" + - else + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" + %h4 + = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' +   + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + .col-sm-6 + = milestone_progress_bar(milestone) + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 57dc235f5bb..008d5a6bd22 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -18,29 +18,5 @@ .nothing-here-block No milestones to show - else - @group_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .pull-right - - if can?(current_user, :manage_group, @group) - - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" - - else - = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - %h4 - = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) - %div - %div - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' -   - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' -   - %span.light #{milestone.percent_complete}% complete - = milestone_progress_bar(milestone) - %div - %br - - milestone.milestones.each do |milestone| - = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) do - %span.label.label-default - = milestone.project.name + = render 'milestone', milestone: milestone = paginate @group_milestones, theme: "gitlab" diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 7039c85bb2c..62360158ff9 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -11,16 +11,14 @@ %span.cred (Expired) %small = milestone.expires_at - - if milestone.is_empty? - %span.muted Empty - - else - %div - %div - = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do - = pluralize milestone.issues.count, 'Issue' -   - = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do - = pluralize milestone.merge_requests.count, 'Merge Request' -   - %span.light #{milestone.percent_complete}% complete + .row + .col-sm-6 + = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = pluralize milestone.issues.count, 'Issue' +   + = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = pluralize milestone.merge_requests.count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + .col-sm-6 = milestone_progress_bar(milestone) diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 49ce6c0888e..35c15cf3a9e 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -15,5 +15,5 @@ if(current_url == log_url) { // Load 10 more commit log for each file in tree // if we still on the same page - ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '/', offset: (@offset + @limit))}'); + ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))}'); } diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index b7383d5594e..297fa537394 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -1,13 +1,13 @@ -- if @contributed_projects.present? +- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? .panel.panel-default.contributed-projects .panel-heading Projects contributed to = render 'shared/projects_list', - projects: @contributed_projects.sort_by(&:star_count).reverse, + projects: contributed_projects.sort_by(&:star_count).reverse, projects_limit: 5, stars: true, avatar: false -- if @projects.present? +- if local_assigns.has_key?(:projects) && projects.present? .panel.panel-default .panel-heading Personal projects = render 'shared/projects_list', - projects: @projects.sort_by(&:star_count).reverse, + projects: projects.sort_by(&:star_count).reverse, projects_limit: 10, stars: true, avatar: false diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 5e1d65e2ed8..9dd8cb0738c 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -44,7 +44,7 @@ = spinner %aside.col-md-4 = render 'profile', user: @user - = render 'projects' + = render 'projects', projects: @projects, contributed_projects: @contributed_projects :coffeescript $(".user-calendar").load("#{user_calendar_path}") diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 3f1ca34a667..760a589d6e2 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -66,8 +66,8 @@ production: &base # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. # This happens when the commit is pushed or merged into the default branch of a project. # When not specified the default issue_closing_pattern as specified below will be used. - # Tip: you can test your closing pattern at http://rubular.com - # issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' + # Tip: you can test your closing pattern at http://rubular.com. + # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' ## Default project features settings default_projects_features: diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md index ddc0c8eac2b..aa65a082a53 100644 --- a/doc/customization/issue_closing.md +++ b/doc/customization/issue_closing.md @@ -1,5 +1,36 @@ # Issue closing pattern -By default you can close issues from commit messages by saying 'Closes #12' or 'Fixed #101'. +If a commit message matches the regular expression below, all issues referenced from +the matched text will be closed. This happens when the commit is pushed or merged +into the default branch of a project. -If you want to customize the message please do so in [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/73b92f85bcd6c213b845cc997843a969cf0906cf/config/gitlab.yml.example#L73) +When not specified, the default issue_closing_pattern as shown below will be used: + +```bash +((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+) +``` + +For example: + +``` +git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes #22). This commit is also related to #17 and fixes #18, #19 and #23." +``` + +will close `#20`, `#21`, `#22`, `#18`, `#19` and `#23`, but `#17` won't be closed +as it does not match the pattern. It also works with multiline commit messages. + +Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site +to test your own patterns. + +## Change the pattern + +For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`: + +``` +issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' +``` + +For manual installs you can customize the pattern in [gitlab.yml][0]. + +[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/40c3675372320febf5264061c9bcd63db2dfd13c/config/gitlab.yml.example#L65 +[1]: http://rubular.com/r/Xmbexed1OJ diff --git a/lib/api/branches.rb b/lib/api/branches.rb index edfdf842f85..592100a7045 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -1,5 +1,4 @@ require 'mime/types' -require 'uri' module API # Projects API @@ -101,10 +100,11 @@ module API # branch (required) - The name of the branch # Example Request: # DELETE /projects/:id/repository/branches/:branch - delete ":id/repository/branches/:branch" do + delete ":id/repository/branches/:branch", + requirements: { branch: /.*/ } do authorize_push_project result = DeleteBranchService.new(user_project, current_user). - execute(URI.unescape(params[:branch])) + execute(params[:branch]) if result[:status] == :success { diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index dbc1025855b..1dbea48ac14 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -121,7 +121,7 @@ module Gitlab markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline result = markdown_pipeline.call(text, markdown_context) - + save_options = 0 if options[:xhtml] save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML @@ -244,18 +244,18 @@ module Gitlab if identifier == "all" link_to( - "@all", - namespace_project_url(project.namespace, project, only_path: options[:reference_only_path]), + "@all", + namespace_project_url(project.namespace, project, only_path: options[:reference_only_path]), link_options ) elsif namespace = Namespace.find_by(path: identifier) url = - if namespace.type == "Group" + if namespace.is_a?(Group) group_url(identifier, only_path: options[:reference_only_path]) - else + else user_url(identifier, only_path: options[:reference_only_path]) end - + link_to("@#{identifier}", url, link_options) end end @@ -300,7 +300,7 @@ module Gitlab class: "gfm gfm-merge_request #{html_options[:class]}" ) url = namespace_project_merge_request_url(project.namespace, project, - merge_request, + merge_request, only_path: options[:reference_only_path]) link_to("#{prefix_text}!#{identifier}", url, link_options) end @@ -314,7 +314,7 @@ module Gitlab ) link_to( "$#{identifier}", - namespace_project_snippet_url(project.namespace, project, snippet, + namespace_project_snippet_url(project.namespace, project, snippet, only_path: options[:reference_only_path]), link_options ) @@ -330,7 +330,7 @@ module Gitlab prefix_text = "#{prefix_text}@" if prefix_text link_to( "#{prefix_text}#{identifier}", - namespace_project_commit_url( project.namespace, project, commit, + namespace_project_commit_url( project.namespace, project, commit, only_path: options[:reference_only_path]), link_options ) @@ -343,8 +343,8 @@ module Gitlab inclusive = identifier !~ /\.{3}/ from_id << "^" if inclusive - if project.valid_repo? && - from = project.repository.commit(from_id) && + if project.valid_repo? && + from = project.repository.commit(from_id) && to = project.repository.commit(to_id) link_options = html_options.merge( @@ -355,8 +355,8 @@ module Gitlab link_to( "#{prefix_text}#{identifier}", - namespace_project_compare_url(project.namespace, project, - from: from_id, to: to_id, + namespace_project_compare_url(project.namespace, project, + from: from_id, to: to_id, only_path: options[:reference_only_path]), link_options ) diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb new file mode 100644 index 00000000000..9c8619722cd --- /dev/null +++ b/spec/controllers/namespaces_controller_spec.rb @@ -0,0 +1,121 @@ +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) } + let!(:project) { create(:project, namespace: group) } + + context "when the group has public projects" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + 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 project doesn't have public projects" do + 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 project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + 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 project" do + it "responds with status 404" do + get :show, id: group.path + + expect(response.status).to eq(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.status).to eq(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