Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Ignore Case via Repository.PathComparer #344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from

Conversation

dahlbyk
Copy link
Member

@dahlbyk dahlbyk commented Feb 20, 2013

Round-about path to initial ignore case support.

  • Update tests to exercise lookup and such with and without ignoring case.
  • Decide between named comparers, IPathComparable (implemented in last four commits), or just using StringPropertyComparer<> with lambdas.
  • Stuff I'm missing...

Also, I included 9bcc2c1 because I don't want to create a dedicated issue unless I'm not alone... on Win8 I'm seeing sporadic Access Denied errors that I haven't seen in quite a while. Just me, or are others seeing it too? Is a retry worth keeping?

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 20, 2013

See also #214, #336

/cc @phkelley

@@ -56,7 +56,7 @@ private List<Conflict> AllConflicts()
continue;
}

if (currentPath != null && !entry.Path.Equals(currentPath, StringComparison.Ordinal))
if (currentPath != null && !repo.PathComparer.Equals(entry.Path, currentPath))
Copy link
Member

Choose a reason for hiding this comment

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

If I have conflicts for both FILE and file, this will overwrite one with the other. I think we need to sort the Conflicts after having coalesced the index entries into a single object.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we would just need to to presort the IndexEntrys by Path with repo.PathComparer.

A better option might be to let LINQ do the grouping:

        private static Conflict BuildConflict(string path, IEnumerable<IndexEntry> entries)
        {
            IndexEntry ancestor = null, ours = null, theirs = null;

            foreach (var entry in entries)
            {
                switch (entry.StageLevel)
                {
                    case StageLevel.Ancestor:
                        ancestor = entry;
                        break;
                    case StageLevel.Ours:
                        ours = entry;
                        break;
                    case StageLevel.Theirs:
                        theirs = entry;
                        break;
                    default:
                        throw new InvalidOperationException(string.Format(
                            CultureInfo.InvariantCulture,
                            "Entry '{0}' bears an unexpected StageLevel '{1}'",
                            entry.Path, entry.StageLevel));
                }
            }

            return new Conflict(ancestor, ours, theirs);
        }

        public virtual IEnumerator<Conflict> GetEnumerator()
        {
            return repo.Index
                       .Where(e => e.StageLevel != StageLevel.Staged)
                       .GroupBy(e => e.Path, BuildConflict, repo.PathComparer)
                       .GetEnumerator();
        }

Copy link
Member Author

Choose a reason for hiding this comment

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

The appropriate implementation here depends on how #345 is resolved.

ReferenceEquals(y, null) ? null : valueSelector(y));
}
}
} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

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

😉

Copy link
Member Author

Choose a reason for hiding this comment

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

And I thought I was so careful... Darn R# 😦

@nulltoken
Copy link
Member

Some random findings:

  • git branch, git tag, git remote all return a list of alphasorted names (à la OrdinalComparison). At least in my msysgit env.
  • case insensitivity seem to be well... not evenly distributed
$ git rev-parse HEAD
34807a6b50c39ba799b6c592ccef086bc6b0d06f

$ git rev-parse HEAd
34807a6b50c39ba799b6c592ccef086bc6b0d06f

$ git rev-parse hEAd
34807a6b50c39ba799b6c592ccef086bc6b0d06f

$ git rev-parse MaStEr
MaStEr
fatal: ambiguous argument 'MaStEr': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

$ git rev-parse master
c1743ba7f9547f53d565222f628fbc7138a65973

$ git rev-parse refs/heads/master
c1743ba7f9547f53d565222f628fbc7138a65973

$ git rev-parse ReFs/heads/master
ReFs/heads/master
fatal: ambiguous argument 'ReFs/heads/master': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

or

$ echo t > a.z

$ git add a.Z

$ git status
# On branch PathComparer
# Your branch is ahead of 'dahlbyk/PathComparer' by 1 commit.
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       a.z
nothing added to commit but untracked files present (use "git add" to track)

$ git add a.z
warning: LF will be replaced by CRLF in a.z.
The file will have its original line endings in your working directory.

