npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smoothbricks/git-format-staged

v1.0.1

Published

Git command to transform staged files according to a command that accepts file content on stdin and produces output on stdout.

Downloads

264

Readme

git-format-staged

Build Status

Consider a project where you want all code formatted consistently. So you use a formatting command. (For example I use prettier in my Javascript and Typescript projects.) You want to make sure that everyone working on the project runs the formatter, so you use a tool like husky to install a git pre-commit hook. The naive way to write that hook would be to:

  • get a list of staged files
  • run the formatter on those files
  • run git add to stage the results of formatting

The problem with that solution is it forces you to commit entire files. At worst this will lead to contributors to unwittingly committing changes. At best it disrupts workflow for contributors who use git add -p.

git-format-staged tackles this problem by running the formatter on the staged version of the file. Staging changes to a file actually produces a new file that exists in the git object database. git-format-staged uses some git plumbing commands to send content from that file to your formatter. The command replaces file content in the git index. The process bypasses the working tree, so any unstaged changes are ignored by the formatter, and remain unstaged.

After formatting a staged file git-format-staged computes a patch which it attempts to apply to the working tree file to keep the working tree in sync with staged changes. If patching fails you will see a warning message. The version of the file that is committed will be formatted properly - the warning just means that working tree copy of the file has been left unformatted. The patch step can be disabled with the --no-update-working-tree option.

Version 4 Features

git-format-staged v4 introduces several major enhancements:

  • Proper gitwildmatch pattern matching: Uses the pathspec library for correct pattern handling
  • Configuration file support: Define formatters in YAML or TOML files
  • Multiple formatter support: Apply multiple formatters to the same file in a pipeline
  • Enhanced debugging: Comprehensive debug output with --debug
  • Working tree formatting: Format unstaged changes with --unstaged or --also-unstaged
  • Improved performance: Files are processed once with all matching formatters applied in sequence

How to install

Install with Nix

Install via the CLI:

$ nix profile add github:hallettj/git-format-staged

Or add to your flake imports, and use the default package output.

Install with NPM

Requires Python 3.8 or later, and the pathspec library.

Install as a development dependency in a project that uses npm packages:

$ npm install --save-dev @smoothbricks/git-format-staged

Or install globally:

$ npm install --global @smoothbricks/git-format-staged

Or just copy the script

Requires Python 3.8 or later.

If you do not use the above methods you can copy the git-format-staged script from this repository and place it in your executable path. The script is MIT-licensed - so you can check the script into version control in your own open source project if you wish.

Note: You'll need to install the Python pathspec library:

$ pip install pathspec pyyaml toml

How to use

For detailed information run:

$ git-format-staged --help

Command Line Usage

The command expects a shell command to run a formatter, and one or more file patterns to identify which files should be formatted. For example:

$ git-format-staged --formatter 'prettier --stdin-filepath "{}"' 'src/*.js'

That will format all files under src/ and its subdirectories using prettier. The file patterns use gitwildmatch-style matching via the pathspec library.

Configuration Files

git-format-staged v4 supports configuration files in YAML or TOML format. Create a .git-format-staged.yml or .git-format-staged.toml file in your project root:

YAML Example (.git-format-staged.yml)

formatters:
  prettier:
    command: "prettier --stdin-filepath '{}'"
    patterns:
      - "*.js"
      - "*.jsx"
      - "*.ts"
      - "*.tsx"
      - "!node_modules/**"
      - "!dist/**"
  
  eslint-fix:
    # Using eslint-stdout wrapper for proper pipe-friendly formatting
    # Install: npm install --save-dev eslint-stdout
    command: "eslint-stdout '{}'"
    patterns:
      - "*.js"
      - "*.jsx"
      - "!*.test.js"

  black:
    command: "black -"
    patterns:
      - "*.py"
      - "!venv/**"

settings:
  update_working_tree: true
  show_commands: false

TOML Example (.git-format-staged.toml)

version = 1
debug = false

[formatters.prettier]
command = "prettier --stdin-filepath '{}'"
patterns = [
  "*.js",
  "*.jsx", 
  "*.ts",
  "*.tsx",
  "!node_modules/**",
  "!dist/**"
]

[formatters.black]
command = "black -"
patterns = [
  "*.py",
  "!venv/**",
  "!.venv/**"
]

[settings]
update_working_tree = true
show_commands = false

With a configuration file, you can simply run:

$ git-format-staged

Multiple Formatters

When multiple formatters match the same file, they are applied in sequence as a pipeline. The output of one formatter becomes the input of the next:

formatters:
  # First, format with prettier
  prettier:
    command: "prettier --stdin-filepath '{}'"
    patterns: ["*.js"]
  
  # Then run eslint --fix
  eslint-fix:
    command: "eslint-stdout '{}'"
    patterns: ["*.js"]

Readonly Mode in Config Files

