Skip to content
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

Add basic ignore functionality #71

Merged
merged 28 commits into from
Jul 29, 2018
Merged

Conversation

sirnewton01
Copy link
Collaborator

Adds a new check-ignore command. The status command uses the check-ignore backing logic to filter out any untracked changes. The official git t0008-ignores.sh suite went from completely failing to 297/392 passing.

git/status.go Outdated
@@ -346,6 +346,11 @@ func StatusLong(c *Client, files []File, untracked StatusUntrackedMode, linepref
if err != nil {
return "", err
}
// TODO instead of filtering afterwards, perhaps the untracked should be filtered at the source
matches, _ := CheckIgnore(c, CheckIgnoreOptions{}, []File{fname})
Copy link
Owner

@driusan driusan Jul 23, 2018

Choose a reason for hiding this comment

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

Why don't we put this check in LsFiles and include ExcludeStandard in the opts? That would go a lot farther than adding a filter in every porcelain command

@sirnewton01
Copy link
Collaborator Author

The latest commits add more capabilities with the ignore patterns. Next step is to integrate the ignores into ls-files so that other plumbing commands get the benefit, such as status and add.

@sirnewton01
Copy link
Collaborator Author

Thanks @driusan I see that you anticipated that the LsFiles() function would be the place where ignore capabilities are centralized. Hooking it into status/add was really straight forward. With the latest changes in this PR the t0008-ignores.sh official test has only 87 failures out of 392. I'm going to try to whittle this number down further so that I can add it to the travis CI.

git init &&
echo a >a &&
git add a &&
- git commit -m"commit in submodule"
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's no space between the -m and the commit message, which causes go's flags package to fail on it and also the git manual specifies a space between -m and the message. It seems that the official git command-line just supports this unspecified syntax and the test here happens to use it.

Copy link
Owner

@driusan driusan left a comment

Choose a reason for hiding this comment

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

Some suggestions for cleanup, but I don't mind merging this as-is if you prefer, because it's still a huge step forward. Just let me know when you're done enough that you want it merged.


for idx, path := range paths {
if !opts.NoIndex {
log.Printf("Checking if %s is tracked by git\n", path.String())
Copy link
Owner

Choose a reason for hiding this comment

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

I think the stringer call is implicit since your formatting directive is "%s"

// Be sure to assign the pathname in all cases so that clients can match the outputs to their inputs
patternMatches[idx].PathName = path

// TODO: consider the other places where ignores can come from, such as core.excludesFile and .git/info/exclude
Copy link
Owner

Choose a reason for hiding this comment

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

I think the way to do this is to invert your logic: check-ignore should call ls-files, ls-files shouldn't call check-ignore. ls-files has the exclude-per-directory and exclude-from options, while check-ignore doesn't have any way to customize

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was thinking about this also. The trouble in my thinking comes with the check-ignore --no-index option. I'm not sure how to push that option down into ls-files.

Copy link
Owner

Choose a reason for hiding this comment

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

I'm not an expert on the exclude semantics, but from how I interpret the man page it just affects whether it calls ls-files -o or ls-files -c -o

return "", 0, nil
}

func matchesGlob(path string, isDir bool, pattern string) bool {
Copy link
Owner

Choose a reason for hiding this comment

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

Why not replace the File.MatchGlob I added with this instead of adding a new way to do it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For now, I was going to keep this local because it doesn't quite fit the File.MatchGlob pattern after all. For example, this ones needs to know if the path points to a directory or not for some of the matching logic, but the path may be relative to arbitrary positions within the work directory.

gitignore := filepath.Join(dir, ".gitignore")
log.Printf("Checking .gitignore in %s\n", gitignore)
ignorePath, err := filepath.Rel(dir, abs)
pattern, lineNumber, err := findPatternInIgnoreFile(c, gitignore, ignorePath, isDir)
Copy link
Owner

Choose a reason for hiding this comment

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

What about matches that come from .gitignores higher up in the tree? I think the logic for directories is easier to reason about recursively than iteratively. For instance, we could parse the ignore file and return a slice of patterns, then pass that slice to children to deal with parent ignores. (Which also has the advantages of only parsing the file once). If that was done and the ls-files inversion, it would be easy to add exclude-per-directory and exclude-from

Copy link
Collaborator Author

@sirnewton01 sirnewton01 Jul 26, 2018

Choose a reason for hiding this comment

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

Yes, I could think about this a bit more. Some of the complexity comes from the fact that in order to match a pattern in a gitignore file you need to make the path of the file to be ignore/not ignored relative to that gitignore file in order to check the matches. That makes it a bit more difficult to use patterns from a git ignore file out of context, but perhaps the gitignore parse result could be made into a type that has helper methods on it to prevent mistakes in usage.

Copy link
Owner

@driusan driusan left a comment

Choose a reason for hiding this comment

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

Drive-by review.

(It's really impressive how close this is getting to passing all the tests considering how many tests there are. )

}
}

func TestIgnorePatternMatches(t *testing.T) {
Copy link
Owner

Choose a reason for hiding this comment

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

This test seems to want to be a table driven test

defer file.Close()

ignorePatterns := []IgnorePattern{}
reader := bufio.NewReader(file)
Copy link
Owner

Choose a reason for hiding this comment

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

I haven't looked into this loop in detail, but from a cursory glance it seems that this might be easier using a bufio.Scanner to scan lines instead of a bufio.Reader

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wish that were the case, but unfortunately buffo.Scanner will miss the last line of a stream if there is no newline character at the end. :( This is why there is the pain of having to use the reader directly.

return nil, err
}

// Let's check that this path is in the git work dir first
Copy link
Owner

Choose a reason for hiding this comment

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

This check seems useful enough that it might make sense to make it a method on File rather than inline in the loop.

git/file.go Outdated
@@ -72,6 +72,45 @@ func (f File) IsDir() bool {

}

// Returns true if the file is inside a submodule and the submodule file.
func (f File) IsInSubmodule(c *Client) (bool, File, error) {
Copy link
Owner

Choose a reason for hiding this comment

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

Can you add a FIXME saying that when we implement submodules this should be inverted? (Submodules contain Files, not the other way around, but we don't currently have a submodule type..)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

+1


// Invoke the ignore routines directly, rather than relying on
// LsFiles because we need to be able to do things such as
// return non-matches and match details that aren't supported there.
Copy link
Owner

Choose a reason for hiding this comment

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

I don't think I understand this comment. Isn't determining whether nonmatches or matches are returned what the -i option (which admittedly isn't currently implemented, but that's effectively what this PR is doing) toggles? What details that aren't supported need to be matched?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right the "-i" option is not yet enabled and the LsFiles won't return back the pattern that matched or wasn't matched so that checkignore can output that with the -v flag.

@sirnewton01
Copy link
Collaborator Author

@driusan I've reorganized things here quite a bit hoping that it is a much cleaner implementation with better separation of concerns. I've gotten the number of official git tests down to only 28 out of 395 for the ignore tests. The remainder are mostly related to the fact that I have not yet implemented the negation '!' operator and a couple of edge cases with escaping of whitespace vs. the slash character itself.

@sirnewton01
Copy link
Collaborator Author

I have added all of the passing official git ignore tests to travis to catch regressions. The majority of the failing tests are because of the negation operator, which I think should be tackled as a separate issue.

@driusan driusan merged commit dafef00 into driusan:master Jul 29, 2018
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.

2 participants