$ git status
# On branch PathComparer
# Your branch is ahead of 'dahlbyk/PathComparer' by 1 commit.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       new file:   a.z
#

$

I haven't got any issue diverging from git implementation, but we'll have to be backed up by a thick test suite to express, platform by platform, what is the proposed behavior.

Thoughts?

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 20, 2013

git branch, git tag, git remote all return a list of alphasorted names (à la OrdinalComparison). At least in my msysgit env.

Mine too, I was on the fence about changing this.

I haven't got any issue diverging from git implementation, but we'll have to be backed up by a thick test suite to express, platform by platform, what is the proposed behavior.

That's what I'm afraid of. Realistically, I'm using PathComparer here far more than it probably should be. Ideally most ignorecase type stuff will be handled by libgit2 and we just need to be aware of it in a few places (e.g. #345).

More generally, I'm still on the fence about IPathComarable (or IPathAccessor or whatever)...I think it might be better to just provide the generic comparer that accepts a lambda.

@phkelley
Copy link
Member

I don't think I understand this PR. What is the goal here? I do agree that there are some places in libgit2sharp where we need to understand the case-sensitive or case-insensitive nature of the underlying filesystem. For example, I think there is a method on Index (Index.Stage?) where we take in an absolute path and have to check to see whether or not that absolute path is underneath the working directory for the repository. I noticed a while back that that string comparer was Ordinal, all the time, which isn't right on Windows. (But it is on some platforms where Libgit2sharp runs under Mono.) So we need some logic for picking a path comparer in that context. But isn't ignorecase support the purview of libgit2? These are just bindings.

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 21, 2013

What is the goal here?

Case-insensitivity where it's necessary. Identifying where it's necessary is a work in progress. I started by using PathComparer almost everywhere we use StringComparer/StringComparison, but that's clearly not correct.

But isn't ignorecase support the purview of libgit2? These are just bindings.

For the most part, but there are cases where we do things like sort index entries that depend on case-sensitivity. If I do a dir on Windows, it's sorted case-insensitively; for a list of modified files, it may make sense to do the same. Or maybe we just make the call that sorting refs, files, etc. isn't our responsibility and we can throw all of this away.

In the Index.Stage() example, is it reasonable to respect core.ignorecase for that underneath-WD check, or should it depend on an OS check instead?

@phkelley
Copy link
Member

Thanks Keith for the explanation and your patience with me.

I think you can make the argument either way on that Index.Stage() example as to what the best implementation is. Probably it's more correct to try to detect what platform we're running on and determine case-sensitivity that way -- but it's easier to just look at core.ignorecase. I think either one is acceptable.

I'm not sure that we should start guaranteeing a sort order on collections that we get back from libgit2 -- refs, index entries, etc. There is a perf cost to sorting, and ownership of the sort order probably ought to be a guarantee that either originates all the way down the stack and flows up from there, or is applied at the top of the stack by the eventual consumer. But applying a sort in the middle of the stack (which is what Libgit2sharp is) feels weird to me.

@ethomson
Copy link
Member

I tend to agree with @phkelley about the sorting on things like refs and the like. Me personally, I would expect enumerating the Index, or a Tree, to match the sorting that's inbuilt to those objects.

For something like git-tf, I can imagine enumerating Tree entries, and then having to sort them back to the weird tree format...

I presume that as a consumer I would have some control over sorting, but I just thought I'd throw that out there.

@nulltoken
Copy link
Member

I'm not sure that we should start guaranteeing a sort order on collections that we get back from libgit2 -- refs, index entries, etc. There is a perf cost to sorting, and ownership of the sort order probably ought to be a guarantee that either originates all the way down the stack and flows up from there, or is applied at the top of the stack by the eventual consumer. But applying a sort in the middle of the stack (which is what Libgit2sharp is) feels weird to me.

I'm the one to blame here. I think I did add the initial .Sort() and .OrderBy() calls when building the Enumerators.

👍 to drop them.

and ownership of the sort order probably ought to be a guarantee that either originates all the way down the stack and flows up from there

One thing to note though regarding loose references: IIRC @carlosmn explained me once that there's no guarantee that git_path_direach() will return anything sorted.

