Skip to content

Support compiling & matching against a tree of gitignore patterns #10

@jwodder

Description

@jwodder

Add the following function (name not final) that takes a mapping from basedirs to gitignore patterns and produces an object that takes the gitignore patterns for multiple directories into account at once as appropriate:

compile_tree(
    patterns: Mapping[
        AnyStr | os.PathLike[AnyStr],
        Iterable[AnyStr] | Gitignore[AnyStr]
    ],
    ignorecase: bool = False,
) -> GitignoreTree[AnyStr]
  • Basedirs must be normalized and relative, possibly with trailing slashes

  • To refer to the root directory, use os.curdir (or an empty string?)

  • Raises an exception if the same basedir is given more than once

  • If there's an entry for a given basedir, and the patterns for a parent directory match that basedir, should the basedir's patterns be honored (as though the basedir were tracked) or not?

  • Possible alternative API: The user constructs an empty GitignoreTree (with optional ignorecase option) and then adds basedir+patterns pairs one at a time (via __setitem__ or some other method?)

    • If the same basedir is given more than once (even in different forms), the patterns for the old entry are replaced (unless you use some extend_dir_patterns(basedir, patterns) method)
  • Regardless of which API is used, GitignoreTrees should be mutable, i.e., it should be possible to add more Gitignores to them after construction. This will be useful when progressively walking a directory tree, e.g., with iterpath.


Notes on how Git combines multiple .gitignore files:

  • If /.gitignore contains bar or foo/bar, and foo/.gitignore contains !bar, foo/bar will not be ignored.
  • If /.gitignore contains !bar, and foo/.gitignore contains bar,
    foo/bar will be ignored.
  • If /.gitignore contains !foo, and foo/.gitignore contains bar, foo/bar will be ignored.
  • If /.gitignore contains foo and foo/.gitignore contains !bar, foo/bar will be ignored.
    • If foo/.gitignore is forcibly added to Git, foo/bar will still be ignored.
  • If /.gitignore contains both bar & foo/.gitignore and foo/.gitignore contains !bar, foo/bar will not be ignored, even if foo/.gitignore is forcibly added to Git.
  • If foo/.gitignore contains foo, then foo/bar will not be ignored.
  • If foo/bar/.gitignore contains foo, then foo/bar/baz will not be ignored.
  • If .gitignore contains /bar, then foo/bar will not be ignored.

Conclusions:

  • Matching a path in a tree is done by concatenating all .gitignores from the root of the tree up through the directory containing the path (ordered from the root upwards) and using that as the pattern set to test against, except that a pattern from foo/.gitignore will be tested against paths relative to foo/.

    • Note that the rule about testing parent directories first still applies, and each parent directory is only tested against .gitignores up through the parent's parent directory.
  • Ignoring a .gitignore but not its containing directory has no effect on whether anything in the directory is ignored

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request thereforhigh priorityWork on these firstunder considerationDev has not yet decided whether or how to implement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions