-
-
Notifications
You must be signed in to change notification settings - Fork 45
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
distinguish between "unignored" and "not ignored" (so results can be combined) #31
Comments
No,
|
Besides, the Suppose that: /path/to/A/
|-- .gitignore # which contains foo.txt
|-- foo.txt Then: ig.ignores('/A/foo.txt') // WRONG !
ig.ignores('/path/to/a/foo.txt') // WRONG!
ig.ignores('./foo.txt') // WRONG !
ig.ignores('foo.txt') // Right |
I'm sorry I didn't communicate this clearly. I know that
Using multiple instances isn't sufficient. You also need to keep track at each level in the hierarchy whether a file has been ignored by a previous instance. That is why I proposed the feature I did, because otherwise how would you know whether a file that is currently ignored should be un-ignored? Anyway I did find a workaround. To check if a file that is currently ignored should be un-ignored, you can amend the ruleset by adding an ignore For the record here's my code. async function isIgnored (workdir, pathname) {
let pairs = [
{
gitignore: path.join(workdir, '.gitignore'),
pathname
}
]
let pieces = pathname.split('/')
for (let i = 1; i < pieces.length; i++) {
let dir = pieces.slice(0, i).join('/')
let file = pieces.slice(i).join('/')
pairs.push({
gitignore: path.join(workdir, dir, '.gitignore'),
pathname: file
})
}
let ignoredStatus = false
for (let p of pairs) {
let file
try {
file = await read(p.gitignore, 'utf8')
} catch (err) {
if (err.code === 'NOENT') continue
}
let ign = ignore().add(file)
let unign = ignore().add(`**\n${file}`)
// If the parent directory is excluded, we are done.
// "It is not possible to re-include a file if a parent directory of that file is excluded. Git doesn’t list excluded directories for performance reasons, so any patterns on contained files have no effect, no matter where they are defined."
// source: https://git-scm.com/docs/gitignore
let parentdir = path.dirname(p.pathname)
if (ign.ignores(parentdir)) return true
// If the file is currently ignored, test for UNignoring.
if (ignoredStatus) {
ignoredStatus = unign.ignores(p.pathname)
} else {
ignoredStatus = ign.ignores(p.pathname)
}
}
return ignoredStatus
} The hack/workaround I mentioned are the lines: let ign = ignore().add(file)
let unign = ignore().add(`**\n${file}`)
...
if (ignoredStatus) {
ignoredStatus = unign.ignores(p.pathname)
} else {
ignoredStatus = ign.ignores(p.pathname)
} |
There's one bug: -if (ign.ignores(parentdir)) return true
+if (parentdir !== '.' && ign.ignores(parentdir)) return true |
Another implementation of hierarchical paths matcher: // @flow
import ignore from 'ignore'
import * as path from 'path'
import * as R from 'ramda'
type Options = {
// Be case-insensitive? Default is true.
ignorecase?: boolean,
}
const joinPaths = R.apply(path.join)
function splitPath (filename: string) {
return filename.split(path.sep).filter(s => s !== '')
}
function parsePatterns (patterns: string, opts: Options = {}) {
const ign = ignore(opts).add(patterns)
const unign = ignore(opts).add(`**\n${patterns}`)
return (filename: string, defaultState: boolean = false) =>
defaultState ? unign.ignores(filename) : ign.ignores(filename)
}
export default (patternsReader: ((dirname: string) => ?string), opts: Options = {}) => {
const patternsMatcherFromDir = R.memoize((dirname: string) => {
const patts = patternsReader(dirname)
if (patts) { return parsePatterns(patts, opts) }
})
// (filename: string) => boolean
return R.pipe(
splitPath,
// ex.: ["a", "b", "c"] -> [[".", "a"/b/c"], ["a", "b/c"], ["a/b", "c"]]
parts => parts.map((_, idx) => {
return R.splitAt(idx, parts).map(joinPaths)
}),
R.reduce((state, [dirname, filename]) => {
const matches = patternsMatcherFromDir(dirname)
// If there's no ignore file in dirname, go up.
if (!matches) {
return state
}
// If the parent directory is excluded, we are done.
// It is not possible to re-include a file if a parent directory of that
// file is excluded. - https://git-scm.com/docs/gitignore
const parentDir = path.dirname(filename)
if (parentDir !== '.' && matches(parentDir, false)) {
return R.reduced(true)
}
return matches(filename, state)
}, false),
)
} |
Do NOT pass paths like See also previous comments of this issue. I've updated the readme about this. |
I'm working on a project I'll close this issue after |
I don't, please read the code properly.
Great! |
Awesome! I'm looking forward to using |
@wmhilton coming soon 😄 |
I need to implement hierarchical .gitignore files for my application, and I've run into an issue trying to combine the results of multiple rulesets. My approach has been to walk down the pathname and apply .gitignore files successively.¹ However, with the current API I cannot tell whether a file is merely "not ignored" or is actually "unignored".
Here's a specific scenario:
Given the result values of both .gitignore files, there's not enough information to determine the combined result. "/A/foo.txt" should be ignored, but "/B/foo.txt" should NOT be ignored, because it was unignored.
I think the cleanest way to solve this would be to allow specifying an starting value to .ignores. Like:
But I would also be OK with a new function that returned a ternary value, like true/null/false or "ignored"/"nochange"/"unignored".
¹
and yes I take into account the "It is not possible to re-include a file if a parent directory of that file is excluded." by testing the rules on the parent directory first and breaking out of the loop if they match.The text was updated successfully, but these errors were encountered: