Skip to content

Fix issue with directory glob ignore in subdirectories#4239

Merged
ethomson merged 1 commit into
libgit2:masterfrom
roblg:toplevel-dir-ignore-fix
Jun 4, 2017
Merged

Fix issue with directory glob ignore in subdirectories#4239
ethomson merged 1 commit into
libgit2:masterfrom
roblg:toplevel-dir-ignore-fix

Conversation

@roblg
Copy link
Copy Markdown
Contributor

@roblg roblg commented May 14, 2017

Observed issue: Git understands /*/ in a .gitignore file to mean "ignore all root-level directories, but include files". Published libgit2 has the same behavior except when the .gitignore file is in a subdirectory of the repository. I reproduced this behavior with tests master and 0.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 .gitignore files in subdirectories, and only for directory matches.

My proposed changed seems safe to me, because relpath is derived from path->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!

Copy link
Copy Markdown
Member

@pks-t pks-t left a comment

Choose a reason for hiding this comment

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

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! :)

Comment thread tests/status/ignore.c
"empty_standard_repo/dist/main.o",
NULL
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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? 😌

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Heh, yeah. My fault for didn't taking a proper look

Comment thread tests/status/ignore.c
cl_git_mkfile(
"empty_standard_repo/.gitignore",
"/*/\n"
"!/src\n");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread src/attr_file.c
/* for attribute checks or root ignore checks, fail match */
if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
path->basename == path->path)
path->basename == relpath)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Okay. I'll take a jab at this, hopefully no later than tomorrow.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@roblg roblg force-pushed the toplevel-dir-ignore-fix branch from 3b5e4ff to c3b8e8b Compare May 17, 2017 15:31
@pks-t
Copy link
Copy Markdown
Member

pks-t commented May 19, 2017

Looks good to me, thanks again! @ethomson?

@pks-t pks-t mentioned this pull request May 19, 2017
8 tasks
@ethomson
Copy link
Copy Markdown
Member

ethomson commented Jun 4, 2017

Thanks for the fix and thanks especially for the wonderful unit tests! 😀

@ethomson ethomson merged commit 82e929a into libgit2:master Jun 4, 2017
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.

3 participants