Skip to content

Commit 8e6a11e

Browse files
kaorukobojcouball
authored andcommitted
fix: properly parse UTF-8(multibyte) file paths in git output
1 parent 3d2c473 commit 8e6a11e

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

lib/git/lib.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ def ls_tree(sha, opts = {})
643643
args << opts[:path] if opts[:path]
644644

645645
command_lines('ls-tree', *args).each do |line|
646-
(info, filenm) = line.split("\t")
646+
(info, filenm) = split_status_line(line)
647647
(mode, type, sha) = info.split
648648
data[type][filenm] = { mode: mode, sha: sha }
649649
end
@@ -905,9 +905,9 @@ def ls_files(location = nil)
905905
location ||= '.'
906906
{}.tap do |files|
907907
command_lines('ls-files', '--stage', location).each do |line|
908-
(info, file) = line.split("\t")
908+
(info, file) = split_status_line(line)
909909
(mode, sha, stage) = info.split
910-
files[unescape_quoted_path(file)] = {
910+
files[file] = {
911911
path: file, mode_index: mode, sha_index: sha, stage: stage
912912
}
913913
end
@@ -956,7 +956,9 @@ def ignored_files
956956
end
957957

958958
def untracked_files
959-
command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir)
959+
command_lines('ls-files', '--others', '--exclude-standard', chdir: @git_work_dir).map do |f|
960+
unescape_quoted_path(f)
961+
end
960962
end
961963

962964
def config_remote(name)
@@ -1602,7 +1604,7 @@ def self.warn_if_old_command(lib) # rubocop:disable Naming/PredicateMethod
16021604

16031605
def parse_diff_path_status(args)
16041606
command_lines('diff', *args).each_with_object({}) do |line, memo|
1605-
status, path = line.split("\t")
1607+
status, path = split_status_line(line)
16061608
memo[path] = status
16071609
end
16081610
end
@@ -1727,7 +1729,7 @@ def parse_diff_stats_output(lines)
17271729

17281730
def parse_stat_lines(lines)
17291731
lines.map do |line|
1730-
insertions_s, deletions_s, filename = line.split("\t")
1732+
insertions_s, deletions_s, filename = split_status_line(line)
17311733
{
17321734
filename: filename,
17331735
insertions: insertions_s.to_i,
@@ -1736,6 +1738,12 @@ def parse_stat_lines(lines)
17361738
end
17371739
end
17381740

1741+
def split_status_line(line)
1742+
parts = line.split("\t")
1743+
parts[-1] = unescape_quoted_path(parts[-1]) if parts.any?
1744+
parts
1745+
end
1746+
17391747
def build_final_stats_hash(file_stats)
17401748
{
17411749
total: build_total_stats(file_stats),
@@ -1965,7 +1973,7 @@ def diff_as_hash(diff_command, opts = [])
19651973
# update index before diffing to avoid spurious diffs
19661974
command('status')
19671975
command_lines(diff_command, *opts).each_with_object({}) do |line, memo|
1968-
info, file = line.split("\t")
1976+
info, file = split_status_line(line)
19691977
mode_src, mode_dest, sha_src, sha_dest, type = info.split
19701978

19711979
memo[file] = {

tests/units/test_status.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,54 @@ def test_changed_cache
235235
assert(!git.status.changed?('test_file_1'))
236236
end
237237
end
238+
239+
def test_multibyte_path
240+
# a name consisting of UTF-8 characters
241+
multibyte_name = "\u30DE\u30EB\u30C1\u30D0\u30A4\u30C8\u6587\u5B57\u30D5\u30A1\u30A4\u30EB\u263A"
242+
243+
in_temp_dir do |_path|
244+
`git init`
245+
246+
File.write('file1', 'contents1')
247+
`git add file1`
248+
`git commit -m "my message"`
249+
250+
git = Git.open('.')
251+
252+
# Test added
253+
File.write("#{multibyte_name}_added.txt", 'contents_mb_added')
254+
`git add #{multibyte_name}_added.txt`
255+
256+
status = git.status
257+
assert_equal(1, status.added.size)
258+
assert_equal(["#{multibyte_name}_added.txt"], status.added.keys)
259+
260+
# Test untracked
261+
File.write("#{multibyte_name}_untracked.txt", 'contents_mb_untracked')
262+
263+
status = git.status
264+
assert_equal(1, status.untracked.size)
265+
assert_equal(["#{multibyte_name}_untracked.txt"], status.untracked.keys)
266+
267+
# Test changed
268+
File.write("#{multibyte_name}_changed.txt", 'original_content')
269+
`git add #{multibyte_name}_changed.txt`
270+
`git commit -m "add multibyte file"`
271+
File.write("#{multibyte_name}_changed.txt", 'modified_content')
272+
273+
status = git.status
274+
assert_equal(1, status.changed.size)
275+
assert_equal(["#{multibyte_name}_changed.txt"], status.changed.keys)
276+
277+
# Test deleted
278+
File.write("#{multibyte_name}_deleted.txt", 'to_be_deleted')
279+
`git add #{multibyte_name}_deleted.txt`
280+
`git commit -m "add file to be deleted"`
281+
File.delete("#{multibyte_name}_deleted.txt")
282+
283+
status = git.status
284+
assert_equal(1, status.deleted.size)
285+
assert_equal(["#{multibyte_name}_deleted.txt"], status.deleted.keys)
286+
end
287+
end
238288
end

0 commit comments

Comments
 (0)