{"id":16737511,"url":"https://github.com/robotdana/fast_ignore","last_synced_at":"2025-04-09T08:11:19.548Z","repository":{"id":45485765,"uuid":"177962853","full_name":"robotdana/fast_ignore","owner":"robotdana","description":"parse gitignore files in ruby","archived":false,"fork":false,"pushed_at":"2025-02-26T11:23:57.000Z","size":1725,"stargazers_count":17,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-02T06:07:45.465Z","etag":null,"topics":["gitignore","gitignore-files","gitignore-parser","gitignore-patterns","gitignore-rules","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robotdana.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-27T09:38:51.000Z","updated_at":"2024-10-15T20:22:10.000Z","dependencies_parsed_at":"2024-06-20T23:28:59.946Z","dependency_job_id":"5c8a5ab3-fea2-4c5e-9ec9-92e8f3976ff2","html_url":"https://github.com/robotdana/fast_ignore","commit_stats":{"total_commits":183,"total_committers":2,"mean_commits":91.5,"dds":"0.021857923497267784","last_synced_commit":"6cf6251eff6bab85abad575e625b3c3481a18eb2"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robotdana%2Ffast_ignore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robotdana%2Ffast_ignore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robotdana%2Ffast_ignore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robotdana%2Ffast_ignore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robotdana","download_url":"https://codeload.github.com/robotdana/fast_ignore/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247999861,"owners_count":21031046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gitignore","gitignore-files","gitignore-parser","gitignore-patterns","gitignore-rules","ruby"],"created_at":"2024-10-13T00:26:45.404Z","updated_at":"2025-04-09T08:11:19.488Z","avatar_url":"https://github.com/robotdana.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FastIgnore\n\n[![travis](https://travis-ci.com/robotdana/fast_ignore.svg?branch=main)](https://travis-ci.com/robotdana/fast_ignore)\n[![Gem Version](https://badge.fury.io/rb/fast_ignore.svg)](https://rubygems.org/gems/fast_ignore)\n\nThis started as a way to quickly and natively ruby-ly parse gitignore files and find matching files.\nIt's now gained an equivalent includes file functionality, ARGV awareness, and some shebang matching, while still being extremely fast, to be a one-stop file-list for your linter.\n\nFilter a directory tree using a .gitignore file. Recognises all of the [gitignore rules](https://www.git-scm.com/docs/gitignore#_pattern_format)\n\n```ruby\nFastIgnore.new(relative: true).sort == `git ls-files`.split(\"\\n\").sort\n```\n\n## Features\n\n- Fast (faster than using `` `git ls-files`.split(\"\\n\") `` for small repos (because it avoids the overhead of ``` `` ```))\n- Supports ruby 2.5-3.1.x \u0026 jruby\n- supports all [gitignore rule patterns](https://git-scm.com/docs/gitignore#_pattern_format)\n- doesn't require git to be installed\n- supports a gitignore-esque \"include\" patterns. ([`include_rules:`](#include_rules)/[`include_files:`](#include_files))\n- supports an expansion of include patterns, expanding and anchoring paths ([`argv_rules:`](#argv_rules))\n- supports [matching by shebang](#shebang_rules) rather than filename for extensionless files: `#!:`\n- reads .gitignore in all subdirectories\n- reads .git/info/excludes\n- reads the global gitignore file mentioned in your git config\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'fast_ignore'\n```\n\nAnd then execute:\n```sh\n$ bundle\n```\nOr install it yourself as:\n```sh\n$ gem install fast_ignore\n```\n\n## Usage\n\n```ruby\nFastIgnore.new.each { |file| puts \"#{file} is not ignored by the .gitignore file\" }\n```\n\n### `#each`, `#map` etc\n\nThis yields paths that are _not_ ignored by the gitignore, i.e. the paths that would be returned by `git ls-files`.\n\nA FastIgnore instance is an Enumerable and responds to all Enumerable methods:\n\n```ruby\nFastIgnore.new.to_a\nFastIgnore.new.map { |file| file.upcase }\n```\n\nLike other enumerables, `FastIgnore#each` can return an enumerator:\n\n```ruby\nFastIgnore.new.each.with_index { |file, index| puts \"#{file}#{index}\" }\n```\n\n**Warning: Do not change directory (e.g. `Dir.chdir`) in the block.**\n\n### `#allowed?`\n\nTo check if a single path is allowed, use\n```ruby\nFastIgnore.new.allowed?('relative/path')\nFastIgnore.new.allowed?('./relative/path')\nFastIgnore.new.allowed?('/absolute/path')\nFastIgnore.new.allowed?('~/home/path')\n```\n\nRelative paths will be considered relative to the [`root:`](#root) directory, not the current directory.\n\nThis is aliased as `===` so you can use a FastIgnore instance in case statements.\n```ruby\n@path_matcher ||= FastIgnore.new\n\ncase my_path\nwhen @path_matcher\n  puts(my_path)\nend\n```\n\nIt's recommended to save the FastIgnore instance to a variable to avoid having to read and parse the gitignore file and gitconfig files repeatedly.\n\n#### directory: true/false/nil\n\nIf your code already knows the path to test is/not a directory or wants to lie about whether it is/is not a directory, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)\n\n```\nFastIgnore.new.allowed?('relative/path', directory: false) # matches `path` as a file\nFastIgnore.new.allowed?('relative/path', directory: true) # matches `path` as a directory\nFastIgnore.new.allowed?('relative/path', directory: nil) # matches path as whatever it is on the filesystem\nFastIgnore.new.allowed?('relative/path)                  # or as a file if it doesn't exist on the file system\n```\n\n#### content: true/false/nil\n\ndefault: `nil`\n\nIf your code already knows the path to test is has a particular text content or wants to lie about the content, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)\n\n```\nFastIgnore.new.allowed?('relative/path', content: \"#!/usr/bin/env ruby\\n\\nputs 'hello'\") # matches ruby shebang\nFastIgnore.new.allowed?('relative/path', content: \"#!/usr/bin/env bash\\n\\necho 'hello'\") # matches bash shebang\nFastIgnore.new.allowed?('relative/path', content: nil) # matches path as whatever content is on the filesystem\nFastIgnore.new.allowed?('relative/path)                # or as an empty file if it doesn't actually exist\n```\n\n#### content: true/false/nil\n\ndefault: `nil`\n\nIf your code already knows the path to test is has a particular text content or wants to lie about the content, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)\n\n```\nFastIgnore.new.allowed?('relative/path', content: \"#!/usr/bin/env ruby\\n\\nputs 'hello'\") # matches ruby shebang\nFastIgnore.new.allowed?('relative/path', content: \"#!/usr/bin/env bash\\n\\necho 'hello'\") # matches bash shebang\nFastIgnore.new.allowed?('relative/path', content: nil) # matches path as whatever content is on the filesystem\nFastIgnore.new.allowed?('relative/path)                # or as an empty file if it doesn't actually exist\n```\n\n#### exist: true/false/nil\n\ndefault: `nil`\n\nIf your code already knows the path to test exists or wants to lie about its existence, you can pass `exists: true` or `exists: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `exists: nil` or nothing)\n\n```\nFastIgnore.new.allowed?('relative/path', exists: true) # will check the path regardless of whether it actually truly exists\nFastIgnore.new.allowed?('relative/path', exists: false) # will always return false\nFastIgnore.new.allowed?('relative/path', exists: nil) # asks the filesystem\nFastIgnore.new.allowed?('relative/path)               # asks the filesystem\n```\n\n#### include_directories: true/false\n\ndefault: `false`\n\nBy default a file must not be a directory for it to be considered allowed. This is intended to match the behaviour of `git ls-files` which only lists files.\n\nTo match directories you can pass `include_directories: true` to `allowed?`\n\n```\nFastIgnore.new.allowed?('relative/path', include_directories: true) # will test the path even if it's a directory\nFastIgnore.new.allowed?('relative/path', include_directories: false) # will always return false if the path is a directory\nFastIgnore.new.allowed?('relative/path)                        # will always return false if the path is a directory\n```\n\n### `relative: true`\n\n**Default: false**\n\nWhen `relative: false`: FastIgnore#each will yield full paths.\nWhen `relative: true`: FastIgnore#each will yield paths relative to the [`root:`](#root) directory\n\n```ruby\nFastIgnore.new(relative: true).to_a\n```\n\n### `root:`\n\n**Default: Dir.pwd ($PWD, the current working directory)**\n\nThis directory is used for:\n- the location of `.git/core/exclude`\n- the ancestor of all non-global [automatically loaded `.gitignore` files](#gitignore_false)\n- the root directory for array rules ([`ignore_rules:`](#ignore_rules), [`include_rules:`](#include_rules), [`argv_rules:`](#argv_rules)) containing `/`\n- the path that [`relative:`](#relative_true) is relative to\n- the ancestor of all paths yielded by [`#each`](#each_map_etc)\n- the path that [`#allowed?`](#allowed) considers relative paths relative to\n- the ancestor of all [`include_files:`](#include_files) and [`ignore_files:`](#ignore_files)\n\nTo use a different directory:\n```ruby\nFastIgnore.new(root: '/absolute/path/to/root').to_a\nFastIgnore.new(root: '../relative/path/to/root').to_a\n```\n\nA relative root will be found relative to the current working directory when the FastIgnore instance is initialized, and that will be the last time the current working directory is relevant.\n\n**Note: Changes to the current working directory (e.g. with `Dir.chdir`), after initialising a FastIgnore instance, will _not_ affect the FastIgnore instance. `root:` will always be what it was when the instance was initialized, even as a default value.**\n\n### `gitignore:`\n\n**Default: true**\n\nWhen `gitignore: true`: the .gitignore file in the [`root:`](#root) directory is loaded, plus any .gitignore files in its subdirectories, the global git ignore file as described in git config, and .git/info/exclude. `.git` directories are also excluded to match the behaviour of `git ls-files`.\nWhen `gitignore: false`: no ignore files or git config files are automatically read, and `.git` will not be automatically excluded.\n\n```ruby\nFastIgnore.new(gitignore: false).to_a\n```\n\n### `ignore_files:`\n\n**This is a list of files in the gitignore format to parse and match paths against, not a list of files to ignore**  If you want an array of files use [`ignore_rules:`](#ignore_rules)\n\nAdditional gitignore-style files, either as a path or an array of paths.\n\nYou can specify other gitignore-style files to ignore as well.\nMissing files will raise an `Errno::ENOENT` error.\n\nRelative paths are relative to the [`root:`](#root) directory.\nAbsolute paths also need to be within the [`root:`](#root) directory.\n\n\n```ruby\nFastIgnore.new(ignore_files: 'relative/path/to/my/ignore/file').to_a\nFastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a\n```\n\nNote: the location of the files will affect rules beginning with or containing `/`.\n\nTo avoid raising `Errno::ENOENT` when the file doesn't exist:\n```ruby\nFastIgnore.new(ignore_files: ['/ignore/file'].select { |f| File.exist?(f) }).to_a\n```\n\n### `ignore_rules:`\n\nThis can be a string, or an array of strings, and multiline strings can be used with one rule per line.\n\n```ruby\nFastIgnore.new(ignore_rules: '.DS_Store').to_a\nFastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a\nFastIgnore.new(ignore_rules: \".git\\n.gitkeep\").to_a\n```\n\nThese rules use the [`root:`](#root) argument to resolve rules containing `/`.\n\n### `include_files:`\n\n**This is an array of files in the gitignore format to parse and match paths against, not a list of files to include.**  If you want an array of files use [`include_rules:`](#include_rules).\n\nBuilding on the gitignore format, FastIgnore also accepts rules to include matching paths (rather than ignoring them).\nA rule matching a directory will include all descendants of that directory.\n\nThese rules can be provided in files either as absolute or relative paths, or an array of paths.\nRelative paths are relative to the [`root:`](#root) directory.\nAbsolute paths also need to be within the [`root:`](#root) directory.\n\n```ruby\nFastIgnore.new(include_files: 'my_include_file').to_a\nFastIgnore.new(include_files: ['/absolute/include/file', './relative/include/file']).to_a\n```\n\nMissing files will raise an `Errno::ENOENT` error.\n\nTo avoid raising `Errno::ENOENT` when the file doesn't exist:\n```ruby\nFastIgnore.new(include_files: ['include/file'].select { |f| File.exist?(f) }).to_a\n```\n\n**Note: All paths checked must not be excluded by any ignore files AND each included by include file separately AND the [`include_rules:`](#include_rules) AND the [`argv_rules:`](#argv_rules). see [Combinations](#combinations) for solutions to using OR.**\n\n### `include_rules:`\n\nBuilding on the gitignore format, FastIgnore also accepts rules to include matching paths (rather than ignoring them).\nA rule matching a directory will include all descendants of that directory.\n\nThis can be a string, or an array of strings, and multiline strings can be used with one rule per line.\n```ruby\nFastIgnore.new(include_rules: %w{my*rule /and/another !rule}, gitignore: false).to_a\n```\n\nRules use the [`root:`](#root) argument to resolve rules containing `/`.\n\n**Note: All paths checked must not be excluded by any ignore files AND each included by [include file](#include_files) separately AND the `include_rules:` AND the [`argv_rules:`](#argv_rules). see [Combinations](#combinations) for solutions to using OR.**\n\n### `argv_rules:`\nThis is like [`include_rules:`](#include_rules) with additional features meant for dealing with humans and `ARGV` values.\n\nIt expands rules that are absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`).\nThis means rules beginning with `/` are absolute. Not relative to [`root:`](#root).\n\nAdditionally it assumes all rules are relative to the [`root:`](#root) directory (after resolving absolute paths) unless they begin with `*` (or `!*`).\n\nThis can be a string, or an array of strings, and multiline strings can be used with one rule per line.\n\n```ruby\nFastIgnore.new(argv_rules: ['./a/pasted/path', '/or/a/path/from/stdin', 'an/argument', '*.txt']).to_a\n```\n\n**Warning: it will *not* expand e.g. `/../` in the middle of a rule that doesn't begin with any of `~`,`../`,`./`,`/`.**\n\n**Note: All paths checked must not be excluded by any ignore files AND each included by [include file](#include_files) separately AND the [`include_rules:`](#include_rules) AND the `argv_rules:`. see [Combinations](#combinations) for solutions to using OR.**\n\n### shebang rules\n\nSometimes you need to match files by their shebang/hashbang/etc rather than their path or filename\n\nRules beginning with `#!:` will match whole words in the shebang line of extensionless files.\ne.g.\n```gitignore\n#!:ruby\n```\nwill match shebang lines: `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`\n\ne.g.\n```gitignore\n#!:bin/ruby\n```\nwill match `#!/bin/ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`\nOnly exact substring matches are available, There's no special handling of * or / or etc.\n\nThese rules can be supplied any way regular rules are, whether in a .gitignore file or files mentioned in [`include_files:`](#include_files) or [`ignore_files:`](#ignore_files) or [`include_rules:`](#include_rules) or [`ignore_rules:`](#ignore_rules) or [`argv_rules:`](#argv_rules)\n```ruby\nFastIgnore.new(include_rules: ['*.rb', '#!:ruby']).to_a\nFastIgnore.new(ignore_rules: ['*.sh', '#!:sh', '#!:bash', '#!:zsh']).to_a\n```\n\n**Note: git considers rules like this as a comment and will ignore them.**\n\n## Combinations\n\nIn the simplest case a file must be allowed by each ignore file, each include file, and each array of rules. That is, they are combined using `AND`.\n\nTo combine files using `OR`, that is, a file may be matched by either file it doesn't have to be referred to in both:\nprovide the files as strings to [`include_rules:`](#include_rules) or [`ignore_rules:`](#ignore_rules)\n```ruby\nFastIgnore.new(include_rules: [File.read('/my/path'), File.read('/another/path')])).to_a\n```\nThis does unfortunately lose the file path as the root for rules containing `/`.\nIf that's important, combine the files in the file system and use [`include_files:`](#include_files) or [`ignore_files:`](#ignore_files) as normal.\n\nTo use the additional `ARGV` handling of [`argv_rules:`](#argv_rules) on a file, read the file into the array.\n\n```ruby\nFastIgnore.new(argv_rules: [\"my/rule\", File.read('/my/path')]).to_a\n```\n\nThis does unfortunately lose the file path as the root `/` and there is no workaround except setting the [`root:`](#root) for the whole FastIgnore instance.\n## Limitations\n- Doesn't know what to do if you change the current working directory inside the [`FastIgnore#each`](#each_map_etc) block.\n  So don't do that.\n\n  (It does handle changing the current working directory between [`FastIgnore#allowed?`](#allowed) calls)\n- FastIgnore always matches patterns case-insensitively. (git varies by filesystem).\n- FastIgnore always outputs paths as literal UTF-8 characters. (git depends on your core.quotepath setting but by default outputs non ascii paths with octal escapes surrounded by quotes).\n- git has a system-wide config file installed at `$(prefix)/etc/gitconfig`, where `prefix` is defined for git at install time. FastIgnore assumes that it will always be `/usr/local/etc/gitconfig`. if it's important your system config file is looked at, as that's where you have the core.excludesfile defined, use git's built-in way to override this by adding `export GIT_CONFIG_SYSTEM='/the/actual/location'` to your shell profile.\n- Because git looks at its own index objects and FastIgnore looks at the file system there may be some differences between FastIgnore and `git ls-files`. To avoid these differences you may want to use the [`git_ls`](https://github.com/robotdana/git_ls) gem instead\n  - Tracked files that were committed before the matching ignore rule was committed will be returned by `git ls-files`, but not by FastIgnore.\n  - Untracked files will be returned by FastIgnore, but not by `git ls-files`\n  - Deleted files whose deletions haven't been committed will be returned by `git ls-files`, but not by FastIgnore\n  - On a case insensitive file system, with files that differ only by case, `git ls-files` will include all case variations, while FastIgnore will only include whichever variation git placed in the file system.\n  - FastIgnore is unaware of submodules and just treats them like regular directories. For example: `git ls-files --recurse-submodules` won't use the parent repo's gitignore on a submodule, while FastIgnore doesn't know it's a submodule and will.\n  - FastIgnore will only return the files actually on the file system when using `git sparse-checkout`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/robotdana/fast_ignore.\n\nSome tools that may help:\n\n- `bin/setup`: install development dependencies\n- `bundle exec rspec`: run all tests\n- `bundle exec rake`: run all tests and linters\n- `bin/console`: open a `pry` console with everything required for experimenting\n- `bin/ls [argv_rules]`: the equivalent of `git ls-files`\n- `bin/prof/ls [argv_rules]`: ruby-prof report for `bin/ls`\n- `bin/prof/parse [argv_rules]`: ruby-prof report for parsing root and global gitignore files and any arguments.\n- `bin/time [argv_rules]`: the average time for 30 runs of `bin/ls`\u003cbr\u003e\n  This repo is too small to stress bin/time more than 0.01s, switch to a large repo and find the average time before and after changes.\n- `bin/compare`: compare the speed and output of FastIgnore and `git ls-files`.\n  (suppressing differences that are because of known [limitations](#limitations))\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobotdana%2Ffast_ignore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobotdana%2Ffast_ignore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobotdana%2Ffast_ignore/lists"}