Fix issue with directory glob ignore in subdirectories#4239
Conversation
pks-t
left a comment
There was a problem hiding this comment.
Thanks for this nice PR, especially for all these nice tests :) I just took a quick look at your changes and made some requests for changes, but I'm not yet able to verify if it actually makes sense. I'll hopefully come around to that later on.
Thanks! :)
| "empty_standard_repo/dist/main.o", | ||
| NULL | ||
| }; | ||
|
|
There was a problem hiding this comment.
This applies to all three tests: you're not working in a repository here, as you forgot to call
g_repo = cl_git_sandbox_init("empty_standard_repo");
There was a problem hiding this comment.
This comment gave me a bit of a fright, but it looks like make_test_data is init-ing the repository for me, so I think I'm OK? 😌
There was a problem hiding this comment.
Heh, yeah. My fault for didn't taking a proper look
| cl_git_mkfile( | ||
| "empty_standard_repo/.gitignore", | ||
| "/*/\n" | ||
| "!/src\n"); |
There was a problem hiding this comment.
Minor style-nit: all the other calls have the first argument on the same line. There's also a newline between make_test_data and cl_git_mkfile.
There was a problem hiding this comment.
Good call. I did a survey of the other test functions, and most of the have the first argument on the next line, and tests that use make_test_data tend to not have the space between it and cl_git_mkfile, so I'll push a new commit standardizing on that form.
| /* for attribute checks or root ignore checks, fail match */ | ||
| if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || | ||
| path->basename == path->path) | ||
| path->basename == relpath) |
There was a problem hiding this comment.
This is not really a fault of yours, as you simply mirror the previous style, but: do we really want to compare the pointers here? I bet this should be a strcmp instead. And if not, we should at least add a comment here to explain what's going on.
There was a problem hiding this comment.
I honestly don't recall if I was intending for this to be pointer comparison, however I added some debug logging this morning and it does work, because the pointers in question are all derived from offsets into path->path (basename gets derived in git_attr_path__init; relpath gets derived earlier in git_attr_fnmatch__match).
I would definitely like someone to triple-check my thought process on this though. I don't know if this is actually helpful to others, but it helps my thinking to write it down so:
Based on the immediate closing if-statement: (match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir), my read of the original early return is:
a) IF we're trying to match a directory
b) AND the path we're looking at is not a directory
c) AND the basename and path are the same (i.e., they represent an entry at the root of the tree),
THEN we can't possibly match because we must be looking at a file (because of b) ) in the root of the git tree.
The change to use relpath is an attempt to say the same thing, but to extend c) to include any file directly under the containing_dir of the rule we're comparing with.
Or at least that's what I've been telling myself. :)
I'll throw some more test cases at it. I'm still not 100% convinced that replacing path->path with relpath is the perfect solution. (Does it work if the .gitignore is at the root, and it's ignoring directories in a subdirectory? I think it doesn't...) I think it does preserve existing behavior w.r.t. files at the root, and extends it to files at the root of directories w/ .gitignores.
There was a problem hiding this comment.
Okay. I'll take a jab at this, hopefully no later than tomorrow.
There was a problem hiding this comment.
So just to clear up my thinking here:
a) IF we're trying to match a directory
b) AND the path we're looking at is not a directory
c) AND the basename and path are not the same
then we have a path containing at least one directory, e.g. "foo/bar", where we have to do additional checks. If we're inside of "foo" and try to match a directory "bar" but find a file "bar", we should not match, but currently we do not realize because we use the root-based path->path ("foo/bar") instead of relpath ("bar") to match against the basename.
So I think this change is correct. The comment is becoming a bit stale though, as strictly speaking we do not do this check for the root directory only.
There was a problem hiding this comment.
That line of thinking is what I was thinking. :)
I fixed and force-pushed the changes requested w.r.t. test code style, and an attempt at updating the comment here.
3b5e4ff to
c3b8e8b
Compare
|
Looks good to me, thanks again! @ethomson? |
|
Thanks for the fix and thanks especially for the wonderful unit tests! 😀 |
Observed issue: Git understands
/*/in a.gitignorefile to mean "ignore all root-level directories, but include files". Published libgit2 has the same behavior except when the.gitignorefile is in a subdirectory of the repository. I reproduced this behavior with testsmasterand0.24.3.I added four new tests; three of them already work in master. The one that fails w/o my change is
test_status_ignore__subdir_ignore_all_toplevel_dirs_include_files. The other tests in status::ignore illustrate that the issue was only occurring for.gitignorefiles in subdirectories, and only for directory matches.My proposed changed seems safe to me, because
relpathis derived frompath->path, which is in the original code, and it only affects the case where the match is looking for a directory and the path being looked at is a file. I also rolled a dev version of atom and linked in a version of this patch based on 0.24.3 and verified that the expected behavior (my root-level files show up!) is observed.Old and new tests pass, so how could it possibly be broken, amirite? 😀 But in all seriousness, I'm not at all familiar with this code, so if there's something else I can do, let me know.
Thanks!