From bd21e3d7319dce4600881f7f8677b28f3f55cc5e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 25 Dec 2015 16:41:02 +0100 Subject: Add Open Graph data for group, project and commit. --- app/helpers/page_layout_helper.rb | 2 ++ app/views/groups/show.html.haml | 2 ++ app/views/layouts/_head.html.haml | 10 +++++----- app/views/projects/commit/show.html.haml | 4 +++- app/views/projects/show.html.haml | 2 ++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 791cb9e50bd..b84644d6996 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -53,6 +53,8 @@ module PageLayoutHelper @project.avatar_url || default elsif @user avatar_icon(@user) + elsif @group + @group.avatar_url || default else default end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index c2c7c581b3e..8179cdfac80 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,3 +1,5 @@ +- page_description @group.description + - unless can?(current_user, :read_group, @group) - @disable_search_panel = true diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 2e0bd2007a3..2e9a34a8807 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,13 +1,11 @@ +- site_name = "GitLab" %head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} - %meta{name: 'referrer', content: 'origin-when-cross-origin'} - - %meta{name: "description", content: page_description} -# Open Graph - http://ogp.me/ %meta{property: 'og:type', content: "object"} - %meta{property: 'og:site_name', content: "GitLab"} + %meta{property: 'og:site_name', content: site_name} %meta{property: 'og:title', content: page_title} %meta{property: 'og:description', content: page_description} %meta{property: 'og:image', content: page_image} @@ -20,8 +18,9 @@ %meta{property: 'twitter:image', content: page_image} = page_card_meta_tags - - page_title "GitLab" + - page_title site_name %title= page_title + %meta{name: "description", content: page_description} = favicon_link_tag 'favicon.ico' @@ -34,6 +33,7 @@ = include_gon + %meta{name: 'referrer', content: 'origin-when-cross-origin'} %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'theme-color', content: '#474D57'} diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 069b8b1f169..58aa45e8d2c 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,4 +1,6 @@ -- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" +- page_title "#{@commit.title} (#{@commit.short_id})", "Commits" +- page_description @commit.description + = render "projects/commits/header_title" = render "commit_box" - if @ci_commit diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 7466a098e24..74ce005eaa2 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,3 +1,5 @@ +- page_description @project.description + = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity") -- cgit v1.2.1 From a7756a4b51b0127caa19d1fb20953cb27c4c62a8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 27 Dec 2015 19:49:48 -0500 Subject: Add specs for page_image using a Group's avatar --- spec/helpers/page_layout_helper_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index fd7107779f6..60c4eb21814 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -96,6 +96,22 @@ describe PageLayoutHelper do helper.page_image end end + + context 'with @group' do + it 'uses Group avatar if available' do + group = double(avatar_url: 'http://example.com/uploads/avatar.png') + helper.instance_variable_set(:@group, group) + + expect(helper.page_image).to eq group.avatar_url + end + + it 'falls back to the default' do + group = double(avatar_url: nil) + helper.instance_variable_set(:@group, group) + + expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + end + end end describe 'page_card_attributes' do -- cgit v1.2.1 From dcca64a5230bbfd53ef5db8403d132deac4667f2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 27 Dec 2015 19:58:44 -0500 Subject: Use `assign` instead of `instance_variable_set` --- spec/helpers/page_layout_helper_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 60c4eb21814..300dccf50ec 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -45,14 +45,14 @@ describe PageLayoutHelper do describe 'page_description_default' do it 'uses Project description when available' do project = double(description: 'Project Description') - helper.instance_variable_set(:@project, project) + assign(:project, project) expect(helper.page_description_default).to eq 'Project Description' end it 'uses brand_title when Project description is nil' do project = double(description: nil) - helper.instance_variable_set(:@project, project) + assign(:project, project) expect(helper).to receive(:brand_title).and_return('Brand Title') expect(helper.page_description_default).to eq 'Brand Title' @@ -73,14 +73,14 @@ describe PageLayoutHelper do context 'with @project' do it 'uses Project avatar if available' do project = double(avatar_url: 'http://example.com/uploads/avatar.png') - helper.instance_variable_set(:@project, project) + assign(:project, project) expect(helper.page_image).to eq project.avatar_url end it 'falls back to the default' do project = double(avatar_url: nil) - helper.instance_variable_set(:@project, project) + assign(:project, project) expect(helper.page_image).to end_with 'assets/gitlab_logo.png' end @@ -89,7 +89,7 @@ describe PageLayoutHelper do context 'with @user' do it 'delegates to avatar_icon helper' do user = double('User') - helper.instance_variable_set(:@user, user) + assign(:user, user) expect(helper).to receive(:avatar_icon).with(user) @@ -100,14 +100,14 @@ describe PageLayoutHelper do context 'with @group' do it 'uses Group avatar if available' do group = double(avatar_url: 'http://example.com/uploads/avatar.png') - helper.instance_variable_set(:@group, group) + assign(:group, group) expect(helper.page_image).to eq group.avatar_url end it 'falls back to the default' do group = double(avatar_url: nil) - helper.instance_variable_set(:@group, group) + assign(:group, group) expect(helper.page_image).to end_with 'assets/gitlab_logo.png' end -- cgit v1.2.1 From e57b506222f535774059cbb0f986621384c5a8f7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 5 Jan 2016 05:30:01 -0800 Subject: Suggest prefacing find command with sudo when base permissions are wrong Closes #5872 --- lib/tasks/gitlab/check.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 0469c5a61c3..2dc2953e328 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -431,7 +431,7 @@ namespace :gitlab do try_fixing_it( "sudo chmod -R ug+rwX,o-rwx #{repo_base_path}", "sudo chmod -R ug-s #{repo_base_path}", - "find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s" + "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s" ) for_more_information( see_installation_guide_section "GitLab Shell" -- cgit v1.2.1 From 097faeb481db2a4956b41049c041d55f5da4e2c1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 5 Jan 2016 16:24:42 +0100 Subject: Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running --- CHANGELOG | 1 + app/controllers/projects/merge_requests_controller.rb | 2 +- app/models/merge_request.rb | 16 +++++++++++----- app/views/projects/merge_requests/_show.html.haml | 2 +- lib/api/merge_requests.rb | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d841b149615..240fa43d6cb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.4.0 (unreleased) - Fix API project lookups when querying with a namespace with dots (Stan Hu) v 8.3.3 (unreleased) + - Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) - Enable "Add key" button when user fills in a proper key (Stan Hu) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ab5c953189c..de948d271c8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -153,7 +153,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def merge_check - @merge_request.check_if_can_be_merged if @merge_request.unchecked? + @merge_request.check_if_can_be_merged render partial: "projects/merge_requests/widget/show.html.haml", layout: false end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ac25d38eb63..30d0c2b5961 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -229,6 +229,8 @@ class MergeRequest < ActiveRecord::Base end def check_if_can_be_merged + return unless unchecked? + can_be_merged = project.repository.can_be_merged?(source_sha, target_branch) @@ -252,7 +254,11 @@ class MergeRequest < ActiveRecord::Base end def mergeable? - open? && !work_in_progress? && can_be_merged? + return false unless open? && !work_in_progress? + + check_if_can_be_merged + + can_be_merged? end def gitlab_merge_status @@ -452,6 +458,10 @@ class MergeRequest < ActiveRecord::Base !source_branch_exists? || !target_branch_exists? end + def broken? + self.commits.blank? || branch_missing? || cannot_be_merged? + end + def can_be_merged_by?(user) ::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch) end @@ -507,8 +517,4 @@ class MergeRequest < ActiveRecord::Base def ci_commit @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project end - - def broken? - self.commits.blank? || branch_missing? || cannot_be_merged? - end end diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index ba7c2c01e93..095876450a0 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -38,7 +38,7 @@ = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/widget/show.html.haml" - - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user) + - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user) .light.prepend-top-default You can also accept this merge request manually using the = succeed '.' do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 3c1c6bda260..5c97fe1c88c 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -211,7 +211,7 @@ module API unauthorized! unless merge_request.can_be_merged_by?(current_user) not_allowed! if !merge_request.open? || merge_request.work_in_progress? - merge_request.check_if_can_be_merged if merge_request.unchecked? + merge_request.check_if_can_be_merged render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged? -- cgit v1.2.1 From a298f694327b1241fc0d06618228e3750c20c5a1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 5 Jan 2016 14:50:52 -0500 Subject: Use `User#avatar_url` instead of `avatar_icon` helper --- app/helpers/page_layout_helper.rb | 10 +++---- spec/helpers/page_layout_helper_spec.rb | 51 +++++++++++---------------------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index b84644d6996..f2a4afebbd1 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -49,12 +49,10 @@ module PageLayoutHelper def page_image default = image_url('gitlab_logo.png') - if @project - @project.avatar_url || default - elsif @user - avatar_icon(@user) - elsif @group - @group.avatar_url || default + subject = @project || @user || @group + + if subject.present? + subject.avatar_url || default else default end diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 300dccf50ec..83aeafcf31a 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -70,46 +70,27 @@ describe PageLayoutHelper do expect(helper.page_image).to end_with 'assets/gitlab_logo.png' end - context 'with @project' do - it 'uses Project avatar if available' do - project = double(avatar_url: 'http://example.com/uploads/avatar.png') - assign(:project, project) + %w(project user group).each do |type| + context "with @#{type} assigned" do + it "uses #{type.titlecase} avatar if available" do + object = double(avatar_url: 'http://example.com/uploads/avatar.png') + assign(type, object) - expect(helper.page_image).to eq project.avatar_url - end - - it 'falls back to the default' do - project = double(avatar_url: nil) - assign(:project, project) - - expect(helper.page_image).to end_with 'assets/gitlab_logo.png' - end - end - - context 'with @user' do - it 'delegates to avatar_icon helper' do - user = double('User') - assign(:user, user) + expect(helper.page_image).to eq object.avatar_url + end - expect(helper).to receive(:avatar_icon).with(user) + it 'falls back to the default when avatar_url is nil' do + object = double(avatar_url: nil) + assign(type, object) - helper.page_image + expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + end end - end - - context 'with @group' do - it 'uses Group avatar if available' do - group = double(avatar_url: 'http://example.com/uploads/avatar.png') - assign(:group, group) - - expect(helper.page_image).to eq group.avatar_url - end - - it 'falls back to the default' do - group = double(avatar_url: nil) - assign(:group, group) - expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + context "with no assignments" do + it 'falls back to the default' do + expect(helper.page_image).to end_with 'assets/gitlab_logo.png' + end end end end -- cgit v1.2.1 From 43053c2e6f03ad60f85728f36c46588979f68024 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 5 Jan 2016 14:54:59 -0500 Subject: Make `page_description` less magical :sparkles: --- app/helpers/page_layout_helper.rb | 12 +----------- app/views/layouts/group.html.haml | 7 ++++--- app/views/layouts/project.html.haml | 7 ++++--- spec/helpers/page_layout_helper_spec.rb | 27 ++------------------------- 4 files changed, 11 insertions(+), 42 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index f2a4afebbd1..5c0dd36252e 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -27,7 +27,7 @@ module PageLayoutHelper # # Returns an HTML-safe String. def page_description(description = nil) - @page_description ||= page_description_default + @page_description ||= brand_title if description.present? @page_description = description.squish @@ -36,16 +36,6 @@ module PageLayoutHelper end end - # Default value for page_description when one hasn't been defined manually by - # a view - def page_description_default - if @project - @project.description || brand_title - else - brand_title - end - end - def page_image default = image_url('gitlab_logo.png') diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 31888c5580e..1ce8d0ef7b5 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,5 +1,6 @@ -- page_title @group.name -- header_title group_title(@group) unless header_title -- sidebar "group" unless sidebar +- page_title @group.name +- page_description @group.description +- header_title group_title(@group) unless header_title +- sidebar "group" unless sidebar = render template: "layouts/application" diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index abf73bcc709..f81283a5ddb 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -1,6 +1,7 @@ -- page_title @project.name_with_namespace -- header_title project_title(@project) unless header_title -- sidebar "project" unless sidebar +- page_title @project.name_with_namespace +- page_description @project.description +- header_title project_title(@project) unless header_title +- sidebar "project" unless sidebar - content_for :scripts_body_top do - project = @target_project || @project diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index 83aeafcf31a..a097786ba6d 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -2,8 +2,8 @@ require 'rails_helper' describe PageLayoutHelper do describe 'page_description' do - it 'defaults to value returned by page_description_default helper' do - allow(helper).to receive(:page_description_default).and_return('Foo') + it 'defaults to value returned by brand_title helper' do + allow(helper).to receive(:brand_title).and_return('Foo') expect(helper.page_description).to eq 'Foo' end @@ -42,29 +42,6 @@ describe PageLayoutHelper do end end - describe 'page_description_default' do - it 'uses Project description when available' do - project = double(description: 'Project Description') - assign(:project, project) - - expect(helper.page_description_default).to eq 'Project Description' - end - - it 'uses brand_title when Project description is nil' do - project = double(description: nil) - assign(:project, project) - - expect(helper).to receive(:brand_title).and_return('Brand Title') - expect(helper.page_description_default).to eq 'Brand Title' - end - - it 'falls back to brand_title' do - allow(helper).to receive(:brand_title).and_return('Brand Title') - - expect(helper.page_description_default).to eq 'Brand Title' - end - end - describe 'page_image' do it 'defaults to the GitLab logo' do expect(helper.page_image).to end_with 'assets/gitlab_logo.png' -- cgit v1.2.1 From a0793d69c538cbb6a2b9ff4389192862f6d16962 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 5 Jan 2016 15:02:42 -0500 Subject: Remove now-redundant `page_description` call from Projects#show --- app/views/projects/show.html.haml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index a8f924bbb7c..8436be433b1 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,5 +1,3 @@ -- page_description @project.description - = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "#{@project.name} activity") @@ -70,4 +68,4 @@ = render 'projects/last_commit', commit: @repository.commit, project: @project %div{class: "project-show-#{default_project_view}"} - = render default_project_view \ No newline at end of file + = render default_project_view -- cgit v1.2.1 From 6d3b5ea2a9611dc7d87bd48043f34f9e0930e052 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 5 Jan 2016 16:47:09 -0500 Subject: Remove now-redundant `page_description` call from Groups#show --- app/views/groups/show.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index e7f619d2d6b..a607d860d7d 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,5 +1,3 @@ -- page_description @group.description - - unless can?(current_user, :read_group, @group) - @disable_search_panel = true -- cgit v1.2.1 From 1e6fc0c6a440ad707d990282ab7a93c178e35cfa Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 30 Dec 2015 16:44:22 -0500 Subject: Define a limited set of filters for SingleLinePipeline Removes the following filters from its parent GfmPipeline: - SyntaxHighlightFilter - UploadLinkFilter - TableOfContentsFilter - LabelReferenceFilter - TaskListFilter Closes #1697 --- lib/banzai/pipeline/single_line_pipeline.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb index 6725c9039a9..a3c9d4f43aa 100644 --- a/lib/banzai/pipeline/single_line_pipeline.rb +++ b/lib/banzai/pipeline/single_line_pipeline.rb @@ -3,7 +3,23 @@ require 'banzai' module Banzai module Pipeline class SingleLinePipeline < GfmPipeline + def self.filters + @filters ||= [ + Filter::SanitizationFilter, + Filter::EmojiFilter, + Filter::AutolinkFilter, + Filter::ExternalLinkFilter, + + Filter::UserReferenceFilter, + Filter::IssueReferenceFilter, + Filter::ExternalIssueReferenceFilter, + Filter::MergeRequestReferenceFilter, + Filter::SnippetReferenceFilter, + Filter::CommitRangeReferenceFilter, + Filter::CommitReferenceFilter, + ] + end end end end -- cgit v1.2.1 From 384445eca6249363c0da6d8b96e7ee030dc6fab3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jan 2016 13:02:51 +0100 Subject: Don't override issue page description in project layout. --- app/helpers/page_layout_helper.rb | 11 +++-------- app/views/layouts/_head.html.haml | 2 ++ app/views/layouts/group.html.haml | 2 +- app/views/layouts/project.html.haml | 2 +- spec/helpers/page_layout_helper_spec.rb | 6 ++---- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 5c0dd36252e..82f805fa444 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -27,11 +27,9 @@ module PageLayoutHelper # # Returns an HTML-safe String. def page_description(description = nil) - @page_description ||= brand_title - if description.present? @page_description = description.squish - else + elsif @page_description.present? sanitize(@page_description, tags: []).truncate_words(30) end end @@ -41,11 +39,8 @@ module PageLayoutHelper subject = @project || @user || @group - if subject.present? - subject.avatar_url || default - else - default - end + image = subject.avatar_url if subject.present? + image || default end # Define or get attributes to be used as Twitter card metadata diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 1a2187e551b..38ca4f91c4d 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,3 +1,5 @@ +- page_description brand_title unless page_description + - site_name = "GitLab" %head{prefix: "og: http://ogp.me/ns#"} %meta{charset: "utf-8"} diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index 1ce8d0ef7b5..2e483b7148d 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,5 +1,5 @@ - page_title @group.name -- page_description @group.description +- page_description @group.description unless page_description - header_title group_title(@group) unless header_title - sidebar "group" unless sidebar diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index f81283a5ddb..ab527e8e438 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -1,5 +1,5 @@ - page_title @project.name_with_namespace -- page_description @project.description +- page_description @project.description unless page_description - header_title project_title(@project) unless header_title - sidebar "project" unless sidebar diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index a097786ba6d..cf632f594c7 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -2,10 +2,8 @@ require 'rails_helper' describe PageLayoutHelper do describe 'page_description' do - it 'defaults to value returned by brand_title helper' do - allow(helper).to receive(:brand_title).and_return('Foo') - - expect(helper.page_description).to eq 'Foo' + it 'defaults to nil' do + expect(helper.page_description).to eq nil end it 'returns the last-pushed description' do -- cgit v1.2.1 From b9ed3961b55cf3dbc1a6d4c841d295dd23161c90 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jan 2016 13:25:13 +0100 Subject: Revert "Add DEBUG_BANZAI_CACHE env var to debug Banzai cache issue." This reverts commit 4b027bc93a7875c3937f6b90ac1049b4a4d72da5. --- lib/banzai/renderer.rb | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 910e1c6994e..115ae914524 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,5 +1,7 @@ module Banzai module Renderer + CACHE_ENABLED = false + # Convert a Markdown String into an HTML-safe String of HTML # # Note that while the returned HTML will have been sanitized of dangerous @@ -18,22 +20,13 @@ module Banzai cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) - cacheless = cacheless_render(text, context) - - if cache_key && ENV["DEBUG_BANZAI_CACHE"] - cached = Rails.cache.fetch(cache_key) { cacheless } - - if cached != cacheless - Rails.logger.warn "Banzai cache mismatch" - Rails.logger.warn "Text: #{text.inspect}" - Rails.logger.warn "Context: #{context.inspect}" - Rails.logger.warn "Cache key: #{cache_key.inspect}" - Rails.logger.warn "Cacheless: #{cacheless.inspect}" - Rails.logger.warn "With cache: #{cached.inspect}" + if cache_key && CACHE_ENABLED + Rails.cache.fetch(cache_key) do + cacheless_render(text, context) end + else + cacheless_render(text, context) end - - cacheless end def self.render_result(text, context = {}) -- cgit v1.2.1 From cf19efec3ac0ab4510359dd71df3d511762230c3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jan 2016 13:26:02 +0100 Subject: Revert "Temporarily disable Markdown caching" This reverts commit d337d5e7137d9b3fd0f9b8890a3ba9296323acc7. --- lib/banzai/renderer.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 115ae914524..891c0fd7749 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,7 +1,5 @@ module Banzai module Renderer - CACHE_ENABLED = false - # Convert a Markdown String into an HTML-safe String of HTML # # Note that while the returned HTML will have been sanitized of dangerous @@ -20,7 +18,7 @@ module Banzai cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) - if cache_key && CACHE_ENABLED + if cache_key Rails.cache.fetch(cache_key) do cacheless_render(text, context) end -- cgit v1.2.1 From 37ce5f312eabf95deff7aac68f6bce6ba6e106b9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jan 2016 13:33:11 +0100 Subject: Fix mentionable reference extraction caching. --- app/models/concerns/mentionable.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 6316ee208b5..98f71ae8cb0 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -51,8 +51,11 @@ module Mentionable else self.class.mentionable_attrs.each do |attr, options| text = send(attr) - options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted? - ext.analyze(text, options) + + context = options.dup + context[:cache_key] = [self, attr] if context.delete(:cache) && self.persisted? + + ext.analyze(text, context) end end -- cgit v1.2.1 From 18b17072c6cc7eb199d1da34a3ea481dcd53a8cf Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 6 Jan 2016 13:33:47 +0100 Subject: Add regression test. --- spec/models/note_spec.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 593d8f76215..151a29e974b 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -125,6 +125,19 @@ describe Note, models: true do let(:set_mentionable_text) { ->(txt) { subject.note = txt } } end + describe "#all_references" do + let!(:note1) { create(:note) } + let!(:note2) { create(:note) } + + it "reads the rendered note body from the cache" do + expect(Banzai::Renderer).to receive(:render).with(note1.note, pipeline: :note, cache_key: [note1, "note"], project: note1.project) + expect(Banzai::Renderer).to receive(:render).with(note2.note, pipeline: :note, cache_key: [note2, "note"], project: note2.project) + + note1.all_references + note2.all_references + end + end + describe :search do let!(:note) { create(:note, note: "WoW") } @@ -164,7 +177,7 @@ describe Note, models: true do expect(note.editable?).to be_falsy end end - + describe "set_award!" do let(:issue) { create :issue } -- cgit v1.2.1 From da53fcba2d0e46e47a8fd6a79591a6367e863d57 Mon Sep 17 00:00:00 2001 From: Janis Meybohm Date: Wed, 23 Dec 2015 11:17:25 +0100 Subject: Enable Microsoft Azure OAuth2 support --- CHANGELOG | 1 + Gemfile | 1 + Gemfile.lock | 5 ++ app/assets/images/auth_buttons/azure_64.png | Bin 0 -> 986 bytes app/helpers/auth_helper.rb | 2 +- doc/integration/azure.md | 83 ++++++++++++++++++++++++++++ doc/integration/omniauth.md | 1 + 7 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 app/assets/images/auth_buttons/azure_64.png create mode 100644 doc/integration/azure.md diff --git a/CHANGELOG b/CHANGELOG index cd745d3746a..8ee32013772 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.4.0 (unreleased) - Fix API project lookups when querying with a namespace with dots (Stan Hu) - Update version check images to use SVG - Validate README format before displaying + - Enable Microsoft Azure OAuth2 support (Janis Meybohm) v 8.3.3 (unreleased) - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu) diff --git a/Gemfile b/Gemfile index 3ce4ba4a2a5..6145745b6f3 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'omniauth-saml', '~> 1.4.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd' +gem 'omniauth-azure-oauth2' gem 'rack-oauth2', '~> 1.2.1' # reCAPTCHA protection diff --git a/Gemfile.lock b/Gemfile.lock index ffb7cef0aba..2b42f325503 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -488,6 +488,10 @@ GEM activesupport nokogiri (>= 1.4.4) omniauth (~> 1.0) + omniauth-azure-oauth2 (0.0.6) + jwt (~> 1.0) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.1) opennebula (4.14.2) json nokogiri @@ -927,6 +931,7 @@ DEPENDENCIES omniauth-shibboleth (~> 1.2.0) omniauth-twitter (~> 1.2.0) omniauth_crowd + omniauth-azure-oauth2 org-ruby (~> 0.9.12) paranoia (~> 2.0) pg (~> 0.18.2) diff --git a/app/assets/images/auth_buttons/azure_64.png b/app/assets/images/auth_buttons/azure_64.png new file mode 100644 index 00000000000..a82c751e001 Binary files /dev/null and b/app/assets/images/auth_buttons/azure_64.png differ diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 0cfc0565e84..de669e529a7 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,5 +1,5 @@ module AuthHelper - PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook).freeze + PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2).freeze FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze def ldap_enabled? diff --git a/doc/integration/azure.md b/doc/integration/azure.md new file mode 100644 index 00000000000..48dddf7df44 --- /dev/null +++ b/doc/integration/azure.md @@ -0,0 +1,83 @@ +# Microsoft Azure OAuth2 OmniAuth Provider + +To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your application with Azure. Azure will generate a client ID and secret key for you to use. + +1. Sign in to the [Azure Management Portal](https://manage.windowsazure.com>). + +1. Select "Active Directory" on the left and choose the directory you want to use to register GitLab. + +1. Select "Applications" at the top bar and click the "Add" button the bottom. + +1. Select "Add an application my organization is developing". + +1. Provide the project information and click the "Next" button. + - Name: 'GitLab' works just fine here. + - Type: 'WEB APPLICATION AND/OR WEB API' + +1. On the "App properties" page enter the needed URI's and click the "Complete" button. + - SIGN-IN URL: Enter the URL of your GitLab installation (e.g 'https://gitlab.mycompany.com/') + - APP ID URI: Enter the endpoint URL for Microsoft to use, just has to be unique (e.g 'https://mycompany.onmicrosoft.com/gitlab') + +1. Select "Configure" in the top menu. + +1. Add a "Reply URL" pointing to the Azure OAuth callback of your GitLab installation (e.g. https://gitlab.mycompany.com/users/auth/azure_oauth2/callback). + +1. Create a "Client secret" by selecting a duration, the secret will be generated as soon as you click the "Save" button in the bottom menu.. + +1. Note the "CLIENT ID" and the "CLIENT SECRET". + +1. Select "View endpoints" from the bottom menu. + +1. You will see lots of endpoint URLs in the form 'https://login.microsoftonline.com/TENANT ID/...', note down the TENANT ID part of one of those endpoints. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For installations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "azure_oauth2", + "args" => { + "client_id" => "CLIENT ID", + "client_secret" => "CLIENT SECRET", + "tenant_id" => "TENANT ID", + } + } + ] + ``` + + For installations from source: + + ``` + - { name: 'azure_oauth2', + args: { client_id: "CLIENT ID", + client_secret: "CLIENT SECRET", + tenant_id: "TENANT ID" } } + ``` + +1. Replace 'CLIENT ID', 'CLIENT SECRET' and 'TENANT ID' with the values you got above. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a Microsoft icon below the regular sign in form. Click the icon to begin the authentication process. Microsoft will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index f2b1721fc03..e9e17eb4165 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -78,6 +78,7 @@ Now we can choose one or more of the Supported Providers below to continue confi - [Shibboleth](shibboleth.md) - [SAML](saml.md) - [Crowd](crowd.md) +- [Azure](azure.md) ## Enable OmniAuth for an Existing User -- cgit v1.2.1 From 7549102bb727daecc51da84af39956b32fc41537 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 6 Jan 2016 15:30:02 +0100 Subject: Store SQL/view timings in milliseconds Transaction timings are also already stored in milliseconds, this keeps things consistent. --- lib/gitlab/metrics/subscribers/action_view.rb | 8 ++++++-- lib/gitlab/metrics/subscribers/active_record.rb | 6 +++++- spec/lib/gitlab/metrics/subscribers/action_view_spec.rb | 4 ++-- spec/lib/gitlab/metrics/subscribers/active_record_spec.rb | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb index 7c0105d543a..84d9e383625 100644 --- a/lib/gitlab/metrics/subscribers/action_view.rb +++ b/lib/gitlab/metrics/subscribers/action_view.rb @@ -19,7 +19,7 @@ module Gitlab values = values_for(event) tags = tags_for(event) - current_transaction.increment(:view_duration, event.duration) + current_transaction.increment(:view_duration, duration(event)) current_transaction.add_metric(SERIES, values, tags) end @@ -28,7 +28,7 @@ module Gitlab end def values_for(event) - { duration: event.duration } + { duration: duration(event) } end def tags_for(event) @@ -48,6 +48,10 @@ module Gitlab def current_transaction Transaction.current end + + def duration(event) + event.duration * 1000.0 + end end end end diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 8008b3bc895..6fa73e7a3be 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -8,7 +8,7 @@ module Gitlab def sql(event) return unless current_transaction - current_transaction.increment(:sql_duration, event.duration) + current_transaction.increment(:sql_duration, duration(event)) end private @@ -16,6 +16,10 @@ module Gitlab def current_transaction Transaction.current end + + def duration(event) + event.duration * 1000.0 + end end end end diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index 05e4fbbeb51..0a4cc5e929b 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do describe '#render_template' do it 'tracks rendering of a template' do - values = { duration: 2.1 } + values = { duration: 2100 } tags = { view: 'app/views/x.html.haml', file: 'app/views/x.html.haml', @@ -29,7 +29,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do } expect(transaction).to receive(:increment). - with(:view_duration, 2.1) + with(:view_duration, 2100) expect(transaction).to receive(:add_metric). with(described_class::SERIES, values, tags) diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index 7bc070a4d09..ca86142a2f4 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do and_return(transaction) expect(transaction).to receive(:increment). - with(:sql_duration, 0.2) + with(:sql_duration, 200) subscriber.sql(event) end -- cgit v1.2.1 From 8fdc00bd4c59183a20a60a6b93228268230bbd2e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 6 Jan 2016 17:49:02 +0100 Subject: Remove InfluxDB username/password InfluxDB over UDP doesn't use authentication, thus there's no need for these settings. --- .../admin/application_settings_controller.rb | 2 -- app/views/admin/application_settings/_form.html.haml | 8 -------- .../20160106164438_remove_influxdb_credentials.rb | 6 ++++++ db/schema.rb | 20 +++++++++----------- lib/gitlab/metrics.rb | 6 +----- 5 files changed, 16 insertions(+), 26 deletions(-) create mode 100644 db/migrate/20160106164438_remove_influxdb_credentials.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 10e736fd362..44d06b6a647 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -70,8 +70,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :metrics_enabled, :metrics_host, :metrics_port, - :metrics_username, - :metrics_password, :metrics_pool_size, :metrics_timeout, :metrics_method_call_threshold, diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 89b38a0dad0..81337432ab7 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -179,14 +179,6 @@ your server configuration specifies a database to store data in when sending messages to this port, without it metrics data will not be saved. - .form-group - = f.label :metrics_username, 'InfluxDB username', class: 'control-label col-sm-2' - .col-sm-10 - = f.text_field :metrics_username, class: 'form-control' - .form-group - = f.label :metrics_password, 'InfluxDB password', class: 'control-label col-sm-2' - .col-sm-10 - = f.text_field :metrics_password, class: 'form-control' .form-group = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2' .col-sm-10 diff --git a/db/migrate/20160106164438_remove_influxdb_credentials.rb b/db/migrate/20160106164438_remove_influxdb_credentials.rb new file mode 100644 index 00000000000..47e74400b97 --- /dev/null +++ b/db/migrate/20160106164438_remove_influxdb_credentials.rb @@ -0,0 +1,6 @@ +class RemoveInfluxdbCredentials < ActiveRecord::Migration + def change + remove_column :application_settings, :metrics_username, :string + remove_column :application_settings, :metrics_password, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 48e6983684a..2ded8a45e18 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: 20151229112614) do +ActiveRecord::Schema.define(version: 20160106164438) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -50,16 +50,14 @@ ActiveRecord::Schema.define(version: 20151229112614) do 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.string "metrics_username" - t.string "metrics_password" - 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.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 diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index ee88ab34d6c..44356a0e42c 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -13,8 +13,6 @@ module Gitlab timeout: current_application_settings[:metrics_timeout], method_call_threshold: current_application_settings[:metrics_method_call_threshold], host: current_application_settings[:metrics_host], - username: current_application_settings[:metrics_username], - password: current_application_settings[:metrics_password], port: current_application_settings[:metrics_port] } end @@ -90,12 +88,10 @@ module Gitlab if enabled? @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do host = settings[:host] - user = settings[:username] - pw = settings[:password] port = settings[:port] InfluxDB::Client. - new(udp: { host: host, port: port }, username: user, password: pw) + new(udp: { host: host, port: port }) end end end -- cgit v1.2.1 From 12e32224c17253f9616cd05d0bce806881fa8db9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 6 Jan 2016 20:05:22 +0100 Subject: Add documentation on enabling/disabling GitLab CI [ci skip] --- doc/README.md | 1 + doc/ci/README.md | 1 + doc/ci/enable_or_disable_ci.md | 70 +++++++++++++++++++++++++++++++++++++++ doc/ci/img/features_settings.png | Bin 0 -> 18691 bytes 4 files changed, 72 insertions(+) create mode 100644 doc/ci/enable_or_disable_ci.md create mode 100644 doc/ci/img/features_settings.png diff --git a/doc/README.md b/doc/README.md index f4553a899d3..25fe3abcb9a 100644 --- a/doc/README.md +++ b/doc/README.md @@ -19,6 +19,7 @@ ## CI Documentation - [Quick Start](ci/quick_start/README.md) +- [Enable or disable GitLab CI](ci/enable_or_disable_ci.md) - [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md) - [Configuring runner](ci/runners/README.md) - [Configuring deployment](ci/deployment/README.md) diff --git a/doc/ci/README.md b/doc/ci/README.md index a1f5513d88e..4cdd2e1ad33 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -3,6 +3,7 @@ ### User documentation * [Quick Start](quick_start/README.md) +* [Enable or disable GitLab CI](enable_or_disable_ci.md) * [Configuring project (.gitlab-ci.yml)](yaml/README.md) * [Configuring runner](runners/README.md) * [Configuring deployment](deployment/README.md) diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md new file mode 100644 index 00000000000..2803bb5f34a --- /dev/null +++ b/doc/ci/enable_or_disable_ci.md @@ -0,0 +1,70 @@ +## Enable or disable GitLab CI + +_To effectively use GitLab CI, you need a valid [`.gitlab-ci.yml`](yaml/README.md) +file present at the root directory of your project and a +[runner](runners/README.md) properly set up. You can read our +[quick start guide](quick_start/README.md) to get you started._ + +If you are using an external CI server like Jenkins or Drone CI, it is advised +to disable GitLab CI in order to not have any conflicts with the commits status +API. + +--- + +As of GitLab 8.2, GitLab CI is mainly exposed via the `/builds` page of a +project. Disabling GitLab CI in a project does not delete any previous builds. +In fact, the `/builds` page can still be accessed, although it's hidden from +the left sidebar menu. + +GitLab CI is enabled by default on new installations and can be disabled either +individually under each project's settings, or site wide by modifying the +settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations +respectively. + +### Per project user setting + +The setting to enable or disable GitLab CI can be found with the name **Builds** +under the **Features** area of a project's settings along with **Issues**, +**Merge Requests**, **Wiki** and **Snippets**. Select or deselect the checkbox +and hit **Save** for the settings to take effect. + +![Features settings](img/features_settings.png) + +--- + +### Site wide administrator setting + +You can disable GitLab CI site wide, by modifying the settings in `gitlab.yml` +and `gitlab.rb` for source and Omnibus installations respectively. + +Two things to note. + +1. Disabling GitLab CI, will affect only newly created projects. Projects that + had it enabled prior this modification, will work as before. +1. Even if you disable GitLab CI, users will still be able to enable it in the + project's settings. + +--- + +For installations from source, open `gitlab.yml` with your editor and set +`builds` to `false`: + +```yaml +## Default project features settings +default_projects_features: + issues: true + merge_requests: true + wiki: true + snippets: false + builds: false +``` + +Save the file and restart GitLab: `sudo service gitlab restart`. + +For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line: + +``` +gitlab-rails['gitlab_default_projects_features_builds'] = false +``` + +Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`. diff --git a/doc/ci/img/features_settings.png b/doc/ci/img/features_settings.png new file mode 100644 index 00000000000..17aba5d14d8 Binary files /dev/null and b/doc/ci/img/features_settings.png differ -- cgit v1.2.1 From 7d013f7848ea46e68665a8efcd9d4453584945e1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 6 Jan 2016 21:05:46 -0800 Subject: Fix missing date of month in network graph when commits span a month Closes #3635, #1383 --- CHANGELOG | 1 + app/assets/javascripts/branch-graph.js.coffee | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index cd745d3746a..35577e21415 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.4.0 (unreleased) + - 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) - Implement new UI for group page diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee index 917228bd276..f2fd2a775a4 100644 --- a/app/assets/javascripts/branch-graph.js.coffee +++ b/app/assets/javascripts/branch-graph.js.coffee @@ -66,7 +66,7 @@ class @BranchGraph r.rect(40, 0, 30, @barHeight).attr fill: "#444" for day, mm in @days - if cuday isnt day[0] + if cuday isnt day[0] || cumonth isnt day[1] # Dates r.text(55, @offsetY + @unitTime * mm, day[0]) .attr( -- cgit v1.2.1 From a8e23bd1b5029fb04ecb22f9c60fe7a8f9d45091 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 7 Jan 2016 10:10:46 +0100 Subject: Fix hyphenation typos [ci skip] --- doc/ci/enable_or_disable_ci.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md index 2803bb5f34a..9bd2f5aff22 100644 --- a/doc/ci/enable_or_disable_ci.md +++ b/doc/ci/enable_or_disable_ci.md @@ -17,11 +17,11 @@ In fact, the `/builds` page can still be accessed, although it's hidden from the left sidebar menu. GitLab CI is enabled by default on new installations and can be disabled either -individually under each project's settings, or site wide by modifying the +individually under each project's settings, or site-wide by modifying the settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations respectively. -### Per project user setting +### Per-project user setting The setting to enable or disable GitLab CI can be found with the name **Builds** under the **Features** area of a project's settings along with **Issues**, @@ -32,15 +32,15 @@ and hit **Save** for the settings to take effect. --- -### Site wide administrator setting +### Site-wide administrator setting -You can disable GitLab CI site wide, by modifying the settings in `gitlab.yml` +You can disable GitLab CI site-wide, by modifying the settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations respectively. -Two things to note. +Two things to note: -1. Disabling GitLab CI, will affect only newly created projects. Projects that - had it enabled prior this modification, will work as before. +1. Disabling GitLab CI, will affect only newly-created projects. Projects that + had it enabled prior to this modification, will work as before. 1. Even if you disable GitLab CI, users will still be able to enable it in the project's settings. -- cgit v1.2.1 From 8884505350c8594e663c5af6feb8eb1b06f3a0c8 Mon Sep 17 00:00:00 2001 From: koreamic Date: Sun, 13 Dec 2015 02:27:48 +0900 Subject: Add new feature to find file Using the fuzzy filter, develop "file finder" feature. - feedback(http://feedback.gitlab.com/forums/176466-general/suggestions/4987909-add-file-finder-fuzzy-input-in-files-tab-to-ju ) - fuzzy filter(https://github.com/jeancroy/fuzzaldrin-plus) - shortcuts(when "t" was hitted at tree view, go to 'file find' page and 'esc' is to go back) - depends on gitlab_git 7.2.22 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 2 +- app/assets/javascripts/application.js.coffee | 1 + app/assets/javascripts/dispatcher.js.coffee | 4 +- app/assets/javascripts/project_find_file.js.coffee | 125 +++++++++++++++++++++ .../javascripts/shortcuts_find_file.js.coffee | 19 ++++ app/assets/javascripts/shortcuts_tree.coffee | 4 + app/assets/stylesheets/pages/tree.scss | 8 ++ app/controllers/projects/find_file_controller.rb | 26 +++++ app/controllers/projects/refs_controller.rb | 2 + app/models/repository.rb | 5 + app/views/help/_shortcuts.html.haml | 26 +++++ app/views/layouts/nav/_project.html.haml | 3 +- app/views/projects/_find_file_link.html.haml | 3 + app/views/projects/find_file/show.html.haml | 27 +++++ app/views/projects/tree/show.html.haml | 8 +- config/routes.rb | 18 +++ doc/workflow/shortcuts.png | Bin 78736 -> 48782 bytes features/project/find_file.feature | 42 +++++++ features/steps/project/project_find_file.rb | 73 ++++++++++++ features/steps/shared/paths.rb | 4 + .../projects/find_file_controller_spec.rb | 66 +++++++++++ spec/routing/project_routing_spec.rb | 12 ++ vendor/assets/javascripts/fuzzaldrin-plus.min.js | 1 + 25 files changed, 473 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/project_find_file.js.coffee create mode 100644 app/assets/javascripts/shortcuts_find_file.js.coffee create mode 100644 app/assets/javascripts/shortcuts_tree.coffee create mode 100644 app/controllers/projects/find_file_controller.rb create mode 100644 app/views/projects/_find_file_link.html.haml create mode 100644 app/views/projects/find_file/show.html.haml create mode 100644 features/project/find_file.feature create mode 100644 features/steps/project/project_find_file.rb create mode 100644 spec/controllers/projects/find_file_controller_spec.rb create mode 100644 vendor/assets/javascripts/fuzzaldrin-plus.min.js diff --git a/CHANGELOG b/CHANGELOG index e7f1d2b67da..22fb91baaf0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -106,6 +106,7 @@ v 8.3.0 - 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) + - Add file finder feature in tree view v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/Gemfile b/Gemfile index 6145745b6f3..6b0bc241494 100644 --- a/Gemfile +++ b/Gemfile @@ -49,7 +49,7 @@ gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.20' +gem "gitlab_git", '~> 7.2.22' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 2b42f325503..a1168ed3b7a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -887,7 +887,7 @@ DEPENDENCIES github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) gitlab_emoji (~> 0.2.0) - gitlab_git (~> 7.2.20) + gitlab_git (~> 7.2.22) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.1.0) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index b9b095e004a..c095e5ae2b1 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -40,6 +40,7 @@ #= require shortcuts_network #= require jquery.nicescroll.min #= require_tree . +#= require fuzzaldrin-plus.min window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 69e061ce6e9..58d6b9d4060 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -87,7 +87,9 @@ class Dispatcher new GroupAvatar() when 'projects:tree:show' new TreeView() - shortcut_handler = new ShortcutsNavigation() + shortcut_handler = new ShortcutsTree() + when 'projects:find_file:show' + shortcut_handler = true when 'projects:blob:show' new LineHighlighter() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/project_find_file.js.coffee b/app/assets/javascripts/project_find_file.js.coffee new file mode 100644 index 00000000000..0dd32352c34 --- /dev/null +++ b/app/assets/javascripts/project_find_file.js.coffee @@ -0,0 +1,125 @@ +class @ProjectFindFile + constructor: (@element, @options)-> + @filePaths = {} + @inputElement = @element.find(".file-finder-input") + + # init event + @initEvent() + + # focus text input box + @inputElement.focus() + + # load file list + @load(@options.url) + + # init event + initEvent: -> + @inputElement.off "keyup" + @inputElement.on "keyup", (event) => + target = $(event.target) + value = target.val() + oldValue = target.data("oldValue") ? "" + + if value != oldValue + target.data("oldValue", value) + @findFile() + @element.find("tr.tree-item").eq(0).addClass("selected").focus() + + @element.find(".tree-content-holder .tree-table").on "click", (event) -> + if (event.target.nodeName != "A") + path = @element.find(".tree-item-file-name a", this).attr("href") + location.href = path if path + + # find file + findFile: -> + searchText = @inputElement.val() + result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths + @renderList result, searchText + + # files pathes load + load: (url) -> + $.ajax + url: url + method: "get" + dataType: "json" + success: (data) => + @element.find(".loading").hide() + @filePaths = data + @findFile() + @element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus() + + # render result + renderList: (filePaths, searchText) -> + @element.find(".tree-table > tbody").empty() + + for filePath, i in filePaths + break if i == 20 + + if searchText + matches = fuzzaldrinPlus.match(filePath, searchText) + + blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}" + + html = @makeHtml filePath, matches, blobItemUrl + @element.find(".tree-table > tbody").append(html) + + # highlight text(awefwbwgtc -> awefwbwgtc ) + highlighter = (element, text, matches) -> + lastIndex = 0 + highlightText = "" + matchedChars = [] + + for matchIndex in matches + unmatched = text.substring(lastIndex, matchIndex) + + if unmatched + element.append(matchedChars.join("").bold()) if matchedChars.length + matchedChars = [] + element.append(document.createTextNode(unmatched)) + + matchedChars.push(text[matchIndex]) + lastIndex = matchIndex + 1 + + element.append(matchedChars.join("").bold()) if matchedChars.length + element.append(document.createTextNode(text.substring(lastIndex))) + + # make tbody row html + makeHtml: (filePath, matches, blobItemUrl) -> + $tr = $("") + if matches + $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl)) + else + $tr.find("a").attr("href", blobItemUrl).text(filePath) + + return $tr + + selectRow: (type) -> + rows = @element.find(".files-slider tr.tree-item") + selectedRow = @element.find(".files-slider tr.tree-item.selected") + + if rows && rows.length > 0 + if selectedRow && selectedRow.length > 0 + if type == "UP" + next = selectedRow.prev() + else if type == "DOWN" + next = selectedRow.next() + + if next.length > 0 + selectedRow.removeClass "selected" + selectedRow = next + else + selectedRow = rows.eq(0) + selectedRow.addClass("selected").focus() + + selectRowUp: => + @selectRow "UP" + + selectRowDown: => + @selectRow "DOWN" + + goToTree: => + location.href = @options.treeUrl + + goToBlob: => + path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href") + location.href = path if path diff --git a/app/assets/javascripts/shortcuts_find_file.js.coffee b/app/assets/javascripts/shortcuts_find_file.js.coffee new file mode 100644 index 00000000000..311e80bae19 --- /dev/null +++ b/app/assets/javascripts/shortcuts_find_file.js.coffee @@ -0,0 +1,19 @@ +#= require shortcuts_navigation + +class @ShortcutsFindFile extends ShortcutsNavigation + constructor: (@projectFindFile) -> + super() + _oldStopCallback = Mousetrap.stopCallback + # override to fire shortcuts action when focus in textbox + Mousetrap.stopCallback = (event, element, combo) => + if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter') + # when press up/down key in textbox, cusor prevent to move to home/end + event.preventDefault() + return false + + return _oldStopCallback(event, element, combo) + + Mousetrap.bind('up', @projectFindFile.selectRowUp) + Mousetrap.bind('down', @projectFindFile.selectRowDown) + Mousetrap.bind('esc', @projectFindFile.goToTree) + Mousetrap.bind('enter', @projectFindFile.goToBlob) diff --git a/app/assets/javascripts/shortcuts_tree.coffee b/app/assets/javascripts/shortcuts_tree.coffee new file mode 100644 index 00000000000..ba0839c9fc0 --- /dev/null +++ b/app/assets/javascripts/shortcuts_tree.coffee @@ -0,0 +1,4 @@ +class @ShortcutsTree extends ShortcutsNavigation + constructor: -> + super() + Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file')) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index d4ab6967ccd..97505edeabf 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -1,5 +1,13 @@ .tree-holder { + .file-finder { + width: 50%; + .file-finder-input { + width: 95%; + display: inline-block; + } + } + .tree-table { margin-bottom: 0; diff --git a/app/controllers/projects/find_file_controller.rb b/app/controllers/projects/find_file_controller.rb new file mode 100644 index 00000000000..54a0c447aee --- /dev/null +++ b/app/controllers/projects/find_file_controller.rb @@ -0,0 +1,26 @@ +# Controller for viewing a repository's file structure +class Projects::FindFileController < Projects::ApplicationController + include ExtractsPath + include ActionView::Helpers::SanitizeHelper + include TreeHelper + + before_action :require_non_empty_project + before_action :assign_ref_vars + before_action :authorize_download_code! + + def show + return render_404 unless @repository.commit(@ref) + + respond_to do |format| + format.html + end + end + + def list + file_paths = @repo.ls_files(@ref) + + respond_to do |format| + format.json { render json: file_paths } + end + end +end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index c4e18c17077..a8f091819ca 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -20,6 +20,8 @@ class Projects::RefsController < Projects::ApplicationController namespace_project_network_path(@project.namespace, @project, @id, @options) when "graphs" namespace_project_graph_path(@project.namespace, @project, @id) + when "find_file" + namespace_project_find_file_path(@project.namespace, @project, @id) when "graphs_commits" commits_namespace_project_graph_path(@project.namespace, @project, @id) else diff --git a/app/models/repository.rb b/app/models/repository.rb index 6ecd2d2f27e..9deb08d93b8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -681,6 +681,11 @@ class Repository end end + def ls_files(ref) + actual_ref = ref || root_ref + raw_repository.ls_files(actual_ref) + end + private def cache diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index e8e331dd109..9ee6f07b26b 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -40,6 +40,32 @@ %td.shortcut .key enter %td Open Selection + %tr + %td.shortcut + .key t + %td Go to finding file + %tbody + %tr + %th + %th Finding Project File + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tr + %td.shortcut + .key esc + %td Go back .col-lg-4 %table.shortcut-mappings diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d3eaf0f3209..270ccfd387f 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -25,7 +25,7 @@ %span Activity - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do = icon('files-o fw') %span @@ -117,4 +117,3 @@ %li.hidden = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do Network - diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml new file mode 100644 index 00000000000..08e2fc48be7 --- /dev/null +++ b/app/views/projects/_find_file_link.html.haml @@ -0,0 +1,3 @@ += link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do + = icon('search') + %span Find File diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml new file mode 100644 index 00000000000..2930209fb56 --- /dev/null +++ b/app/views/projects/find_file/show.html.haml @@ -0,0 +1,27 @@ +- page_title "Find File", @ref +- header_title project_title(@project, "Files", project_files_path(@project)) + +.file-finder-holder.tree-holder.clearfix + .gray-content-block.top-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'find_file', path: @path + %ul.breadcrumb.repo-breadcrumb + %li + = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do + = @project.path + %li.file-finder + %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path'} + + %div.tree-content-holder + .table-holder + %table.table.files-slider{class: "table_#{@hex_path} tree-table table-striped" } + %tbody + = spinner nil, true + +:coffeescript + projectFindFile = new ProjectFindFile($(".file-finder-holder"), { + url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}" + treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}" + blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}" + }) + new ShortcutsFindFile(projectFindFile) diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index ec14bd7f65a..c57570afa09 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -3,12 +3,12 @@ = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") - = render 'projects/last_push' -- if can? current_user, :download_code, @project - .tree-download-holder - = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true +.pull-right + = render 'projects/find_file_link' + - if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true #tree-holder.tree-holder.clearfix .gray-content-block.top-block diff --git a/config/routes.rb b/config/routes.rb index 3e7d9f78710..5b69d06eb76 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -440,6 +440,24 @@ Rails.application.routes.draw do ) 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', diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png index 68756ed1f98..e5914aa8e67 100644 Binary files a/doc/workflow/shortcuts.png and b/doc/workflow/shortcuts.png differ diff --git a/features/project/find_file.feature b/features/project/find_file.feature new file mode 100644 index 00000000000..ae8fa245923 --- /dev/null +++ b/features/project/find_file.feature @@ -0,0 +1,42 @@ +@dashboard +Feature: Project Find File + Background: + Given I sign in as a user + And I own a project + And I visit my project's files page + + @javascript + Scenario: Navigate to find file by shortcut + Given I press "t" + Then I should see "find file" page + + Scenario: Navigate to find file + Given I click Find File button + Then I should see "find file" page + + @javascript + Scenario: I search file + Given I visit project find file page + And I fill in file find with "change" + Then I should not see ".gitignore" in files + And I should not see ".gitmodules" in files + And I should see "CHANGELOG" in files + And I should not see "VERSION" in files + + @javascript + Scenario: I search file that not exist + Given I visit project find file page + And I fill in file find with "asdfghjklqwertyuizxcvbnm" + Then I should not see ".gitignore" in files + And I should not see ".gitmodules" in files + And I should not see "CHANGELOG" in files + And I should not see "VERSION" in files + + @javascript + Scenario: I search file that partially matches + Given I visit project find file page + And I fill in file find with "git" + Then I should see ".gitignore" in files + And I should see ".gitmodules" in files + And I should not see "CHANGELOG" in files + And I should not see "VERSION" in files diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb new file mode 100644 index 00000000000..8c1d09d6cc6 --- /dev/null +++ b/features/steps/project/project_find_file.rb @@ -0,0 +1,73 @@ +class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + include SharedProjectTab + + step 'I press "t"' do + find('body').native.send_key('t') + end + + step 'I click Find File button' do + click_link 'Find File' + end + + step 'I should see "find file" page' do + ensure_active_main_tab('Files') + expect(page).to have_selector('.file-finder-holder', count: 1) + end + + step 'I fill in Find by path with "git"' do + ensure_active_main_tab('Files') + expect(page).to have_selector('.file-finder-holder', count: 1) + end + + step 'I fill in file find with "git"' do + find_file "git" + end + + step 'I fill in file find with "change"' do + find_file "change" + end + + step 'I fill in file find with "asdfghjklqwertyuizxcvbnm"' do + find_file "asdfghjklqwertyuizxcvbnm" + end + + step 'I should see "VERSION" in files' do + expect(page).to have_content("VERSION") + end + + step 'I should not see "VERSION" in files' do + expect(page).not_to have_content("VERSION") + end + + step 'I should see "CHANGELOG" in files' do + expect(page).to have_content("CHANGELOG") + end + + step 'I should not see "CHANGELOG" in files' do + expect(page).not_to have_content("CHANGELOG") + end + + step 'I should see ".gitmodules" in files' do + expect(page).to have_content(".gitmodules") + end + + step 'I should not see ".gitmodules" in files' do + expect(page).not_to have_content(".gitmodules") + end + + step 'I should see ".gitignore" in files' do + expect(page).to have_content(".gitignore") + end + + step 'I should not see ".gitignore" in files' do + expect(page).not_to have_content(".gitignore") + end + + + def find_file(text) + fill_in 'file_find', with: text + end +end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index b33bd332655..4264c9c6f1a 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -259,6 +259,10 @@ module SharedPaths visit namespace_project_deploy_keys_path(@project.namespace, @project) end + step 'I visit project find file page' do + visit namespace_project_find_file_path(@project.namespace, @project, root_ref) + end + # ---------------------------------------- # "Shop" Project # ---------------------------------------- diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb new file mode 100644 index 00000000000..038dfeb8466 --- /dev/null +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Projects::FindFileController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + + project.team << [user, :master] + controller.instance_variable_set(:@project, project) + end + + describe "GET #show" do + # Make sure any errors accessing the tree in our views bubble up to this spec + render_views + + before do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id) + end + + context "valid branch" do + let(:id) { 'master' } + it { is_expected.to respond_with(:success) } + end + + context "invalid branch" do + let(:id) { 'invalid-branch' } + it { is_expected.to respond_with(:not_found) } + end + end + + describe "GET #list" do + def go(format: 'json') + get :list, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id, + format: format + end + + context "valid branch" do + let(:id) { 'master' } + it 'returns an array of file path list' do + go + + json = JSON.parse(response.body) + is_expected.to respond_with(:success) + expect(json).not_to eq(nil) + expect(json.length).to be >= 0 + end + end + + context "invalid branch" do + let(:id) { 'invalid-branch' } + + it 'responds with status 404' do + go + is_expected.to respond_with(:not_found) + end + end + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 82f62a8709c..2a70c190337 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -434,6 +434,18 @@ describe Projects::TreeController, 'routing' do end end +# project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/html/} +# project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/json/} +describe Projects::FindFileController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + end + + it 'to #list' do + expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end +end + describe Projects::BlobController, 'routing' do it 'to #edit' do expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.min.js b/vendor/assets/javascripts/fuzzaldrin-plus.min.js new file mode 100644 index 00000000000..3f25c2d8373 --- /dev/null +++ b/vendor/assets/javascripts/fuzzaldrin-plus.min.js @@ -0,0 +1 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0?maxInners:candidates.length;bAllowErrors=!!allowErrors;bKey=key!=null;prepQuery=scorer.prepQuery(query);if(!legacy){for(i=0,len=candidates.length;i0){scoredCandidates.push({candidate:candidate,score:score});if(!--spotLeft){break}}}}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;for(j=0,len1=candidates.length;j0){scoredCandidates.push({candidate:candidate,score:score})}}}scoredCandidates.sort(sortCandidates);candidates=scoredCandidates.map(pluckCandidates);if(maxResults!=null){candidates=candidates.slice(0,maxResults)}return candidates}}).call(this)},{"./legacy":4,"./scorer":6,"path":7}],2:[function(require,module,exports){(function(){var PathSeparator,filter,legacy_scorer,matcher,prepQueryCache,scorer;scorer=require('./scorer');legacy_scorer=require('./legacy');filter=require('./filter');matcher=require('./matcher');PathSeparator=require('path').sep;prepQueryCache=null;module.exports={filter:function(candidates,query,options){if(!((query!=null?query.length:void 0)&&(candidates!=null?candidates.length:void 0))){return[]}return filter(candidates,query,options)},prepQuery:function(query){return scorer.prepQuery(query)},score:function(string,query,prepQuery,arg){var allowErrors,coreQuery,legacy,queryHasSlashes,ref,score;ref=arg!=null?arg:{},allowErrors=ref.allowErrors,legacy=ref.legacy;if(!((string!=null?string.length:void 0)&&(query!=null?query.length:void 0))){return 0}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!legacy){score=scorer.score(string,query,prepQuery,!!allowErrors)}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}}return score},match:function(string,query,prepQuery,arg){var allowErrors,baseMatches,i,matches,query_lw,ref,results,string_lw;allowErrors=(arg!=null?arg:{}).allowErrors;if(!string){return[]}if(!query){return[]}if(string===query){return(function(){results=[];for(var i=0,ref=string.length;0<=ref?iref;0<=ref?i++:i--){results.push(i)}return results}).apply(this)}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!(allowErrors||scorer.isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return[]}string_lw=string.toLowerCase();query_lw=prepQuery.query_lw;matches=matcher.match(string,string_lw,prepQuery);if(matches.length===0){return matches}if(string.indexOf(PathSeparator)>-1){baseMatches=matcher.basenameMatch(string,string_lw,prepQuery);matches=matcher.mergeMatches(matches,baseMatches)}return matches}}}).call(this)},{"./filter":1,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],3:[function(require,module,exports){fuzzaldrinPlus=require('./fuzzaldrin')},{"./fuzzaldrin":2}],4:[function(require,module,exports){(function(){var PathSeparator,queryIsLastPathSegment;PathSeparator=require('path').sep;exports.basenameScore=function(string,query,score){var base,depth,index,lastCharacter,segmentCount,slashCount;index=string.length-1;while(string[index]===PathSeparator){index--}slashCount=0;lastCharacter=index;base=null;while(index>=0){if(string[index]===PathSeparator){slashCount++;if(base==null){base=string.substring(index+1,lastCharacter+1)}}else if(index===0){if(lastCharacterref;stringOffset<=ref?i++:i--){results.push(i)}return results}).apply(this)}queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;matches=[];while(indexInQuery0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return[]}}basePos++;end++;return exports.match(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery,basePos)};exports.mergeMatches=function(a,b){var ai,bj,i,j,m,n,out;m=a.length;n=b.length;if(n===0){return a.slice()}if(m===0){return b.slice()}i=-1;j=0;bj=b[j];out=[];while(++i0?csc_diag:scorer.scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scorer.scoreCharacter(i,j,start,acro_score,csc_score)}score_up=score_row[j];csc_diag=csc_row[j];if(score>score_up){move=LEFT}else{score=score_up;move=UP}if(align>score){score=align;move=DIAGONAL}else{csc_score=0}score_row[j]=score;csc_row[j]=csc_score;trace[++pos]=score>0?move:STOP}}i=m-1;j=n-1;pos=i*n+j;backtrack=true;matches=[];while(backtrack&&i>=0&&j>=0){switch(trace[pos]){case UP:i--;pos-=n;break;case LEFT:j--;pos--;break;case DIAGONAL:matches.push(i+offset);j--;i--;pos-=n+1;break;default:backtrack=false}}matches.reverse();return matches}}).call(this)},{"./scorer":6,"path":7}],6:[function(require,module,exports){(function(){var AcronymResult,PathSeparator,Query,basenameScore,coreChars,countDir,doScore,emptyAcronymResult,file_coeff,isMatch,isSeparator,isWordEnd,isWordStart,miss_coeff,opt_char_re,pos_bonus,scoreAcronyms,scoreCharacter,scoreConsecutives,scoreExact,scoreExactMatch,scorePattern,scorePosition,scoreSize,tau_depth,tau_size,truncatedUpperCase,wm;PathSeparator=require('path').sep;wm=150;pos_bonus=20;tau_depth=13;tau_size=85;file_coeff=1.2;miss_coeff=0.75;opt_char_re=/[ _\-:\/\\]/g;exports.coreChars=coreChars=function(query){return query.replace(opt_char_re,'')};exports.score=function(string,query,prepQuery,allowErrors){var score,string_lw;if(prepQuery==null){prepQuery=new Query(query)}if(allowErrors==null){allowErrors=false}if(!(allowErrors||isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return 0}string_lw=string.toLowerCase();score=doScore(string,string_lw,prepQuery);return Math.ceil(basenameScore(string,string_lw,prepQuery,score))};Query=(function(){function Query(query){if(!(query!=null?query.length:void 0)){return null}this.query=query;this.query_lw=query.toLowerCase();this.core=coreChars(query);this.core_lw=this.core.toLowerCase();this.core_up=truncatedUpperCase(this.core);this.depth=countDir(query,query.length)}return Query})();exports.prepQuery=function(query){return new Query(query)};exports.isMatch=isMatch=function(subject,query_lw,query_up){var i,j,m,n,qj_lw,qj_up,si;m=subject.length;n=query_lw.length;if(!m||!n||n>m){return false}i=-1;j=-1;while(++j-1){return scoreExactMatch(subject,subject_lw,query,query_lw,pos,n,m)}score_row=new Array(n);csc_row=new Array(n);sz=scoreSize(n,m);miss_budget=Math.ceil(miss_coeff*n)+5;miss_left=miss_budget;j=-1;while(++j-1){i--}mm=subject_lw.lastIndexOf(query_lw[n-1],m);if(mm>i){m=mm+1}while(++iscore){score=score_up}csc_score=0;if(query_lw[j]===si_lw){start=isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scoreCharacter(i,j,start,acro_score,csc_score);if(align>score){score=align;miss_left=miss_budget}else{if(record_miss&&--miss_left<=0){return score_row[n-1]*sz}record_miss=false}}score_diag=score_up;csc_diag=csc_row[j];csc_row[j]=csc_score;score_row[j]=score}}return score*sz};exports.isWordStart=isWordStart=function(pos,subject,subject_lw){var curr_s,prev_s;if(pos===0){return true}curr_s=subject[pos];prev_s=subject[pos-1];return isSeparator(curr_s)||isSeparator(prev_s)||(curr_s!==subject_lw[pos]&&prev_s===subject_lw[pos-1])};exports.isWordEnd=isWordEnd=function(pos,subject,subject_lw,len){var curr_s,next_s;if(pos===len-1){return true}curr_s=subject[pos];next_s=subject[pos+1];return isSeparator(curr_s)||isSeparator(next_s)||(curr_s===subject_lw[pos]&&next_s!==subject_lw[pos+1])};isSeparator=function(c){return c===' '||c==='.'||c==='-'||c==='_'||c==='/'||c==='\\'};scorePosition=function(pos){var sc;if(poscsc_score?acro_score:csc_score)+10)}return posBonus+wm*csc_score};exports.scoreConsecutives=scoreConsecutives=function(subject,subject_lw,query,query_lw,i,j,start){var k,m,mi,n,nj,sameCase,startPos,sz;m=subject.length;n=query.length;mi=m-i;nj=n-j;k=mi-1){start=isWordStart(pos2,subject,subject_lw);if(start){pos=pos2}}}i=-1;sameCase=0;while(++i1&&n>1)){return emptyAcronymResult}count=0;pos=0;sameCase=0;i=-1;j=-1;while(++j0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return fullPathScore}}basePos++;end++;basePathScore=doScore(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery);alpha=0.5*tau_depth/(tau_depth+countDir(subject,end+1));return alpha*basePathScore+(1-alpha)*fullPathScore*scoreSize(0,file_coeff*(end-basePos))};exports.countDir=countDir=function(path,end){var count,i;if(end<1){return 0}count=0;i=-1;while(++i=0;i--){var last=parts[i];if(last==='.'){parts.splice(i,1)}else if(last==='..'){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift('..')}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath='',resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=(i>=0)?arguments[i]:process.cwd();if(typeof path!=='string'){throw new TypeError('Arguments to path.resolve must be strings');}else if(!path){continue}resolvedPath=path+'/'+resolvedPath;resolvedAbsolute=path.charAt(0)==='/'}resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){return!!p}),!resolvedAbsolute).join('/');return((resolvedAbsolute?'/':'')+resolvedPath)||'.'};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==='/';path=normalizeArray(filter(path.split('/'),function(p){return!!p}),!isAbsolute).join('/');if(!path&&!isAbsolute){path='.'}if(path&&trailingSlash){path+='/'}return(isAbsolute?'/':'')+path};exports.isAbsolute=function(path){return path.charAt(0)==='/'};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=='string'){throw new TypeError('Arguments to path.join must be strings');}return p}).join('/'))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=='')break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split('/'));var toParts=trim(to.split('/'));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i1){for(var i=1;i Date: Thu, 7 Jan 2016 11:47:06 +0100 Subject: Revert "Store SQL/view timings in milliseconds" This reverts commit 7549102bb727daecc51da84af39956b32fc41537. Apparently I was wrong about ActiveSupport::Notifications::Event#duration returning the duration in seconds, instead it returns it in milliseconds already. --- lib/gitlab/metrics/subscribers/action_view.rb | 8 ++------ lib/gitlab/metrics/subscribers/active_record.rb | 6 +----- spec/lib/gitlab/metrics/subscribers/action_view_spec.rb | 4 ++-- spec/lib/gitlab/metrics/subscribers/active_record_spec.rb | 2 +- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb index 84d9e383625..7c0105d543a 100644 --- a/lib/gitlab/metrics/subscribers/action_view.rb +++ b/lib/gitlab/metrics/subscribers/action_view.rb @@ -19,7 +19,7 @@ module Gitlab values = values_for(event) tags = tags_for(event) - current_transaction.increment(:view_duration, duration(event)) + current_transaction.increment(:view_duration, event.duration) current_transaction.add_metric(SERIES, values, tags) end @@ -28,7 +28,7 @@ module Gitlab end def values_for(event) - { duration: duration(event) } + { duration: event.duration } end def tags_for(event) @@ -48,10 +48,6 @@ module Gitlab def current_transaction Transaction.current end - - def duration(event) - event.duration * 1000.0 - end end end end diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 6fa73e7a3be..8008b3bc895 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -8,7 +8,7 @@ module Gitlab def sql(event) return unless current_transaction - current_transaction.increment(:sql_duration, duration(event)) + current_transaction.increment(:sql_duration, event.duration) end private @@ -16,10 +16,6 @@ module Gitlab def current_transaction Transaction.current end - - def duration(event) - event.duration * 1000.0 - end end end end diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb index 0a4cc5e929b..05e4fbbeb51 100644 --- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb @@ -21,7 +21,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do describe '#render_template' do it 'tracks rendering of a template' do - values = { duration: 2100 } + values = { duration: 2.1 } tags = { view: 'app/views/x.html.haml', file: 'app/views/x.html.haml', @@ -29,7 +29,7 @@ describe Gitlab::Metrics::Subscribers::ActionView do } expect(transaction).to receive(:increment). - with(:view_duration, 2100) + with(:view_duration, 2.1) expect(transaction).to receive(:add_metric). with(described_class::SERIES, values, tags) diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb index ca86142a2f4..7bc070a4d09 100644 --- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb +++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Metrics::Subscribers::ActiveRecord do and_return(transaction) expect(transaction).to receive(:increment). - with(:sql_duration, 200) + with(:sql_duration, 0.2) subscriber.sql(event) end -- cgit v1.2.1 From 41b8a238ce4bd7f091d46fb9b89b7456fde17ddf Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 7 Jan 2016 12:56:18 +0100 Subject: Merge branch 'master' of github.com:gitlabhq/gitlabhq --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 2 +- app/assets/javascripts/application.js.coffee | 1 + app/assets/javascripts/dispatcher.js.coffee | 4 +- app/assets/javascripts/project_find_file.js.coffee | 125 +++++++++++++++++++++ .../javascripts/shortcuts_find_file.js.coffee | 19 ++++ app/assets/javascripts/shortcuts_tree.coffee | 4 + app/assets/stylesheets/pages/tree.scss | 8 ++ app/controllers/projects/find_file_controller.rb | 26 +++++ app/controllers/projects/refs_controller.rb | 2 + app/models/repository.rb | 5 + app/views/help/_shortcuts.html.haml | 26 +++++ app/views/layouts/nav/_project.html.haml | 3 +- app/views/projects/_find_file_link.html.haml | 3 + app/views/projects/find_file/show.html.haml | 27 +++++ app/views/projects/tree/show.html.haml | 8 +- config/routes.rb | 18 +++ doc/workflow/shortcuts.png | Bin 78736 -> 48782 bytes features/project/find_file.feature | 42 +++++++ features/steps/project/project_find_file.rb | 73 ++++++++++++ features/steps/shared/paths.rb | 4 + .../projects/find_file_controller_spec.rb | 66 +++++++++++ spec/routing/project_routing_spec.rb | 12 ++ vendor/assets/javascripts/fuzzaldrin-plus.min.js | 1 + 25 files changed, 473 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/project_find_file.js.coffee create mode 100644 app/assets/javascripts/shortcuts_find_file.js.coffee create mode 100644 app/assets/javascripts/shortcuts_tree.coffee create mode 100644 app/controllers/projects/find_file_controller.rb create mode 100644 app/views/projects/_find_file_link.html.haml create mode 100644 app/views/projects/find_file/show.html.haml create mode 100644 features/project/find_file.feature create mode 100644 features/steps/project/project_find_file.rb create mode 100644 spec/controllers/projects/find_file_controller_spec.rb create mode 100644 vendor/assets/javascripts/fuzzaldrin-plus.min.js diff --git a/CHANGELOG b/CHANGELOG index e7f1d2b67da..22fb91baaf0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -106,6 +106,7 @@ v 8.3.0 - 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) + - Add file finder feature in tree view v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) diff --git a/Gemfile b/Gemfile index 6145745b6f3..6b0bc241494 100644 --- a/Gemfile +++ b/Gemfile @@ -49,7 +49,7 @@ gem "browser", '~> 1.0.0' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.20' +gem "gitlab_git", '~> 7.2.22' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 2b42f325503..a1168ed3b7a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -887,7 +887,7 @@ DEPENDENCIES github-markup (~> 1.3.1) gitlab-flowdock-git-hook (~> 1.0.1) gitlab_emoji (~> 0.2.0) - gitlab_git (~> 7.2.20) + gitlab_git (~> 7.2.22) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.1.0) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index b9b095e004a..c095e5ae2b1 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -40,6 +40,7 @@ #= require shortcuts_network #= require jquery.nicescroll.min #= require_tree . +#= require fuzzaldrin-plus.min window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 69e061ce6e9..58d6b9d4060 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -87,7 +87,9 @@ class Dispatcher new GroupAvatar() when 'projects:tree:show' new TreeView() - shortcut_handler = new ShortcutsNavigation() + shortcut_handler = new ShortcutsTree() + when 'projects:find_file:show' + shortcut_handler = true when 'projects:blob:show' new LineHighlighter() shortcut_handler = new ShortcutsNavigation() diff --git a/app/assets/javascripts/project_find_file.js.coffee b/app/assets/javascripts/project_find_file.js.coffee new file mode 100644 index 00000000000..0dd32352c34 --- /dev/null +++ b/app/assets/javascripts/project_find_file.js.coffee @@ -0,0 +1,125 @@ +class @ProjectFindFile + constructor: (@element, @options)-> + @filePaths = {} + @inputElement = @element.find(".file-finder-input") + + # init event + @initEvent() + + # focus text input box + @inputElement.focus() + + # load file list + @load(@options.url) + + # init event + initEvent: -> + @inputElement.off "keyup" + @inputElement.on "keyup", (event) => + target = $(event.target) + value = target.val() + oldValue = target.data("oldValue") ? "" + + if value != oldValue + target.data("oldValue", value) + @findFile() + @element.find("tr.tree-item").eq(0).addClass("selected").focus() + + @element.find(".tree-content-holder .tree-table").on "click", (event) -> + if (event.target.nodeName != "A") + path = @element.find(".tree-item-file-name a", this).attr("href") + location.href = path if path + + # find file + findFile: -> + searchText = @inputElement.val() + result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths + @renderList result, searchText + + # files pathes load + load: (url) -> + $.ajax + url: url + method: "get" + dataType: "json" + success: (data) => + @element.find(".loading").hide() + @filePaths = data + @findFile() + @element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus() + + # render result + renderList: (filePaths, searchText) -> + @element.find(".tree-table > tbody").empty() + + for filePath, i in filePaths + break if i == 20 + + if searchText + matches = fuzzaldrinPlus.match(filePath, searchText) + + blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}" + + html = @makeHtml filePath, matches, blobItemUrl + @element.find(".tree-table > tbody").append(html) + + # highlight text(awefwbwgtc -> awefwbwgtc ) + highlighter = (element, text, matches) -> + lastIndex = 0 + highlightText = "" + matchedChars = [] + + for matchIndex in matches + unmatched = text.substring(lastIndex, matchIndex) + + if unmatched + element.append(matchedChars.join("").bold()) if matchedChars.length + matchedChars = [] + element.append(document.createTextNode(unmatched)) + + matchedChars.push(text[matchIndex]) + lastIndex = matchIndex + 1 + + element.append(matchedChars.join("").bold()) if matchedChars.length + element.append(document.createTextNode(text.substring(lastIndex))) + + # make tbody row html + makeHtml: (filePath, matches, blobItemUrl) -> + $tr = $("") + if matches + $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl)) + else + $tr.find("a").attr("href", blobItemUrl).text(filePath) + + return $tr + + selectRow: (type) -> + rows = @element.find(".files-slider tr.tree-item") + selectedRow = @element.find(".files-slider tr.tree-item.selected") + + if rows && rows.length > 0 + if selectedRow && selectedRow.length > 0 + if type == "UP" + next = selectedRow.prev() + else if type == "DOWN" + next = selectedRow.next() + + if next.length > 0 + selectedRow.removeClass "selected" + selectedRow = next + else + selectedRow = rows.eq(0) + selectedRow.addClass("selected").focus() + + selectRowUp: => + @selectRow "UP" + + selectRowDown: => + @selectRow "DOWN" + + goToTree: => + location.href = @options.treeUrl + + goToBlob: => + path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href") + location.href = path if path diff --git a/app/assets/javascripts/shortcuts_find_file.js.coffee b/app/assets/javascripts/shortcuts_find_file.js.coffee new file mode 100644 index 00000000000..311e80bae19 --- /dev/null +++ b/app/assets/javascripts/shortcuts_find_file.js.coffee @@ -0,0 +1,19 @@ +#= require shortcuts_navigation + +class @ShortcutsFindFile extends ShortcutsNavigation + constructor: (@projectFindFile) -> + super() + _oldStopCallback = Mousetrap.stopCallback + # override to fire shortcuts action when focus in textbox + Mousetrap.stopCallback = (event, element, combo) => + if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter') + # when press up/down key in textbox, cusor prevent to move to home/end + event.preventDefault() + return false + + return _oldStopCallback(event, element, combo) + + Mousetrap.bind('up', @projectFindFile.selectRowUp) + Mousetrap.bind('down', @projectFindFile.selectRowDown) + Mousetrap.bind('esc', @projectFindFile.goToTree) + Mousetrap.bind('enter', @projectFindFile.goToBlob) diff --git a/app/assets/javascripts/shortcuts_tree.coffee b/app/assets/javascripts/shortcuts_tree.coffee new file mode 100644 index 00000000000..ba0839c9fc0 --- /dev/null +++ b/app/assets/javascripts/shortcuts_tree.coffee @@ -0,0 +1,4 @@ +class @ShortcutsTree extends ShortcutsNavigation + constructor: -> + super() + Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file')) diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index d4ab6967ccd..97505edeabf 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -1,5 +1,13 @@ .tree-holder { + .file-finder { + width: 50%; + .file-finder-input { + width: 95%; + display: inline-block; + } + } + .tree-table { margin-bottom: 0; diff --git a/app/controllers/projects/find_file_controller.rb b/app/controllers/projects/find_file_controller.rb new file mode 100644 index 00000000000..54a0c447aee --- /dev/null +++ b/app/controllers/projects/find_file_controller.rb @@ -0,0 +1,26 @@ +# Controller for viewing a repository's file structure +class Projects::FindFileController < Projects::ApplicationController + include ExtractsPath + include ActionView::Helpers::SanitizeHelper + include TreeHelper + + before_action :require_non_empty_project + before_action :assign_ref_vars + before_action :authorize_download_code! + + def show + return render_404 unless @repository.commit(@ref) + + respond_to do |format| + format.html + end + end + + def list + file_paths = @repo.ls_files(@ref) + + respond_to do |format| + format.json { render json: file_paths } + end + end +end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index c4e18c17077..a8f091819ca 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -20,6 +20,8 @@ class Projects::RefsController < Projects::ApplicationController namespace_project_network_path(@project.namespace, @project, @id, @options) when "graphs" namespace_project_graph_path(@project.namespace, @project, @id) + when "find_file" + namespace_project_find_file_path(@project.namespace, @project, @id) when "graphs_commits" commits_namespace_project_graph_path(@project.namespace, @project, @id) else diff --git a/app/models/repository.rb b/app/models/repository.rb index 6ecd2d2f27e..9deb08d93b8 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -681,6 +681,11 @@ class Repository end end + def ls_files(ref) + actual_ref = ref || root_ref + raw_repository.ls_files(actual_ref) + end + private def cache diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index e8e331dd109..9ee6f07b26b 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -40,6 +40,32 @@ %td.shortcut .key enter %td Open Selection + %tr + %td.shortcut + .key t + %td Go to finding file + %tbody + %tr + %th + %th Finding Project File + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tr + %td.shortcut + .key esc + %td Go back .col-lg-4 %table.shortcut-mappings diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index d3eaf0f3209..270ccfd387f 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -25,7 +25,7 @@ %span Activity - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do + = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do = icon('files-o fw') %span @@ -117,4 +117,3 @@ %li.hidden = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do Network - diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml new file mode 100644 index 00000000000..08e2fc48be7 --- /dev/null +++ b/app/views/projects/_find_file_link.html.haml @@ -0,0 +1,3 @@ += link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do + = icon('search') + %span Find File diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml new file mode 100644 index 00000000000..2930209fb56 --- /dev/null +++ b/app/views/projects/find_file/show.html.haml @@ -0,0 +1,27 @@ +- page_title "Find File", @ref +- header_title project_title(@project, "Files", project_files_path(@project)) + +.file-finder-holder.tree-holder.clearfix + .gray-content-block.top-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'find_file', path: @path + %ul.breadcrumb.repo-breadcrumb + %li + = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do + = @project.path + %li.file-finder + %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path'} + + %div.tree-content-holder + .table-holder + %table.table.files-slider{class: "table_#{@hex_path} tree-table table-striped" } + %tbody + = spinner nil, true + +:coffeescript + projectFindFile = new ProjectFindFile($(".file-finder-holder"), { + url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}" + treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}" + blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}" + }) + new ShortcutsFindFile(projectFindFile) diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index ec14bd7f65a..c57570afa09 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -3,12 +3,12 @@ = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits") - = render 'projects/last_push' -- if can? current_user, :download_code, @project - .tree-download-holder - = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true +.pull-right + = render 'projects/find_file_link' + - if can? current_user, :download_code, @project + = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true #tree-holder.tree-holder.clearfix .gray-content-block.top-block diff --git a/config/routes.rb b/config/routes.rb index 3e7d9f78710..5b69d06eb76 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -440,6 +440,24 @@ Rails.application.routes.draw do ) 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', diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png index 68756ed1f98..e5914aa8e67 100644 Binary files a/doc/workflow/shortcuts.png and b/doc/workflow/shortcuts.png differ diff --git a/features/project/find_file.feature b/features/project/find_file.feature new file mode 100644 index 00000000000..ae8fa245923 --- /dev/null +++ b/features/project/find_file.feature @@ -0,0 +1,42 @@ +@dashboard +Feature: Project Find File + Background: + Given I sign in as a user + And I own a project + And I visit my project's files page + + @javascript + Scenario: Navigate to find file by shortcut + Given I press "t" + Then I should see "find file" page + + Scenario: Navigate to find file + Given I click Find File button + Then I should see "find file" page + + @javascript + Scenario: I search file + Given I visit project find file page + And I fill in file find with "change" + Then I should not see ".gitignore" in files + And I should not see ".gitmodules" in files + And I should see "CHANGELOG" in files + And I should not see "VERSION" in files + + @javascript + Scenario: I search file that not exist + Given I visit project find file page + And I fill in file find with "asdfghjklqwertyuizxcvbnm" + Then I should not see ".gitignore" in files + And I should not see ".gitmodules" in files + And I should not see "CHANGELOG" in files + And I should not see "VERSION" in files + + @javascript + Scenario: I search file that partially matches + Given I visit project find file page + And I fill in file find with "git" + Then I should see ".gitignore" in files + And I should see ".gitmodules" in files + And I should not see "CHANGELOG" in files + And I should not see "VERSION" in files diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb new file mode 100644 index 00000000000..8c1d09d6cc6 --- /dev/null +++ b/features/steps/project/project_find_file.rb @@ -0,0 +1,73 @@ +class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + include SharedProjectTab + + step 'I press "t"' do + find('body').native.send_key('t') + end + + step 'I click Find File button' do + click_link 'Find File' + end + + step 'I should see "find file" page' do + ensure_active_main_tab('Files') + expect(page).to have_selector('.file-finder-holder', count: 1) + end + + step 'I fill in Find by path with "git"' do + ensure_active_main_tab('Files') + expect(page).to have_selector('.file-finder-holder', count: 1) + end + + step 'I fill in file find with "git"' do + find_file "git" + end + + step 'I fill in file find with "change"' do + find_file "change" + end + + step 'I fill in file find with "asdfghjklqwertyuizxcvbnm"' do + find_file "asdfghjklqwertyuizxcvbnm" + end + + step 'I should see "VERSION" in files' do + expect(page).to have_content("VERSION") + end + + step 'I should not see "VERSION" in files' do + expect(page).not_to have_content("VERSION") + end + + step 'I should see "CHANGELOG" in files' do + expect(page).to have_content("CHANGELOG") + end + + step 'I should not see "CHANGELOG" in files' do + expect(page).not_to have_content("CHANGELOG") + end + + step 'I should see ".gitmodules" in files' do + expect(page).to have_content(".gitmodules") + end + + step 'I should not see ".gitmodules" in files' do + expect(page).not_to have_content(".gitmodules") + end + + step 'I should see ".gitignore" in files' do + expect(page).to have_content(".gitignore") + end + + step 'I should not see ".gitignore" in files' do + expect(page).not_to have_content(".gitignore") + end + + + def find_file(text) + fill_in 'file_find', with: text + end +end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index b33bd332655..4264c9c6f1a 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -259,6 +259,10 @@ module SharedPaths visit namespace_project_deploy_keys_path(@project.namespace, @project) end + step 'I visit project find file page' do + visit namespace_project_find_file_path(@project.namespace, @project, root_ref) + end + # ---------------------------------------- # "Shop" Project # ---------------------------------------- diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb new file mode 100644 index 00000000000..038dfeb8466 --- /dev/null +++ b/spec/controllers/projects/find_file_controller_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe Projects::FindFileController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + + project.team << [user, :master] + controller.instance_variable_set(:@project, project) + end + + describe "GET #show" do + # Make sure any errors accessing the tree in our views bubble up to this spec + render_views + + before do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id) + end + + context "valid branch" do + let(:id) { 'master' } + it { is_expected.to respond_with(:success) } + end + + context "invalid branch" do + let(:id) { 'invalid-branch' } + it { is_expected.to respond_with(:not_found) } + end + end + + describe "GET #list" do + def go(format: 'json') + get :list, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: id, + format: format + end + + context "valid branch" do + let(:id) { 'master' } + it 'returns an array of file path list' do + go + + json = JSON.parse(response.body) + is_expected.to respond_with(:success) + expect(json).not_to eq(nil) + expect(json.length).to be >= 0 + end + end + + context "invalid branch" do + let(:id) { 'invalid-branch' } + + it 'responds with status 404' do + go + is_expected.to respond_with(:not_found) + end + end + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 82f62a8709c..2a70c190337 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -434,6 +434,18 @@ describe Projects::TreeController, 'routing' do end end +# project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/html/} +# project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/json/} +describe Projects::FindFileController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + end + + it 'to #list' do + expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end +end + describe Projects::BlobController, 'routing' do it 'to #edit' do expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.min.js b/vendor/assets/javascripts/fuzzaldrin-plus.min.js new file mode 100644 index 00000000000..3f25c2d8373 --- /dev/null +++ b/vendor/assets/javascripts/fuzzaldrin-plus.min.js @@ -0,0 +1 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0?maxInners:candidates.length;bAllowErrors=!!allowErrors;bKey=key!=null;prepQuery=scorer.prepQuery(query);if(!legacy){for(i=0,len=candidates.length;i0){scoredCandidates.push({candidate:candidate,score:score});if(!--spotLeft){break}}}}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;for(j=0,len1=candidates.length;j0){scoredCandidates.push({candidate:candidate,score:score})}}}scoredCandidates.sort(sortCandidates);candidates=scoredCandidates.map(pluckCandidates);if(maxResults!=null){candidates=candidates.slice(0,maxResults)}return candidates}}).call(this)},{"./legacy":4,"./scorer":6,"path":7}],2:[function(require,module,exports){(function(){var PathSeparator,filter,legacy_scorer,matcher,prepQueryCache,scorer;scorer=require('./scorer');legacy_scorer=require('./legacy');filter=require('./filter');matcher=require('./matcher');PathSeparator=require('path').sep;prepQueryCache=null;module.exports={filter:function(candidates,query,options){if(!((query!=null?query.length:void 0)&&(candidates!=null?candidates.length:void 0))){return[]}return filter(candidates,query,options)},prepQuery:function(query){return scorer.prepQuery(query)},score:function(string,query,prepQuery,arg){var allowErrors,coreQuery,legacy,queryHasSlashes,ref,score;ref=arg!=null?arg:{},allowErrors=ref.allowErrors,legacy=ref.legacy;if(!((string!=null?string.length:void 0)&&(query!=null?query.length:void 0))){return 0}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!legacy){score=scorer.score(string,query,prepQuery,!!allowErrors)}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}}return score},match:function(string,query,prepQuery,arg){var allowErrors,baseMatches,i,matches,query_lw,ref,results,string_lw;allowErrors=(arg!=null?arg:{}).allowErrors;if(!string){return[]}if(!query){return[]}if(string===query){return(function(){results=[];for(var i=0,ref=string.length;0<=ref?iref;0<=ref?i++:i--){results.push(i)}return results}).apply(this)}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!(allowErrors||scorer.isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return[]}string_lw=string.toLowerCase();query_lw=prepQuery.query_lw;matches=matcher.match(string,string_lw,prepQuery);if(matches.length===0){return matches}if(string.indexOf(PathSeparator)>-1){baseMatches=matcher.basenameMatch(string,string_lw,prepQuery);matches=matcher.mergeMatches(matches,baseMatches)}return matches}}}).call(this)},{"./filter":1,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],3:[function(require,module,exports){fuzzaldrinPlus=require('./fuzzaldrin')},{"./fuzzaldrin":2}],4:[function(require,module,exports){(function(){var PathSeparator,queryIsLastPathSegment;PathSeparator=require('path').sep;exports.basenameScore=function(string,query,score){var base,depth,index,lastCharacter,segmentCount,slashCount;index=string.length-1;while(string[index]===PathSeparator){index--}slashCount=0;lastCharacter=index;base=null;while(index>=0){if(string[index]===PathSeparator){slashCount++;if(base==null){base=string.substring(index+1,lastCharacter+1)}}else if(index===0){if(lastCharacterref;stringOffset<=ref?i++:i--){results.push(i)}return results}).apply(this)}queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;matches=[];while(indexInQuery0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return[]}}basePos++;end++;return exports.match(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery,basePos)};exports.mergeMatches=function(a,b){var ai,bj,i,j,m,n,out;m=a.length;n=b.length;if(n===0){return a.slice()}if(m===0){return b.slice()}i=-1;j=0;bj=b[j];out=[];while(++i0?csc_diag:scorer.scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scorer.scoreCharacter(i,j,start,acro_score,csc_score)}score_up=score_row[j];csc_diag=csc_row[j];if(score>score_up){move=LEFT}else{score=score_up;move=UP}if(align>score){score=align;move=DIAGONAL}else{csc_score=0}score_row[j]=score;csc_row[j]=csc_score;trace[++pos]=score>0?move:STOP}}i=m-1;j=n-1;pos=i*n+j;backtrack=true;matches=[];while(backtrack&&i>=0&&j>=0){switch(trace[pos]){case UP:i--;pos-=n;break;case LEFT:j--;pos--;break;case DIAGONAL:matches.push(i+offset);j--;i--;pos-=n+1;break;default:backtrack=false}}matches.reverse();return matches}}).call(this)},{"./scorer":6,"path":7}],6:[function(require,module,exports){(function(){var AcronymResult,PathSeparator,Query,basenameScore,coreChars,countDir,doScore,emptyAcronymResult,file_coeff,isMatch,isSeparator,isWordEnd,isWordStart,miss_coeff,opt_char_re,pos_bonus,scoreAcronyms,scoreCharacter,scoreConsecutives,scoreExact,scoreExactMatch,scorePattern,scorePosition,scoreSize,tau_depth,tau_size,truncatedUpperCase,wm;PathSeparator=require('path').sep;wm=150;pos_bonus=20;tau_depth=13;tau_size=85;file_coeff=1.2;miss_coeff=0.75;opt_char_re=/[ _\-:\/\\]/g;exports.coreChars=coreChars=function(query){return query.replace(opt_char_re,'')};exports.score=function(string,query,prepQuery,allowErrors){var score,string_lw;if(prepQuery==null){prepQuery=new Query(query)}if(allowErrors==null){allowErrors=false}if(!(allowErrors||isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return 0}string_lw=string.toLowerCase();score=doScore(string,string_lw,prepQuery);return Math.ceil(basenameScore(string,string_lw,prepQuery,score))};Query=(function(){function Query(query){if(!(query!=null?query.length:void 0)){return null}this.query=query;this.query_lw=query.toLowerCase();this.core=coreChars(query);this.core_lw=this.core.toLowerCase();this.core_up=truncatedUpperCase(this.core);this.depth=countDir(query,query.length)}return Query})();exports.prepQuery=function(query){return new Query(query)};exports.isMatch=isMatch=function(subject,query_lw,query_up){var i,j,m,n,qj_lw,qj_up,si;m=subject.length;n=query_lw.length;if(!m||!n||n>m){return false}i=-1;j=-1;while(++j-1){return scoreExactMatch(subject,subject_lw,query,query_lw,pos,n,m)}score_row=new Array(n);csc_row=new Array(n);sz=scoreSize(n,m);miss_budget=Math.ceil(miss_coeff*n)+5;miss_left=miss_budget;j=-1;while(++j-1){i--}mm=subject_lw.lastIndexOf(query_lw[n-1],m);if(mm>i){m=mm+1}while(++iscore){score=score_up}csc_score=0;if(query_lw[j]===si_lw){start=isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scoreCharacter(i,j,start,acro_score,csc_score);if(align>score){score=align;miss_left=miss_budget}else{if(record_miss&&--miss_left<=0){return score_row[n-1]*sz}record_miss=false}}score_diag=score_up;csc_diag=csc_row[j];csc_row[j]=csc_score;score_row[j]=score}}return score*sz};exports.isWordStart=isWordStart=function(pos,subject,subject_lw){var curr_s,prev_s;if(pos===0){return true}curr_s=subject[pos];prev_s=subject[pos-1];return isSeparator(curr_s)||isSeparator(prev_s)||(curr_s!==subject_lw[pos]&&prev_s===subject_lw[pos-1])};exports.isWordEnd=isWordEnd=function(pos,subject,subject_lw,len){var curr_s,next_s;if(pos===len-1){return true}curr_s=subject[pos];next_s=subject[pos+1];return isSeparator(curr_s)||isSeparator(next_s)||(curr_s===subject_lw[pos]&&next_s!==subject_lw[pos+1])};isSeparator=function(c){return c===' '||c==='.'||c==='-'||c==='_'||c==='/'||c==='\\'};scorePosition=function(pos){var sc;if(poscsc_score?acro_score:csc_score)+10)}return posBonus+wm*csc_score};exports.scoreConsecutives=scoreConsecutives=function(subject,subject_lw,query,query_lw,i,j,start){var k,m,mi,n,nj,sameCase,startPos,sz;m=subject.length;n=query.length;mi=m-i;nj=n-j;k=mi-1){start=isWordStart(pos2,subject,subject_lw);if(start){pos=pos2}}}i=-1;sameCase=0;while(++i1&&n>1)){return emptyAcronymResult}count=0;pos=0;sameCase=0;i=-1;j=-1;while(++j0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return fullPathScore}}basePos++;end++;basePathScore=doScore(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery);alpha=0.5*tau_depth/(tau_depth+countDir(subject,end+1));return alpha*basePathScore+(1-alpha)*fullPathScore*scoreSize(0,file_coeff*(end-basePos))};exports.countDir=countDir=function(path,end){var count,i;if(end<1){return 0}count=0;i=-1;while(++i=0;i--){var last=parts[i];if(last==='.'){parts.splice(i,1)}else if(last==='..'){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift('..')}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath='',resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=(i>=0)?arguments[i]:process.cwd();if(typeof path!=='string'){throw new TypeError('Arguments to path.resolve must be strings');}else if(!path){continue}resolvedPath=path+'/'+resolvedPath;resolvedAbsolute=path.charAt(0)==='/'}resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){return!!p}),!resolvedAbsolute).join('/');return((resolvedAbsolute?'/':'')+resolvedPath)||'.'};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==='/';path=normalizeArray(filter(path.split('/'),function(p){return!!p}),!isAbsolute).join('/');if(!path&&!isAbsolute){path='.'}if(path&&trailingSlash){path+='/'}return(isAbsolute?'/':'')+path};exports.isAbsolute=function(path){return path.charAt(0)==='/'};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=='string'){throw new TypeError('Arguments to path.join must be strings');}return p}).join('/'))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=='')break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split('/'));var toParts=trim(to.split('/'));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i1){for(var i=1;i