TechEarl

How to List the Files Changed in Git

List the files changed in git: working tree, staged, the last commit, between branches, or since N commits. Then pipe the list straight into a linter so you only check what changed.

Ishan Karunaratne⏱️ 7 min readUpdated
Share thisCopied
List the files changed in git from the command line: working tree, staged, last commit, between two branches, and piping the list to a linter to check only what changed.

The fastest way to list the files changed in git is git status -s for a working-tree overview, or git diff --name-only when you want a clean, scriptable list of nothing but paths. Which one you reach for depends on the question you are actually asking: what have I touched right now, what is staged for the next commit, what did the last commit change, or what differs between two branches. Each is a different command, and getting them confused is why people end up grepping output that has the wrong shape.

Here is the short version, then the detail behind each case.

bash
git status -s                          # working tree, short and human-readable
git diff --name-only                   # unstaged changes, paths only
git diff --name-only --cached          # staged changes, paths only
git diff-tree --no-commit-id --name-only -r HEAD   # files in the last commit
git diff --name-only main...feature    # changed between two branches

Files you have changed in the working tree

For a quick look at everything that is different in your working tree, the short status is the one I run dozens of times a day:

bash
git status -s

That prints a two-column code in front of each path. The left column is the staged (index) state, the right column is the working-tree state, so M file.js (M on the left) is staged and M file.js (M on the right) is modified but not staged. It is compact and it tells you both halves at once.

When you want only the paths, with no status codes and nothing else to strip, use git diff with --name-only. This is the form to pipe into other tools:

bash
git diff --name-only            # tracked files changed but NOT yet staged
git diff --name-only --cached   # tracked files that ARE staged for the next commit

--cached and --staged are the same flag (--staged was added later as the readable alias), so use whichever reads better to you. The one thing git diff will not show is brand-new untracked files, because there is nothing in the index to diff against yet. For those, git status -s shows them with ??, or list them on their own:

bash
git ls-files --others --exclude-standard   # untracked files, honoring .gitignore

Files changed in the last commit

To see what a single commit touched, ask git diff-tree for HEAD:

bash
git diff-tree --no-commit-id --name-only -r HEAD

The flags matter: -r recurses into subdirectories (without it you get the top-level tree entries, not the actual files), and --no-commit-id suppresses the commit SHA line so the output is paths and nothing else. Swap HEAD for any commit reference to inspect an older commit.

A friendlier equivalent, if you do not mind a slightly less script-pure tool, is git show:

bash
git show --name-only --pretty="" HEAD

The empty --pretty="" strips the commit header so you are left with just the changed file names.

Files changed between branches or since a commit

To list what differs between two branches, the three-dot form is almost always what you want:

bash
git diff --name-only main...feature

The three dots (...) compare feature against the point where it branched off main, which answers "what did this feature branch actually change" rather than dragging in every commit that landed on main in the meantime. Two dots (main..feature) compares the branch tips directly, which is occasionally what you want but more often is not. This main...feature form is the backbone of "lint only what this branch changed" in CI.

To list what has changed since a specific point in your own history, diff against a commit reference:

bash
git diff --name-only HEAD~3   # everything that changed across the last 3 commits
git diff --name-only abc1234  # everything changed since commit abc1234

Lint or format only the files that changed

This is the payoff. Once the list is just paths, you pipe it into a linter or formatter and check only what changed, instead of walking the whole tree on every commit. The pattern for a pre-commit check, linting only the JavaScript you have staged:

bash
git diff --name-only --cached | grep '\.js$' | xargs eslint

There are two traps in that one-liner worth fixing before you ship it.

First, deletions. If you delete a file and stage the deletion, its path is still in the diff, so xargs eslint tries to lint a file that no longer exists and errors out. Filter the diff down to files that still exist with --diff-filter:

bash
git diff --name-only --cached --diff-filter=ACMR | grep '\.js$' | xargs eslint

ACMR keeps Added, Copied, Modified, and Renamed files and drops Deleted ones (D). It is the standard filter for any "do something to each changed file" pipeline.

Second, filenames with spaces. A path like my notes.js gets split into two arguments by the default whitespace word-splitting, and both halves fail. The fix is to delimit on NUL bytes instead of newlines, which is the one byte a filename can never contain. git diff emits NUL-delimited output with -z, and xargs reads it with -0:

bash
git diff --name-only -z --diff-filter=ACMR --cached | xargs -0 eslint

That is the robust form to put in a hook. If you want this handled for you across a whole team, a tool like lint-staged wraps exactly this logic, and you wire it in through a git hook so it runs on every commit. The manual pipeline above is what lint-staged is doing under the hood, and it is enough on its own for a personal project or a quick CI step.

One scripting note: when you genuinely need to parse status (not just diff), prefer git status --porcelain over git status -s. The --porcelain format is explicitly guaranteed stable across git versions, so a script built on it will not break when someone upgrades git. -s is for humans; --porcelain is for machines, even though they look nearly identical.

See also

Sources

Authoritative references this article was fact-checked against.

Tagsgitgit diffgit statuschanged fileslint staged filesCLIDevOps

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Discard Local Changes in Git

Discard local changes in Git with restore, checkout, and clean. How to throw away edits to tracked files, delete untracked files, and reset to the last commit.

How to Undo (Almost) Anything in Git

A by-situation map for undoing things in git: discard a change, unstage a file, undo a commit, undo a pushed commit safely, delete untracked files, and recover with reflog. Modern restore and switch, not just checkout and reset.

How to List Only Filenames with grep -l

grep -l prints the name of each file that contains a match and stops reading at the first hit, which makes it the fast answer to 'which files contain this string'. The lowercase -l, the inverted -L for files missing a pattern, the grep -rl one-liner, the NUL-safe xargs pipeline for find-and-replace, and the BSD vs GNU notes.