Commit 41b8a238 authored by Jacob Vosmaer's avatar Jacob Vosmaer
Browse files

Merge branch 'master' of

parent da912c8f
......@@ -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)
......@@ -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
......@@ -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)
......@@ -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()
......@@ -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()
class @ProjectFindFile
constructor: (@element, @options)->
@filePaths = {}
@inputElement = @element.find(".file-finder-input")
# init event
# focus text input box
# load file list
# init event
initEvent: -> "keyup"
@inputElement.on "keyup", (event) =>
target = $(
value = target.val()
oldValue ="oldValue") ? ""
if value != oldValue"oldValue", value)
@element.find(".tree-content-holder .tree-table").on "click", (event) ->
if ( != "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) ->
url: url
method: "get"
dataType: "json"
success: (data) =>
@filePaths = data
@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 -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
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 = []
lastIndex = matchIndex + 1
element.append(matchedChars.join("").bold()) if matchedChars.length
# make tbody row html
makeHtml: (filePath, matches, blobItemUrl) ->
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>")
if matches
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl))
$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 =
if next.length > 0
selectedRow.removeClass "selected"
selectedRow = next
selectedRow = rows.eq(0)
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
#= require shortcuts_navigation
class @ShortcutsFindFile extends ShortcutsNavigation
constructor: (@projectFindFile) ->
_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
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)
class @ShortcutsTree extends ShortcutsNavigation
constructor: ->
Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file'))
.tree-holder {
.file-finder {
width: 50%;
.file-finder-input {
width: 95%;
display: inline-block;
.tree-table {
margin-bottom: 0;
# 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|
def list
file_paths = @repo.ls_files(@ref)
respond_to do |format|
format.json { render json: file_paths }
......@@ -20,6 +20,8 @@ def switch
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)
......@@ -681,6 +681,11 @@ def commit_with_hooks(current_user, branch)
def ls_files(ref)
actual_ref = ref || root_ref
def cache
......@@ -40,6 +40,32 @@
.key enter
%td Open Selection
.key t
%td Go to finding file
%th Finding Project File
%td Move selection up
%td Move selection down
.key enter
%td Open Selection
.key esc
%td Go back
......@@ -25,7 +25,7 @@
- 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')
......@@ -117,4 +117,3 @@
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
= 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
- page_title "Find File", @ref
- header_title project_title(@project, "Files", project_files_path(@project))
= render 'shared/ref_switcher', destination: 'find_file', path: @path
= link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
= @project.path
%input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path'}
%table.table.files-slider{class: "table_#{@hex_path} tree-table table-striped" }
= spinner nil, true
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 ||}"
new ShortcutsFindFile(projectFindFile)
......@@ -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: "#{}:#{@ref} commits")
= render 'projects/last_push'
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
= 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
......@@ -440,6 +440,24 @@
scope do
to: 'find_file#show',
constraints: { id: /.+/, format: /html/ },
as: :find_file
scope do
to: 'find_file#list',
constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ },
as: :files
scope do

76.9 KB | W: | H:


47.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
Feature: Project Find File
Given I sign in as a user
And I own a project
And I visit my project's files page
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
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
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
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
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment