Skip to content

Commit 6ce3d4d

Browse files
committed
Handle ignored files with quoted (non-ASCII) filenames
1 parent dd8e8d4 commit 6ce3d4d

File tree

4 files changed

+74
-10
lines changed

4 files changed

+74
-10
lines changed

lib/git/base.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,13 @@ def grep(string, path_limiter = nil, opts = {})
309309
self.object('HEAD').grep(string, path_limiter, opts)
310310
end
311311

312+
# List the files in the worktree that are ignored by git
313+
# @return [Array<String>] the list of ignored files relative to teh root of the worktree
314+
#
315+
def ignored_files
316+
self.lib.ignored_files
317+
end
318+
312319
# removes file(s) from the git repository
313320
def rm(path = '.', opts = {})
314321
self.lib.rm(path, opts)

lib/git/escaped_path.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
module Git
44
# Represents an escaped Git path string
55
#
6-
# Git commands that output paths (e.g. ls-files, diff), will escape usual
6+
# Git commands that output paths (e.g. ls-files, diff), will escape unusual
77
# characters in the path with backslashes in the same way C escapes control
88
# characters (e.g. \t for TAB, \n for LF, \\ for backslash) or bytes with values
99
# larger than 0x80 (e.g. octal \302\265 for "micro" in UTF-8).

lib/git/lib.rb

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -574,18 +574,52 @@ def diff_index(treeish)
574574
diff_as_hash('diff-index', treeish)
575575
end
576576

577+
# List all files that are in the index
578+
#
579+
# @param location [String] the location to list the files from
580+
#
581+
# @return [Hash<String, Hash>] a hash of files in the index
582+
# * key: file [String] the file path
583+
# * value: file_info [Hash] the file information containing the following keys:
584+
# * :path [String] the file path
585+
# * :mode_index [String] the file mode
586+
# * :sha_index [String] the file sha
587+
# * :stage [String] the file stage
588+
#
577589
def ls_files(location=nil)
578590
location ||= '.'
579-
hsh = {}
580-
command_lines('ls-files', '--stage', location).each do |line|
581-
(info, file) = line.split("\t")
582-
(mode, sha, stage) = info.split
583-
if file.start_with?('"') && file.end_with?('"')
584-
file = Git::EscapedPath.new(file[1..-2]).unescape
591+
{}.tap do |files|
592+
command_lines('ls-files', '--stage', location).each do |line|
593+
(info, file) = line.split("\t")
594+
(mode, sha, stage) = info.split
595+
files[unescape_quoted_path(file)] = {
596+
:path => file, :mode_index => mode, :sha_index => sha, :stage => stage
597+
}
585598
end
586-
hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
587599
end
588-
hsh
600+
end
601+
602+
# Unescape a path if it is quoted
603+
#
604+
# Git commands that output paths (e.g. ls-files, diff), will escape unusual
605+
# characters.
606+
#
607+
# @example
608+
# lib.unescape_if_quoted('"quoted_file_\\342\\230\\240"') # => 'quoted_file_☠'
609+
# lib.unescape_if_quoted('unquoted_file') # => 'unquoted_file'
610+
#
611+
# @param path [String] the path to unescape if quoted
612+
#
613+
# @return [String] the unescaped path if quoted otherwise the original path
614+
#
615+
# @api private
616+
#
617+
def unescape_quoted_path(path)
618+
if path.start_with?('"') && path.end_with?('"')
619+
Git::EscapedPath.new(path[1..-2]).unescape
620+
else
621+
path
622+
end
589623
end
590624

591625
def ls_remote(location=nil, opts={})
@@ -606,7 +640,7 @@ def ls_remote(location=nil, opts={})
606640
end
607641

608642
def ignored_files
609-
command_lines('ls-files', '--others', '-i', '--exclude-standard')
643+
command_lines('ls-files', '--others', '-i', '--exclude-standard').map { |f| unescape_quoted_path(f) }
610644
end
611645

612646
def untracked_files
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env ruby
2+
# encoding: utf-8
3+
4+
require 'test_helper'
5+
6+
# Test diff when the file path has to be quoted according to core.quotePath
7+
# See https://git-scm.com/docs/git-config#Documentation/git-config.txt-corequotePath
8+
#
9+
class TestIgnoredFilesWithEscapedPath < Test::Unit::TestCase
10+
def test_ignored_files_with_non_ascii_filename
11+
in_temp_dir do |path|
12+
create_file('README.md', '# My Project')
13+
`git init`
14+
`git add .`
15+
`git config --local core.safecrlf false` if Gem.win_platform?
16+
`git commit -m "First Commit"`
17+
create_file('my_other_file_☠', "First Line\n")
18+
create_file(".gitignore", "my_other_file_☠")
19+
files = Git.open('.').ignored_files
20+
assert_equal(['my_other_file_☠'].sort, files)
21+
end
22+
end
23+
end

0 commit comments

Comments
 (0)