You can configure formatters to run in readonly mode using the readonly option (or no_write for compatibility). This is useful for linters that should check code but not modify files:

formatters:
  # First: formatter that modifies files
  prettier:
    command: "prettier --stdin-filepath '{}'"
    patterns: ["*.js"]
  
  # Then: linter that checks the formatted result
  eslint-check:
    command: "eslint --stdin --stdin-filename '{}' >&2"
    patterns: ["*.js"]
    readonly: true  # Check the prettified code without modifying it

This is useful for workflows where you want to:

  1. Format code with prettier
  2. Then verify the formatted code passes linting rules
  3. Abort the commit if linting fails

When a formatter has readonly: true:

  • The formatter runs and can fail the commit if it exits with non-zero status
  • That specific formatter doesn't modify files (but other formatters still can)
  • Output to stdout is ignored (send errors to stderr with >&2)
  • Regular formatters run first and modify files, then readonly formatters check the result

Pattern Sets and Inheritance

You can define reusable pattern sets and use extends to inherit patterns:

# Define reusable pattern sets
pattern_sets:
  common:
    - "src/**/*.js"
    - "src/**/*.ts"
    - "!node_modules/**"
    - "!dist/**"
  
  tests:
    - "test/**/*.js"
    - "test/**/*.spec.ts"
    - "!test/fixtures/**"

formatters:
  prettier:
    command: "prettier --stdin-filepath '{}'"
    extends: [common, tests]  # Inherit from multiple sets
    patterns:  # Additional patterns
      - "*.json"
      - "*.yml"
  
  eslint:
    command: "eslint-stdout '{}'"
    extends: common  # Single inheritance
    patterns:
      - "test/**/*.js"  # Add more patterns

The same works in TOML:

[pattern_sets]
common = [
  "src/**/*.js",
  "src/**/*.ts", 
  "!node_modules/**",
  "!dist/**"
]

tests = [
  "test/**/*.js",
  "test/**/*.spec.ts",
  "!test/fixtures/**"
]

[formatters.prettier]
command = "prettier --stdin-filepath '{}'"
extends = ["common", "tests"]
patterns = ["*.json", "*.yml"]

When using extends:

  • Patterns from all extended sets are merged in order
  • Additional patterns in the formatter are appended
  • All patterns are flattened into a single list

YAML Anchors and Pattern Sets

Both YAML anchors and pattern sets can be used for pattern reuse, but they work differently:

YAML Anchors (YAML only)

# Define patterns with YAML anchor
common_patterns: &common
  - "*.js"
  - "*.ts"
  - "!node_modules/**"

formatters:
  # Direct alias reference - replaces entire patterns list
  formatter1:
    command: "prettier --stdin-filepath '{}'"
    patterns: *common
  
  # Anchor in list - creates nested list (automatically flattened)
  formatter2:
    command: "eslint-stdout '{}'"
    patterns:
      - *common      # Flattened automatically
      - "*.json"     # Additional patterns

Pattern Sets vs YAML Anchors

  • Pattern Sets: Work in both YAML and TOML, designed for pattern inheritance
  • YAML Anchors: YAML-only feature, more flexible but can be confusing with lists
  • Glob patterns: Patterns like *myfile or !*test* are treated as glob patterns, not YAML aliases (unless a matching anchor exists)

Special Characters in Patterns

Patterns starting with *, !, &, etc. are automatically handled:

  • If a pattern like *common has a matching anchor &common, it's treated as a YAML alias
  • Otherwise, it's treated as a glob pattern (e.g., *myfile matches files ending with "myfile")
  • No manual quoting needed - the tool handles this automatically

Pattern Matching