@carlosmn
Copy link
Member

Right, a directory listing will be in whatever order the OS feels like (at least on unix). The index and trees have inherent order (and the trees have odd sorting so these both match) inside them, but there is no particular sorting of refs that makes more sense, particularly since you usually want to filter them by type anyway.

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 21, 2013

Let's try this again... I've refocused on places where we use case-sensitive comparison for paths within the working directory (Index.Stage(), etc). I didn't touch Conflict until we figure out what's going on with #345.

I also included 07fc2c3ddae3367410763134ec3ff043fe29e2cd to remove sorting - from a consumer standpoint, this is a UX-breaking change.

DirectoryHelper.DeleteDirectory(directory);
for (var i = 0; i < 3; i++)
{
try
Copy link
Member

Choose a reason for hiding this comment

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

Do e still need this with @yorah's 81d1490 being merged?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably not, will kill the next time around.

Copy link
Member Author

Choose a reason for hiding this comment

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

Gone.

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 21, 2013

Updated with fixes for the failing Travis tests around the sorting change.

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 22, 2013

Down to one failing test:

error : LibGit2Sharp.Tests.IndexFixture.CanStageANewFileWithAFullPath(ignorecase: True): LibGit2Sharp.LibGit2SharpException : An error was raised by libgit2. Category = Os (NotFound).
Failed to stat file '/home/travis/build/libgit2/libgit2sharp/LibGit2Sharp.Tests/bin/Release/TestRepos/30978dcb/NEW_UNTRACKED_FILE.TXT': No such file or directory
error : at LibGit2Sharp.Core.Ensure.HandleError (Int32 result) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Core.Ensure.ZeroResult (Int32 result) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Core.Proxy.git_index_add_bypath (LibGit2Sharp.Core.Handles.IndexSafeHandle index, LibGit2Sharp.Core.FilePath path) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Index.AddToIndex (System.String relativePath) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Index.Stage (IEnumerable`1 paths) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Index.Stage (System.String path) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Tests.IndexFixture.AssertStage (Nullable`1 ignorecase, IRepository repo, System.String path) [0x00000] in <filename unknown>:0
 at LibGit2Sharp.Tests.IndexFixture.CanStageANewFileWithAFullPath (Boolean ignorecase) [0x00000] in <filename unknown>:0 

@carlosmn If ignorecase = true on a case-sensitive platform, is it expected that one would not be able to stage a file using the wrong case?

@nulltoken
Copy link
Member

@carlosmn If ignorecase = true on a case-sensitive platform, is it expected that one would not be able to stage a file using the wrong case?

Looks like this goes down to "Is there a way to make fopen() case insensitive on a Linux based platform?". I don't think so.

I can think of a hack to make this work, though. Beware, it's ugly and greedy:

  • Retrieve the content of the parent directory
  • Make a case sensitive comparison for each entry
    • One exact match => Stage() it and exit
  • Make a case insensitive comparison on each entry
    • One match => Stage() it
    • More than one match => throw an AmbiguousBlaBlaBlaException

Ugly and greedy, isn't it? I told you. 😉 There's an additional bonus drawback, this couldn't support glob patterns that are about to be introduced by #343 (through the diff-based approach).

@dahlbyk
Copy link
Member Author

dahlbyk commented Feb 22, 2013

Ugly and greedy, isn't it?

Yes

Ultimately I think our behavior needs to mimic git.git here. Does it even try to support core.ignorecase = true on *nix?

@carlosmn
Copy link
Member

The core.ignorecase option is a way to tell git that the is underlying filesystem case-insensitive; and that's all it's for. It's not a way of asking it to behave as though it were in a case-insensitive filesystem.

EDIT: it does support it on unix if you mount NTFS for example. It's not about the OS but about the filesystem, you can have either on OSX.

@dahlbyk
Copy link
Member Author

dahlbyk commented Mar 3, 2013

What do you think of 4673aa7 to skip ignorecase = true if the filesystem won't support it?

@@ -778,6 +780,21 @@ public virtual IEnumerable<MergeHead> MergeHeads
}
}

