From 546959fe6d9b3448a289b0ecfc402bfe9c566dc3 Mon Sep 17 00:00:00 2001
From: Ibrahim AshShohail <ibra.sho@gmail.com>
Date: Wed, 2 Aug 2017 15:32:20 +0300
Subject: [PATCH 1/6] internal/fs: add EqualPaths()

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
---
 context.go             |  4 ++--
 internal/fs/fs.go      | 31 ++++++++++++++++++++++++++++++-
 internal/fs/fs_test.go | 40 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/context.go b/context.go
index f149df106b..84476a1d82 100644
--- a/context.go
+++ b/context.go
@@ -179,7 +179,7 @@ func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
 	pGOPATH, perr := c.detectGOPATH(p.AbsRoot)
 
 	// If p.AbsRoot is a not symlink, attempt to detect GOPATH for p.AbsRoot only.
-	if p.AbsRoot == p.ResolvedAbsRoot {
+	if equal, _ := fs.EqualPaths(p.AbsRoot, p.ResolvedAbsRoot); equal {
 		return pGOPATH, perr
 	}
 
@@ -191,7 +191,7 @@ func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
 	}
 
 	// If pGOPATH equals rGOPATH, then both are within the same GOPATH.
-	if pGOPATH == rGOPATH {
+	if equal, _ := fs.EqualPaths(pGOPATH, rGOPATH); equal {
 		return "", errors.Errorf("both %s and %s are in the same GOPATH %s", p.AbsRoot, p.ResolvedAbsRoot, pGOPATH)
 	}
 
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 90d6f87908..69a1fe0536 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -100,6 +100,36 @@ func HasFilepathPrefix(path, prefix string) bool {
 	return true
 }
 
+// EqualPaths compares the paths passed to check if the are equivalent.
+// It respects the case-sensitivity of the underlaying filesysyems.
+func EqualPaths(p1, p2 string) (bool, error) {
+	p1 = filepath.Clean(p1)
+	p2 = filepath.Clean(p2)
+
+	if isDir, err := IsDir(p2); err != nil && !strings.HasSuffix(err.Error(), "is not a directory") {
+		return false, err
+	} else if !isDir {
+		// If p2 is not a directory, compare the bases of the paths to ensure
+		// they are equivalent.
+		var p1Base, p2Base string
+
+		p1, p1Base = filepath.Split(p1)
+		p2, p2Base = filepath.Split(p2)
+
+		if isCaseSensitiveFilesystem(p1) {
+			if p1Base != p2Base {
+				return false, nil
+			}
+		} else {
+			if strings.ToLower(p1Base) != strings.ToLower(p2Base) {
+				return false, nil
+			}
+		}
+	}
+
+	return len(p1) == len(p2) && HasFilepathPrefix(p1, p2), nil
+}
+
 // RenameWithFallback attempts to rename a file or directory, but falls back to
 // copying in the event of a cross-device link error. If the fallback copy
 // succeeds, src is still removed, emulating normal rename behavior.
@@ -343,7 +373,6 @@ func cloneSymlink(sl, dst string) error {
 
 // IsDir determines is the path given is a directory or not.
 func IsDir(name string) (bool, error) {
-	// TODO: lstat?
 	fi, err := os.Stat(name)
 	if err != nil {
 		return false, err
diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go
index e16f772c9c..f6849fddd6 100644
--- a/internal/fs/fs_test.go
+++ b/internal/fs/fs_test.go
@@ -134,6 +134,46 @@ func TestHasFilepathPrefix_Files(t *testing.T) {
 	}
 }
 
+func TestEqualPaths(t *testing.T) {
+	h := test.NewHelper(t)
+	h.TempDir("dir")
+	h.TempDir("dir2")
+
+	h.TempFile("file", "")
+	h.TempFile("file2", "")
+
+	h.TempDir("DIR")
+	h.TempFile("FILE", "")
+
+	// caseInsensitive flag ensures that these cases are only equivalent on a
+	// case-insensitive filesystem.
+	caseInsensitive := isCaseSensitiveFilesystem(h.Path("dir"))
+
+	testcases := []struct {
+		p1, p2 string
+		want   bool
+		err    bool
+	}{
+		{h.Path("dir"), h.Path("dir"), true, false},
+		{h.Path("file"), h.Path("file"), true, false},
+		{h.Path("dir"), h.Path("dir2"), false, false},
+		{h.Path("file"), h.Path("file2"), false, false},
+		{h.Path("dir"), h.Path("file"), false, false},
+		{h.Path("dir"), h.Path("DIR"), caseInsensitive, false},
+		{strings.ToLower(h.Path("dir")), strings.ToUpper(h.Path("dir")), caseInsensitive, true},
+	}
+
+	for _, tc := range testcases {
+		got, err := EqualPaths(tc.p1, tc.p2)
+		if err != nil && !tc.err {
+			t.Error("unexpected error:", err)
+		}
+		if got != tc.want {
+			t.Errorf("expected EqualPaths(%q, %q) to be %t, got %t", tc.p1, tc.p2, tc.want, got)
+		}
+	}
+}
+
 func TestRenameWithFallback(t *testing.T) {
 	dir, err := ioutil.TempDir("", "dep")
 	if err != nil {

From 8878146855a3223925e9ec0eb54d5507ff94181f Mon Sep 17 00:00:00 2001
From: Ibrahim AshShohail <ibra.sho@gmail.com>
Date: Fri, 4 Aug 2017 14:39:34 +0300
Subject: [PATCH 2/6] internal/fs: fix isCaseSensitiveFilesystem bug

isCaseSensitiveFilesystem returns true when os.Stat fails on the dir
passed. This caused HasFilepathPrefix to always treat *nix systems as
case-sensitive, since it passed a relative path (missing the root) to
isCaseSensitiveFilesystem.

This commit updates isCaseSensitiveFilesystem to return an error in
addtion to the boolean check. Also, HasFilepathPrefix now passes absolute
paths to isCaseSensitiveFilesystem.

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
---
 internal/fs/fs.go      | 72 ++++++++++++++++++++++++++----------------
 internal/fs/fs_test.go | 40 +++++++++++++----------
 2 files changed, 69 insertions(+), 43 deletions(-)

diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 69a1fe0536..01dadf1bda 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -45,13 +45,11 @@ func HasFilepathPrefix(path, prefix string) bool {
 		return false
 	}
 
-	// because filepath.Join("c:","dir") returns "c:dir", we have to manually add path separator to drive letters
-	if strings.HasSuffix(vnPath, ":") {
-		vnPath += string(os.PathSeparator)
-	}
-	if strings.HasSuffix(vnPrefix, ":") {
-		vnPrefix += string(os.PathSeparator)
-	}
+	// Because filepath.Join("c:","dir") returns "c:dir", we have to manually
+	// add path separator to drive letters. Also, we need to set the path root
+	// on *nix systems, since filepath.Join("", "dir") returns a relative path.
+	vnPath += string(os.PathSeparator)
+	vnPrefix += string(os.PathSeparator)
 
 	var dn string
 
@@ -74,7 +72,7 @@ func HasFilepathPrefix(path, prefix string) bool {
 		return false
 	}
 
-	// d,p are initialized with "" on *nix and volume name on Windows
+	// d,p are initialized with "/" on *nix and volume name on Windows
 	d := vnPath
 	p := vnPrefix
 
@@ -84,7 +82,11 @@ func HasFilepathPrefix(path, prefix string) bool {
 		// something like ext4 filesystem mounted on FAT
 		// mountpoint, mounted on ext4 filesystem, i.e. the
 		// problematic filesystem is not the last one.
-		if isCaseSensitiveFilesystem(filepath.Join(d, dirs[i])) {
+		caseSensitive, err := isCaseSensitiveFilesystem(filepath.Join(d, dirs[i]))
+		if err != nil {
+			return false
+		}
+		if caseSensitive {
 			d = filepath.Join(d, dirs[i])
 			p = filepath.Join(p, prefixes[i])
 		} else {
@@ -106,28 +108,45 @@ func EqualPaths(p1, p2 string) (bool, error) {
 	p1 = filepath.Clean(p1)
 	p2 = filepath.Clean(p2)
 
-	if isDir, err := IsDir(p2); err != nil && !strings.HasSuffix(err.Error(), "is not a directory") {
-		return false, err
-	} else if !isDir {
-		// If p2 is not a directory, compare the bases of the paths to ensure
-		// they are equivalent.
-		var p1Base, p2Base string
+	fi1, err := os.Stat(p1)
+	if err != nil {
+		return false, errors.Wrapf(err, "could not check for path equivalence")
+	}
+	fi2, err := os.Stat(p2)
+	if err != nil {
+		return false, errors.Wrapf(err, "could not check for path equivalence")
+	}
 
-		p1, p1Base = filepath.Split(p1)
-		p2, p2Base = filepath.Split(p2)
+	p1Filename, p2Filename := "", ""
 
-		if isCaseSensitiveFilesystem(p1) {
-			if p1Base != p2Base {
+	if !fi1.IsDir() {
+		p1, p1Filename = filepath.Split(p1)
+	}
+	if !fi2.IsDir() {
+		p2, p2Filename = filepath.Split(p2)
+	}
+
+	if !HasFilepathPrefix(p1, p2) || !HasFilepathPrefix(p2, p1) {
+		return false, nil
+	}
+
+	if p1Filename != "" || p2Filename != "" {
+		caseSensitive, err := isCaseSensitiveFilesystem(filepath.Join(p1, p1Filename))
+		if err != nil {
+			return false, errors.Wrap(err, "could not check for filesystem case-sensitivity")
+		}
+		if caseSensitive {
+			if p1Filename != p2Filename {
 				return false, nil
 			}
 		} else {
-			if strings.ToLower(p1Base) != strings.ToLower(p2Base) {
+			if strings.ToLower(p1Filename) != strings.ToLower(p2Filename) {
 				return false, nil
 			}
 		}
 	}
 
-	return len(p1) == len(p2) && HasFilepathPrefix(p1, p2), nil
+	return true, nil
 }
 
 // RenameWithFallback attempts to rename a file or directory, but falls back to
@@ -189,21 +208,20 @@ func renameByCopy(src, dst string) error {
 // If the input directory is such that the last component is composed
 // exclusively of case-less codepoints (e.g.  numbers), this function will
 // return false.
-func isCaseSensitiveFilesystem(dir string) bool {
-	alt := filepath.Join(filepath.Dir(dir),
-		genTestFilename(filepath.Base(dir)))
+func isCaseSensitiveFilesystem(dir string) (bool, error) {
+	alt := filepath.Join(filepath.Dir(dir), genTestFilename(filepath.Base(dir)))
 
 	dInfo, err := os.Stat(dir)
 	if err != nil {
-		return true
+		return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem")
 	}
 
 	aInfo, err := os.Stat(alt)
 	if err != nil {
-		return true
+		return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem")
 	}
 
-	return !os.SameFile(dInfo, aInfo)
+	return !os.SameFile(dInfo, aInfo), nil
 }
 
 // genTestFilename returns a string with at most one rune case-flipped.
diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go
index f6849fddd6..a52cefacb6 100644
--- a/internal/fs/fs_test.go
+++ b/internal/fs/fs_test.go
@@ -145,22 +145,24 @@ func TestEqualPaths(t *testing.T) {
 	h.TempDir("DIR")
 	h.TempFile("FILE", "")
 
-	// caseInsensitive flag ensures that these cases are only equivalent on a
-	// case-insensitive filesystem.
-	caseInsensitive := isCaseSensitiveFilesystem(h.Path("dir"))
-
 	testcases := []struct {
-		p1, p2 string
-		want   bool
-		err    bool
+		p1, p2                   string
+		caseSensitiveEquivalent  bool
+		caseInensitiveEquivalent bool
+		err                      bool
 	}{
-		{h.Path("dir"), h.Path("dir"), true, false},
-		{h.Path("file"), h.Path("file"), true, false},
-		{h.Path("dir"), h.Path("dir2"), false, false},
-		{h.Path("file"), h.Path("file2"), false, false},
-		{h.Path("dir"), h.Path("file"), false, false},
-		{h.Path("dir"), h.Path("DIR"), caseInsensitive, false},
-		{strings.ToLower(h.Path("dir")), strings.ToUpper(h.Path("dir")), caseInsensitive, true},
+		{h.Path("dir"), h.Path("dir"), true, true, false},
+		{h.Path("file"), h.Path("file"), true, true, false},
+		{h.Path("dir"), h.Path("dir2"), false, false, false},
+		{h.Path("file"), h.Path("file2"), false, false, false},
+		{h.Path("dir"), h.Path("file"), false, false, false},
+		{h.Path("dir"), h.Path("DIR"), false, true, false},
+		{strings.ToLower(h.Path("dir")), strings.ToUpper(h.Path("dir")), false, true, true},
+	}
+
+	caseSensitive, err := isCaseSensitiveFilesystem(h.Path("dir"))
+	if err != nil {
+		t.Fatal("unexpcted error:", err)
 	}
 
 	for _, tc := range testcases {
@@ -168,8 +170,14 @@ func TestEqualPaths(t *testing.T) {
 		if err != nil && !tc.err {
 			t.Error("unexpected error:", err)
 		}
-		if got != tc.want {
-			t.Errorf("expected EqualPaths(%q, %q) to be %t, got %t", tc.p1, tc.p2, tc.want, got)
+		if caseSensitive {
+			if tc.caseSensitiveEquivalent != got {
+				t.Errorf("expected EqualPaths(%q, %q) to be %t on case-sensitive filesystem, got %t", tc.p1, tc.p2, tc.caseSensitiveEquivalent, got)
+			}
+		} else {
+			if tc.caseInensitiveEquivalent != got {
+				t.Errorf("expected EqualPaths(%q, %q) to be %t on case-insensitive filesystem, got %t", tc.p1, tc.p2, tc.caseInensitiveEquivalent, got)
+			}
 		}
 	}
 }

From d94a0f3b370ebbb1a3c2632f6b1294ce961e1381 Mon Sep 17 00:00:00 2001
From: Ibrahim AshShohail <ibra.sho@gmail.com>
Date: Fri, 4 Aug 2017 16:21:05 +0300
Subject: [PATCH 3/6] fix tests

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
---
 context_test.go | 40 +++++++++++++++-------------------------
 1 file changed, 15 insertions(+), 25 deletions(-)

diff --git a/context_test.go b/context_test.go
index e522d25b4d..503ed1beaf 100644
--- a/context_test.go
+++ b/context_test.go
@@ -314,23 +314,16 @@ func TestDetectProjectGOPATH(t *testing.T) {
 	h := test.NewHelper(t)
 	defer h.Cleanup()
 
-	h.TempDir("go")
-	h.TempDir("go-two")
+	h.TempDir(filepath.Join("sym", "symlink"))
+	h.TempDir(filepath.Join("go", "src", "sym", "path"))
+	h.TempDir(filepath.Join("go", "src", "real", "path"))
+	h.TempDir(filepath.Join("go-two", "src", "real", "path"))
+	h.TempDir(filepath.Join("go-two", "src", "sym"))
 
 	ctx := &Ctx{
 		GOPATHs: []string{h.Path("go"), h.Path("go-two")},
 	}
 
-	h.TempDir("go/src/real/path")
-
-	// Another directory used as a GOPATH
-	h.TempDir("go-two/src/sym")
-
-	h.TempDir(filepath.Join(".", "sym/symlink")) // Directory for symlinks
-	h.TempDir(filepath.Join("go", "src", "sym", "path"))
-	h.TempDir(filepath.Join("go", "src", " real", "path"))
-	h.TempDir(filepath.Join("go-two", "src", "real", "path"))
-
 	testcases := []struct {
 		name         string
 		root         string
@@ -383,7 +376,7 @@ func TestDetectProjectGOPATH(t *testing.T) {
 		{
 			name:         "AbsRoot-is-a-symlink-to-ResolvedAbsRoot",
 			root:         filepath.Join(h.Path("."), "sym", "symlink"),
-			resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", " real", "path"),
+			resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
 			GOPATH:       ctx.GOPATHs[0],
 		},
 	}
@@ -412,36 +405,33 @@ func TestDetectGOPATH(t *testing.T) {
 	th := test.NewHelper(t)
 	defer th.Cleanup()
 
-	th.TempDir("go")
-	th.TempDir("gotwo")
+	th.TempDir(filepath.Join("code", "src", "github.com", "username", "package"))
+	th.TempDir(filepath.Join("go", "src", "github.com", "username", "package"))
+	th.TempDir(filepath.Join("gotwo", "src", "github.com", "username", "package"))
 
 	ctx := &Ctx{GOPATHs: []string{
 		th.Path("go"),
 		th.Path("gotwo"),
 	}}
 
-	th.TempDir(filepath.Join("code", "src", "github.com", "username", "package"))
-	th.TempDir(filepath.Join("go", "src", "github.com", "username", "package"))
-	th.TempDir(filepath.Join("gotwo", "src", "github.com", "username", "package"))
-
 	testcases := []struct {
 		GOPATH string
 		path   string
 		err    bool
 	}{
-		{th.Path("go"), filepath.Join(th.Path("go"), "src/github.com/username/package"), false},
-		{th.Path("go"), filepath.Join(th.Path("go"), "src/github.com/username/package"), false},
-		{th.Path("gotwo"), filepath.Join(th.Path("gotwo"), "src/github.com/username/package"), false},
-		{"", filepath.Join(th.Path("."), "code/src/github.com/username/package"), true},
+		{th.Path("go"), th.Path(filepath.Join("go", "src", "github.com", "username", "package")), false},
+		{th.Path("go"), th.Path(filepath.Join("go", "src", "github.com", "username", "package")), false},
+		{th.Path("gotwo"), th.Path(filepath.Join("gotwo", "src", "github.com", "username", "package")), false},
+		{"", th.Path(filepath.Join("code", "src", "github.com", "username", "package")), true},
 	}
 
 	for _, tc := range testcases {
 		GOPATH, err := ctx.detectGOPATH(tc.path)
 		if tc.err && err == nil {
-			t.Error("Expected error but got none")
+			t.Error("expected error but got none")
 		}
 		if GOPATH != tc.GOPATH {
-			t.Errorf("Expected GOPATH to be %s, got %s", GOPATH, tc.GOPATH)
+			t.Errorf("expected GOPATH to be %s, got %s", GOPATH, tc.GOPATH)
 		}
 	}
 }

From a0e33dd19b39a31c5008af3e0285336e50c90efd Mon Sep 17 00:00:00 2001
From: Ibrahim AshShohail <ibra.sho@gmail.com>
Date: Tue, 8 Aug 2017 21:54:08 +0300
Subject: [PATCH 4/6] internal/fs: rename EqualPaths to EquivalentPaths

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
---
 context.go             | 4 ++--
 internal/fs/fs.go      | 4 ++--
 internal/fs/fs_test.go | 8 ++++----
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/context.go b/context.go
index 84476a1d82..4426be4c46 100644
--- a/context.go
+++ b/context.go
@@ -179,7 +179,7 @@ func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
 	pGOPATH, perr := c.detectGOPATH(p.AbsRoot)
 
 	// If p.AbsRoot is a not symlink, attempt to detect GOPATH for p.AbsRoot only.
-	if equal, _ := fs.EqualPaths(p.AbsRoot, p.ResolvedAbsRoot); equal {
+	if equal, _ := fs.EquivalentPaths(p.AbsRoot, p.ResolvedAbsRoot); equal {
 		return pGOPATH, perr
 	}
 
@@ -191,7 +191,7 @@ func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
 	}
 
 	// If pGOPATH equals rGOPATH, then both are within the same GOPATH.
-	if equal, _ := fs.EqualPaths(pGOPATH, rGOPATH); equal {
+	if equal, _ := fs.EquivalentPaths(pGOPATH, rGOPATH); equal {
 		return "", errors.Errorf("both %s and %s are in the same GOPATH %s", p.AbsRoot, p.ResolvedAbsRoot, pGOPATH)
 	}
 
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 01dadf1bda..2e8b11656a 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -102,9 +102,9 @@ func HasFilepathPrefix(path, prefix string) bool {
 	return true
 }
 
-// EqualPaths compares the paths passed to check if the are equivalent.
+// EquivalentPaths compares the paths passed to check if the are equivalent.
 // It respects the case-sensitivity of the underlaying filesysyems.
-func EqualPaths(p1, p2 string) (bool, error) {
+func EquivalentPaths(p1, p2 string) (bool, error) {
 	p1 = filepath.Clean(p1)
 	p2 = filepath.Clean(p2)
 
diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go
index a52cefacb6..3c4c38596d 100644
--- a/internal/fs/fs_test.go
+++ b/internal/fs/fs_test.go
@@ -134,7 +134,7 @@ func TestHasFilepathPrefix_Files(t *testing.T) {
 	}
 }
 
-func TestEqualPaths(t *testing.T) {
+func TestEquivalentPaths(t *testing.T) {
 	h := test.NewHelper(t)
 	h.TempDir("dir")
 	h.TempDir("dir2")
@@ -166,17 +166,17 @@ func TestEqualPaths(t *testing.T) {
 	}
 
 	for _, tc := range testcases {
-		got, err := EqualPaths(tc.p1, tc.p2)
+		got, err := EquivalentPaths(tc.p1, tc.p2)
 		if err != nil && !tc.err {
 			t.Error("unexpected error:", err)
 		}
 		if caseSensitive {
 			if tc.caseSensitiveEquivalent != got {
-				t.Errorf("expected EqualPaths(%q, %q) to be %t on case-sensitive filesystem, got %t", tc.p1, tc.p2, tc.caseSensitiveEquivalent, got)
+				t.Errorf("expected EquivalentPaths(%q, %q) to be %t on case-sensitive filesystem, got %t", tc.p1, tc.p2, tc.caseSensitiveEquivalent, got)
 			}
 		} else {
 			if tc.caseInensitiveEquivalent != got {
-				t.Errorf("expected EqualPaths(%q, %q) to be %t on case-insensitive filesystem, got %t", tc.p1, tc.p2, tc.caseInensitiveEquivalent, got)
+				t.Errorf("expected EquivalentPaths(%q, %q) to be %t on case-insensitive filesystem, got %t", tc.p1, tc.p2, tc.caseInensitiveEquivalent, got)
 			}
 		}
 	}

From d93ac9874d6809e6a44412c7ed2d8291b53d86dc Mon Sep 17 00:00:00 2001
From: Ibrahim AshShohail <ibra.sho@gmail.com>
Date: Tue, 15 Aug 2017 07:44:51 +0300
Subject: [PATCH 5/6] fix typos

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
---
 internal/fs/fs.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 2e8b11656a..9b0525e6ad 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -102,8 +102,8 @@ func HasFilepathPrefix(path, prefix string) bool {
 	return true
 }
 
-// EquivalentPaths compares the paths passed to check if the are equivalent.
-// It respects the case-sensitivity of the underlaying filesysyems.
+// EquivalentPaths compares the paths passed to check if they are equivalent.
+// It respects the case-sensitivity of the underlying filesysyems.
 func EquivalentPaths(p1, p2 string) (bool, error) {
 	p1 = filepath.Clean(p1)
 	p2 = filepath.Clean(p2)

From 67f83fc6938f540922a3037ff090490165357253 Mon Sep 17 00:00:00 2001
From: Ibrahim AshShohail <ibra.sho@gmail.com>
Date: Mon, 21 Aug 2017 22:06:27 +0300
Subject: [PATCH 6/6] internal/fs: fix bug in isCaseSensitiveFilesystem

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
---
 cmd/dep/gopath_scanner.go |  2 +-
 context.go                | 12 ++++++++++--
 internal/fs/fs.go         | 25 +++++++++++++++++--------
 internal/fs/fs_test.go    | 17 +++++++++++++----
 4 files changed, 41 insertions(+), 15 deletions(-)

diff --git a/cmd/dep/gopath_scanner.go b/cmd/dep/gopath_scanner.go
index 99dbb1f6ed..48d3189ac9 100644
--- a/cmd/dep/gopath_scanner.go
+++ b/cmd/dep/gopath_scanner.go
@@ -134,7 +134,7 @@ func (g *gopathScanner) overlay(rootM *dep.Manifest, rootL *dep.Lock) {
 }
 
 func trimPathPrefix(p1, p2 string) string {
-	if fs.HasFilepathPrefix(p1, p2) {
+	if isPrefix, _ := fs.HasFilepathPrefix(p1, p2); isPrefix {
 		return p1[len(p2):]
 	}
 	return p1
diff --git a/context.go b/context.go
index 4426be4c46..2227be8a0a 100644
--- a/context.go
+++ b/context.go
@@ -210,7 +210,11 @@ func (c *Ctx) DetectProjectGOPATH(p *Project) (string, error) {
 // detectGOPATH detects the GOPATH for a given path from ctx.GOPATHs.
 func (c *Ctx) detectGOPATH(path string) (string, error) {
 	for _, gp := range c.GOPATHs {
-		if fs.HasFilepathPrefix(path, gp) {
+		isPrefix, err := fs.HasFilepathPrefix(path, gp)
+		if err != nil {
+			return "", errors.Wrap(err, "failed to detect GOPATH")
+		}
+		if isPrefix {
 			return gp, nil
 		}
 	}
@@ -221,7 +225,11 @@ func (c *Ctx) detectGOPATH(path string) (string, error) {
 // `$GOPATH/src/` prefix.  Returns an error for paths equal to, or without this prefix.
 func (c *Ctx) ImportForAbs(path string) (string, error) {
 	srcprefix := filepath.Join(c.GOPATH, "src") + string(filepath.Separator)
-	if fs.HasFilepathPrefix(path, srcprefix) {
+	isPrefix, err := fs.HasFilepathPrefix(path, srcprefix)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to find import path")
+	}
+	if isPrefix {
 		if len(path) <= len(srcprefix) {
 			return "", errors.New("dep does not currently support using GOPATH/src as the project root")
 		}
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
index 9b0525e6ad..928cc0b4f5 100644
--- a/internal/fs/fs.go
+++ b/internal/fs/fs.go
@@ -30,7 +30,7 @@ import (
 // same file. In that situation HasFilepathPrefix("/Foo/Bar", "/foo")
 // will return true. The implementation is *not* OS-specific, so a FAT32
 // filesystem mounted on Linux will be handled correctly.
-func HasFilepathPrefix(path, prefix string) bool {
+func HasFilepathPrefix(path, prefix string) (bool, error) {
 	// this function is more convoluted then ideal due to need for special
 	// handling of volume name/drive letter on Windows. vnPath and vnPrefix
 	// are first compared, and then used to initialize initial values of p and
@@ -42,7 +42,7 @@ func HasFilepathPrefix(path, prefix string) bool {
 	vnPath := strings.ToLower(filepath.VolumeName(path))
 	vnPrefix := strings.ToLower(filepath.VolumeName(prefix))
 	if vnPath != vnPrefix {
-		return false
+		return false, nil
 	}
 
 	// Because filepath.Join("c:","dir") returns "c:dir", we have to manually
@@ -54,7 +54,7 @@ func HasFilepathPrefix(path, prefix string) bool {
 	var dn string
 
 	if isDir, err := IsDir(path); err != nil {
-		return false
+		return false, errors.Wrap(err, "failed to check filepath prefix")
 	} else if isDir {
 		dn = path
 	} else {
@@ -69,7 +69,7 @@ func HasFilepathPrefix(path, prefix string) bool {
 	prefixes := strings.Split(prefix, string(os.PathSeparator))[1:]
 
 	if len(prefixes) > len(dirs) {
-		return false
+		return false, nil
 	}
 
 	// d,p are initialized with "/" on *nix and volume name on Windows
@@ -84,7 +84,7 @@ func HasFilepathPrefix(path, prefix string) bool {
 		// problematic filesystem is not the last one.
 		caseSensitive, err := isCaseSensitiveFilesystem(filepath.Join(d, dirs[i]))
 		if err != nil {
-			return false
+			return false, errors.Wrap(err, "failed to check filepath prefix")
 		}
 		if caseSensitive {
 			d = filepath.Join(d, dirs[i])
@@ -95,11 +95,11 @@ func HasFilepathPrefix(path, prefix string) bool {
 		}
 
 		if p != d {
-			return false
+			return false, nil
 		}
 	}
 
-	return true
+	return true, nil
 }
 
 // EquivalentPaths compares the paths passed to check if they are equivalent.
@@ -126,7 +126,11 @@ func EquivalentPaths(p1, p2 string) (bool, error) {
 		p2, p2Filename = filepath.Split(p2)
 	}
 
-	if !HasFilepathPrefix(p1, p2) || !HasFilepathPrefix(p2, p1) {
+	if isPrefix1, err := HasFilepathPrefix(p1, p2); err != nil {
+		return false, errors.Wrap(err, "failed to check for path equivalence")
+	} else if isPrefix2, err := HasFilepathPrefix(p2, p1); err != nil {
+		return false, errors.Wrap(err, "failed to check for path equivalence")
+	} else if !isPrefix1 || !isPrefix2 {
 		return false, nil
 	}
 
@@ -218,6 +222,11 @@ func isCaseSensitiveFilesystem(dir string) (bool, error) {
 
 	aInfo, err := os.Stat(alt)
 	if err != nil {
+		// If the file doesn't exists, assume we are on a case-sensitive filesystem.
+		if os.IsNotExist(err) {
+			return true, nil
+		}
+
 		return false, errors.Wrap(err, "could not determine the case-sensitivity of the filesystem")
 	}
 
diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go
index 3c4c38596d..920d925bb1 100644
--- a/internal/fs/fs_test.go
+++ b/internal/fs/fs_test.go
@@ -75,7 +75,11 @@ func TestHasFilepathPrefix(t *testing.T) {
 			t.Fatal(err)
 		}
 
-		if got := HasFilepathPrefix(c.path, c.prefix); c.want != got {
+		got, err := HasFilepathPrefix(c.path, c.prefix)
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if c.want != got {
 			t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.path, c.prefix, c.want, got)
 		}
 	}
@@ -122,13 +126,18 @@ func TestHasFilepathPrefix_Files(t *testing.T) {
 		path   string
 		prefix string
 		want   bool
+		err    bool
 	}{
-		{existingFile, filepath.Join(dir2), true},
-		{nonExistingFile, filepath.Join(dir2), false},
+		{existingFile, filepath.Join(dir2), true, false},
+		{nonExistingFile, filepath.Join(dir2), false, true},
 	}
 
 	for _, c := range cases {
-		if got := HasFilepathPrefix(c.path, c.prefix); c.want != got {
+		got, err := HasFilepathPrefix(c.path, c.prefix)
+		if err != nil && !c.err {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		if c.want != got {
 			t.Fatalf("dir: %q, prefix: %q, expected: %v, got: %v", c.path, c.prefix, c.want, got)
 		}
 	}