Patterns use gitwildmatch-style syntax:

  • *.js matches all .js files recursively
  • src/**/*.js matches .js files under src/
  • !vendor/** excludes all files under vendor/
  • *.test.js matches test files
  • !*.test.js excludes test files

Files can be excluded by prefixing a pattern with !. Exclusions take precedence regardless of where they appear in the pattern list, so ['*', '!*.md'] and ['!*.md', '*'] both exclude Markdown files.

Working Tree Formatting

Format unstaged changes instead of staged changes:

$ git-format-staged --unstaged --formatter 'black -' '*.py'

Format both staged and unstaged changes:

$ git-format-staged --also-unstaged --formatter 'prettier --stdin-filepath "{}"' '*.js'

By default, --unstaged and --also-unstaged include only tracked files with unstaged changes. Add --include-untracked to also format untracked files:

$ git-format-staged --unstaged --include-untracked --formatter 'black -' '*.py'

The --files option is explicit and can format supplied files regardless of whether Git tracks them.

Debugging

Use --debug to see detailed information about pattern matching and formatter execution:

$ git-format-staged --debug

Breaking Changes in v4

Pattern Matching

  • Old behavior: Used Python's fnmatch which had bugs with absolute path conversion
  • New behavior: Uses pathspec library for proper gitwildmatch-style matching with exclusion precedence
  • Migration: Patterns should work the same, but edge cases are now handled correctly

Multiple Formatters

  • Old behavior: Only one formatter could be specified
  • New behavior: Config files can define multiple formatters that run in sequence
  • Migration: Command-line usage remains the same for single formatters

Configuration Files

  • New feature: YAML/TOML config files are now supported
  • Migration: Not required - command-line usage still works

Performance

  • Old behavior: Each formatter was run separately for each file
  • New behavior: All formatters for a file run in a single pipeline
  • Impact: Significantly faster when using multiple formatters

Using ESLint with git-format-staged

For ESLint formatting, we recommend using eslint-stdout, a wrapper that makes ESLint work seamlessly with piped formatters like git-format-staged:

$ npm install --save-dev eslint-stdout

Then in your configuration:

formatters:
  eslint:
    command: "eslint-stdout '{}'"
    patterns: ["*.js", "*.jsx", "*.ts", "*.tsx"]

The eslint-stdout wrapper handles the complexity of ESLint's stdin/stdout behavior and outputs only the fixed code to stdout while sending errors to stderr.

Check staged changes with a linter without formatting

Perhaps you do not want to reformat files automatically; but you do want to prevent files from being committed if they do not conform to style rules. You can use git-format-staged with the --no-write option, and supply a lint command instead of a format command. Here is an example using ESLint:

$ git-format-staged --no-write -f 'eslint --stdin --stdin-filename "{}" >&2' 'src/*.js'

If this command is run in a pre-commit hook, and the lint command fails the commit will be aborted and error messages will be displayed. The lint command must read file content via stdin. Anything that the lint command outputs to stdout will be ignored. In the example above eslint is given the --stdin option to tell it to read content from stdin instead of reading files from disk, and messages from eslint are redirected to stderr (using the >&2 notation) so that you can see them.

Set up a pre-commit hook with Husky

Follow these steps to automatically format all Javascript files on commit in a project that uses npm.

Install git-format-staged, husky, and a formatter (I use prettier):

$ npm install --save-dev @smoothbricks/git-format-staged husky prettier

Add a prepare script to install husky when running npm install:

$ npm set-script prepare "husky install"
$ npm run prepare

Add the pre-commit hook:

$ npx husky add .husky/pre-commit "git-format-staged --formatter 'prettier --stdin-filepath \"{}\"' '*.js' '*.ts'"
$ git add .husky/pre-commit

Once again note that the formatter command and the '*.js' and '*.ts' patterns are quoted!

That's it! Whenever a file is changed as a result of formatting on commit you will see a message in the output from git commit.

Comparisons to similar utilities

There are other tools that will format or lint staged files. What distinguishes git-format-staged is that when a file has both staged and unstaged changes git-format-staged ignores the unstaged changes; and it leaves unstaged changes unstaged when applying formatting.

Some linters (such as precise-commits) have an option to restrict linting to certain lines or character ranges in files, which is one way to ignore unstaged changes while linting. The author is not aware of a utility other than git-format-staged that can apply any arbitrary linter so that it ignores unstaged changes.

Some other formatting utilities (such as pre-commit) use a different strategy to keep unstaged changes unstaged:

  1. stash unstaged changes
  2. apply the formatter to working tree files
  3. stage any resulting changes
  4. reapply stashed changes to the working tree.

The problem is that you may get a conflict where stashed changes cannot be automatically merged after formatting has been applied. In those cases the user has to do some manual fixing to retrieve unstaged changes. As far as the author is aware git-format-staged is the only utility that applies a formatter without touching working tree files, and then merges formatting changes to the working tree. The advantage of merging formatting changes into unstaged changes (as opposed to merging unstaged changes into formatting changes) is that git-format-staged is non-lossy: if there are conflicts between unstaged changes and formatting the unstaged changes win, and are kept in the working tree, while staged/committed files are formatted properly.

Another advantage of git-format-staged is that it has no dependencies beyond Python and git, and can be dropped into any programming language ecosystem.

Some more comparisons:

  • lint-staged lints and formats staged files. At the time of this writing it does not have an official strategy for ignoring unstaged changes when linting, or for keeping unstaged changes unstaged when formatting. But lint-staged does provide powerful configuration options around which files should be linted or formatted, what should happen before and after linting, and so on.
  • pretty-quick formats staged files with prettier. By default pretty-quick will abort the commit if files are partially staged to allow the user to decide how to re-stage changes from formatting. The result is more manual effort compared to git-format-staged.
  • the one-liner git diff --diff-filter=d --cached | grep '^[+-]' | grep -Ev '^(--- a/|\+\+\+ b/)' | LINT_COMMAND (described here) extracts changed hunks and feeds them to a linter. But linting will fail if the linter requires the entire file for context. For example a linter might report errors if it cannot see import lines.