private PathCase PathCase
{
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it boils down to a personal preference, but maybe we could remove the private property and directly access pathCase.Value?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or another idea: maybe we could change the access modifier to internal, rename the property to something like PathCaseSupport, and just access the .Comparer and .StartsWith() directly?
That would allow us to get rid of the other property and method below.

Copy link
Member Author

Choose a reason for hiding this comment

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

I lean toward inlining the PathCase property, but I guess there's not much difference between repo.PathComparer and repo.PathCase.Comparer.

@dahlbyk
Copy link
Member Author

dahlbyk commented Mar 13, 2013

So per @yorah's comments, it sounds like the only case-sensitivity fix we actually need is what @phkelley noted in #214. I'll drop the RepositoryStatus and TreeChanges bits, which just leaves the relative path fixes. I probably should have just started there, eh?

@nulltoken
Copy link
Member

@dahlbyk Travis doesn't look happy... Could you please peek at those?

StatusFixture.cs(39,13): error CS0815: An implicitly typed local variable declaration cannot be initialized with `void'
StatusFixture.cs(55,24): error CS0411: The type arguments for method `Xunit.Assert.Equal<T>(T, T, System.Collections.Generic.IEqualityComparer<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly
StatusFixture.cs(71,24): error CS0411: The type arguments for method `Xunit.Assert.Equal<T>(T, T, System.Collections.Generic.IEqualityComparer<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly
StatusFixture.cs(74,24): error CS0411: The type arguments for method `Xunit.Assert.Equal<T>(T, T, System.Collections.Generic.IEqualityComparer<T>)' cannot be inferred from the usage. Try specifying the type arguments explicitly

@dahlbyk
Copy link
Member Author

dahlbyk commented Mar 14, 2013

Should be fixed now...missed a test that I thought I'd rolled back.

@dahlbyk
Copy link
Member Author

dahlbyk commented Mar 16, 2013

Update on top of latest, but...what seems to be an unrelated test is failing. Naturally it works on my machine... Ideas?

LibGit2Sharp.Tests.CommitFixture.CanEnumerateAllCommits: Assert.Equal() Failure
Position: First difference is at position 7
Expected: String[] { "44d5d18", "bb65291", "532740a", "503a16f", "3dfd6fd", "4409de1", "902c60b", "4c062a6", "e90810b", "6dcf9bf", "a4a7dce", "be3563a", "c47800c", "9fd738e", "4a202b3", "41bc8c6", "5001298", "5b5b025", "8496071" }
Actual: String[] { "44d5d18", "bb65291", "532740a", "503a16f", "3dfd6fd", "4409de1", "902c60b", "e90810b", "6dcf9bf", "4a202b3", "41bc8c6", "5001298", "5b5b025", "8496071" }
at LibGit2Sharp.Tests.CommitFixture.AssertEnumerationOfCommitsInRepo (LibGit2Sharp.Repository repo, System.Func`2 filterBuilder, IEnumerable`1 abbrevIds) [0x00000] in <filename unknown>:0
at LibGit2Sharp.Tests.CommitFixture.AssertEnumerationOfCommits (System.Func`2 filterBuilder, IEnumerable`1 abbrevIds) [0x00000] in <filename unknown>:0
at LibGit2Sharp.Tests.CommitFixture.CanEnumerateAllCommits () [0x00000] in <filename unknown>:0 

@nulltoken
Copy link
Member

Update on top of latest, but...what seems to be an unrelated test is failing. Naturally it works on my machine... Ideas?

It looks like the failure comes from 8e8ba01. I've cherry picked it onto the top of vNext and ran it on TeamCity. The Mono build configuration failed as well.

@dahlbyk
Copy link
Member Author

dahlbyk commented Mar 17, 2013

Good call...overlooked the use of unsorted Refs. 💚

@nulltoken
Copy link
Member

❤️ Thanks a lot for this very cool piece of work.

Merged!

@nulltoken nulltoken closed this Mar 17, 2013
@dahlbyk dahlbyk deleted the PathComparer branch March 18, 2013 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants