Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion path_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,32 @@ package fsnotify
import (
"strings"
"syscall"
"unicode"
"unicode/utf8"
)

// pathKey returns a comparison key for p. NTFS is case-insensitive, so
// fold to lowercase before using a path as a map key.
func pathKey(p string) string {
return strings.ToLower(p)
isASCII := true
for i := 0; i < len(p); i++ {
if p[i] >= utf8.RuneSelf {
isASCII = false
break
}
}
if isASCII { // optimize for ASCII-only strings.
return strings.ToLower(p)
}

return strings.Map(func(r rune) rune {
if r == 'ß' || r == 'ẞ' {
// NTFS is case-insensitive, but as an exception,
// it distinguishes between uppercase and lowercase Sharp S.
return r
}
return unicode.ToLower(r)
}, p)
}

// canonicalizeOS expands an 8.3 short-form path (e.g. C:\PROGRA~1) to
Expand Down
91 changes: 59 additions & 32 deletions watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,43 +519,70 @@ func TestAddCaseInsensitive(t *testing.T) {
}

func TestAddSharpS(t *testing.T) {
if runtime.GOOS != "darwin" {
t.Skip("This test is only applicable on macOS")
}
switch runtime.GOOS {
case "darwin":
// APFS is case-insensitive.
// Uppercase and lowercase Sharp S are considered the same path on APFS.
// Furthermore, "ss" and "SS" are also considered the same path as "ß" and "ẞ".

parent := tempDir(t)
w := newWatcher(t)

dir := filepath.Join(parent, "ß") // LATIN SMALL LETTER SHARP S
if err := os.Mkdir(dir, 0o755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if err := w.Add(dir, All); err != nil {
t.Fatalf("Add: %v", err)
}

parent := tempDir(t)
w := newWatcher(t)
dir = filepath.Join(parent, "ẞ") // LATIN CAPITAL LETTER SHARP S
if err := os.Mkdir(dir, 0o755); !errors.Is(err, os.ErrExist) {
t.Errorf("Mkdir(ẞ) = %v, want os.ErrExist", err)
}
if err := w.Add(dir, All); !errors.Is(err, ErrAlreadyAdded) {
t.Errorf("Add(ẞ) = %v, want ErrAlreadyAdded", err)
}

dir := filepath.Join(parent, "ß") // LATIN SMALL LETTER SHARP S
if err := os.Mkdir(dir, 0o755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
if err := w.Add(dir, All); err != nil {
t.Fatalf("Add: %v", err)
}
dir = filepath.Join(parent, "ss")
if err := os.Mkdir(dir, 0o755); !errors.Is(err, os.ErrExist) {
t.Errorf("Mkdir(ss) = %v, want os.ErrExist", err)
}
if err := w.Add(dir, All); !errors.Is(err, ErrAlreadyAdded) {
t.Errorf("Add(ss) = %v, want ErrAlreadyAdded", err)
}

dir = filepath.Join(parent, "ẞ") // LATIN CAPITAL LETTER SHARP S
if err := os.Mkdir(dir, 0o755); !errors.Is(err, os.ErrExist) {
t.Errorf("Mkdir() = %v, want os.ErrExist", err)
}
if err := w.Add(dir, All); !errors.Is(err, ErrAlreadyAdded) {
t.Errorf("Add() = %v, want ErrAlreadyAdded", err)
}
dir = filepath.Join(parent, "SS")
if err := os.Mkdir(dir, 0o755); !errors.Is(err, os.ErrExist) {
t.Errorf("Mkdir(SS) = %v, want os.ErrExist", err)
}
if err := w.Add(dir, All); !errors.Is(err, ErrAlreadyAdded) {
t.Errorf("Add(SS) = %v, want ErrAlreadyAdded", err)
}

dir = filepath.Join(parent, "ss")
if err := os.Mkdir(dir, 0o755); !errors.Is(err, os.ErrExist) {
t.Errorf("Mkdir(ss) = %v, want os.ErrExist", err)
}
if err := w.Add(dir, All); !errors.Is(err, ErrAlreadyAdded) {
t.Errorf("Add(ss) = %v, want ErrAlreadyAdded", err)
}
default:
// On other platforms, including Windows, "ß" and "ẞ" are considered different paths.
// NTFS is case-insensitive, but as an exception,
// it distinguishes between uppercase and lowercase Sharp S.

dir = filepath.Join(parent, "SS")
if err := os.Mkdir(dir, 0o755); !errors.Is(err, os.ErrExist) {
t.Errorf("Mkdir(SS) = %v, want os.ErrExist", err)
}
if err := w.Add(dir, All); !errors.Is(err, ErrAlreadyAdded) {
t.Errorf("Add(SS) = %v, want ErrAlreadyAdded", err)
parent := tempDir(t)
w := newWatcher(t)

dir1 := filepath.Join(parent, "ß") // LATIN SMALL LETTER SHARP S
if err := os.Mkdir(dir1, 0o755); err != nil {
t.Fatalf("Mkdir(ß): %v", err)
}
if err := w.Add(dir1, All); err != nil {
t.Fatalf("Add(ß): %v", err)
}

dir2 := filepath.Join(parent, "ẞ") // LATIN CAPITAL LETTER SHARP S
if err := os.Mkdir(dir2, 0o755); err != nil {
t.Fatalf("Mkdir(ẞ): %v", err)
}
if err := w.Add(dir2, All); err != nil {
t.Fatalf("Add(ẞ): %v", err)
}
}
}

Expand Down
Loading