Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

[WIP]: internal/fs: add EquivalentPaths function #940

Merged
merged 6 commits into from
Sep 2, 2017

Conversation

ibrasho
Copy link
Collaborator

@ibrasho ibrasho commented Aug 3, 2017

What does this do / why do we need it?

Add a new method to internal/fs to check for path equivalence.

What should your reviewer look out for in this PR?

Does this work as expected?

Which issue(s) does this PR fix?

fixes #772
fixes #805

@ibrasho ibrasho requested a review from sdboyer August 3, 2017 18:50
@ibrasho ibrasho changed the title internal/fs: add EqualPaths() WIP: internal/fs: add EqualPaths() Aug 3, 2017
@ibrasho ibrasho changed the title WIP: internal/fs: add EqualPaths() [WIP]: internal/fs: add EqualPaths() Aug 3, 2017
@ibrasho ibrasho force-pushed the add-EqualPaths-to-fs branch 3 times, most recently from 22f4852 to fcfaa20 Compare August 3, 2017 20:51
@ibrasho ibrasho force-pushed the add-EqualPaths-to-fs branch from fcfaa20 to 0ec076d Compare August 4, 2017 07:24
@ibrasho
Copy link
Collaborator Author

ibrasho commented Aug 4, 2017

@sdboyer I believe I found a bug in fs.HasFilepathPrefix...

I wasted a few hours debugging why the tests for EqualPaths fails on Mac on Travis and it seems that HasFilepathPrefix has a bug on case-insensitive filesystems on *nix.

This is where it happens:

	// d,p are initialized with "" on *nix and volume name on Windows
	d := vnPath
	p := vnPrefix

The next for loop always assume that the filesystem is case-sensitive since isCaseSensitiveFilesystem returns false when the passed dir doesn't exist.

On *nix, d and p are defaulting to "" instead of "/" and joining them with additional directory names results in a relative path. This happens because filepath.Join("", "dirName") returns "dirName".

	for i := range prefixes {
		// need to test each component of the path for
		// case-sensitiveness because on Unix we could have
		// 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])) {
			d = filepath.Join(d, dirs[i])
			p = filepath.Join(p, prefixes[i])
		} else {
			d = filepath.Join(d, strings.ToLower(dirs[i]))
			p = filepath.Join(p, strings.ToLower(prefixes[i]))
		}

     // ...
}

isCaseSensitiveFilesystem has not tests, and it's quite hard to test honestly... And the tests for HasFilepathPrefix don't cover this case.

I've pushed a commit with my proposed fix, which is to update both isCaseSensitiveFilesystem and HasFilepathPrefix.

context_test.go Outdated
@@ -341,49 +334,49 @@ func TestDetectProjectGOPATH(t *testing.T) {
{
name: "project-with-no-AbsRoot",
root: "",
resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src", "real", "path"),
resolvedRoot: filepath.Join(ctx.GOPATHs[0], "src/real/path"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why these changes? AFAIK this isn't safe for windows

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sdboyer you're right, it isn't.

Copy link
Collaborator Author

@ibrasho ibrasho Aug 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm under the impression that filepath.Join will call filepath.Clean which calls filepath.FromSlash on the passed path. Am I missing something else?

Anyhow, this is a purposeless change and I can revert it. 😁

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, ty; yes, it does call filepath.Clean(), but that func doesn't do separator transformations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just trying to clarify, shouldn't this line do that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh. guess i'm wrong on that!

@@ -100,6 +102,53 @@ 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EquivalentPaths would probably be a better name here than EqualPaths, as it emphasizes that we are testing for logically equivalent paths rather than byte-identical ones (which is the whole point!)

@ibrasho ibrasho force-pushed the add-EqualPaths-to-fs branch from 4c1cbbb to b60c5a9 Compare August 9, 2017 04:14
@ibrasho ibrasho changed the title [WIP]: internal/fs: add EqualPaths() [WIP]: internal/fs: add EquivalentPaths function Aug 9, 2017
Copy link
Member

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just two little comment nits, then i think this is 👍 (feel free to merge once you fix those)

@@ -98,6 +102,53 @@ func HasFilepathPrefix(path, prefix string) bool {
return true
}

// EquivalentPaths compares the paths passed to check if the are equivalent.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/if the are/if they are/

@@ -98,6 +102,53 @@ 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/underlaying/underlying/

@sdboyer
Copy link
Member

sdboyer commented Aug 17, 2017

well that's some catastrophic failure on linux :(

@ibrasho
Copy link
Collaborator Author

ibrasho commented Aug 21, 2017

I figured out what's happening finally. 😓
A non-existing file error wasn't being handled properly.

@ibrasho ibrasho force-pushed the add-EqualPaths-to-fs branch from 7d6b41a to a64d6b7 Compare August 21, 2017 19:11
Signed-off-by: Ibrahim AshShohail <[email protected]>
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 <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
Signed-off-by: Ibrahim AshShohail <[email protected]>
@ibrasho ibrasho force-pushed the add-EqualPaths-to-fs branch from a64d6b7 to 67f83fc Compare August 22, 2017 17:17
@ibrasho
Copy link
Collaborator Author

ibrasho commented Sep 2, 2017

@sdboyer Should we merge this? 😀

Copy link
Member

@sdboyer sdboyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i think this is probably good to go. (sorry, busy week). just one comment - up to you if you want to address

{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},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think that after looking through, you turned out to be totally right on this - it'll convert to windows line endings automatically. i'm fine with changing it back, though it's not really necessary either

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not worth changing, to be honest. 😛

@ibrasho ibrasho merged commit 3b01418 into golang:master Sep 2, 2017
@sudo-suhas
Copy link
Contributor

@ibrasho Could you please tell me why you are not using os.SameFile? I ran the tests(TestEquivalentPaths) successfully with this modification on windows and linux:

func EquivalentPaths(p1, p2 string) (bool, error) {
    p1 = filepath.Clean(p1)
    p2 = filepath.Clean(p2)

    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")
    }

    return os.SameFile(fi1, fi2), nil
}

@sdboyer
Copy link
Member

sdboyer commented Sep 5, 2017

Could you please tell me why you are not using os.SameFile?

i'd swear we have a good reason for this, because we certainly knew that function existed beforehand. i can't remember that reason now, though 😢

@sudo-suhas
Copy link
Contributor

Hmm.. What do you suggest we do? I can make a PR with the changes above. We can see if everything works as expected.

@sdboyer
Copy link
Member

sdboyer commented Sep 5, 2017

We can see if everything works as expected.

no, we need to trace back over the lines of thinking that got us to here. hasty PRs in areas related to casing just lead to more headaches; "see if everything works as expected" is not something we can do, as there is no well-formed definition of "everything," let alone one codified into tests.

@sudo-suhas
Copy link
Contributor

Okay. At the very least, we should identify and add a test which fails for os.Samefile but not the current implementation.

@ibrasho
Copy link
Collaborator Author

ibrasho commented Sep 5, 2017

@sudo-suhas As Sam has said, we are trying to handle something that os.SameFile doesn't.

If I remember correctly, it has to do with nested filesystems that handle case-sensitivity differently. But I can't remember the exact case. 😕

zknill pushed a commit to zknill/dep that referenced this pull request Oct 6, 2017
* internal/fs: add EquivalentPaths

* 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 <[email protected]>
@ibrasho ibrasho deleted the add-EqualPaths-to-fs branch November 29, 2017 06:18
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
5 participants