Audit Tracked Files with git ls-files

git ls-files answers the question git status only gestures at: exactly which paths Git has in its index right now. By the end you will have a small set of commands for three separate questions: what Git tracks, what has changed in the working tree, and which files are hidden by ignore rules. The examples use Git 2.43.0, from the installed git-man package version 1:2.43.0-1ubuntu7.3.

Allow about fifteen minutes. You need Git and an existing repository. Everything below is read-only unless you run a command that changes the repository yourself. No command needs sudo: elevated privileges only make permissions harder to diagnose, and they do not reveal files Git cannot read.

1. Check the installed command

Run this from the repository you want to inspect:

$ git --version
git version 2.43.0
$ git rev-parse --show-toplevel
/path/to/project

The second command doubles as a checkpoint. If it says the current directory is not a Git repository, change into the correct checkout rather than pointing the inventory commands at a guessed path.

git ls-files reads Git's index and, when you ask, combines it with the working tree. With no selection option its default is the cached view: paths currently recorded in the index. That means it lists tracked paths, including a tracked file that has since been deleted from disk.

2. List the paths in the index

Start with the default form:

$ git ls-files
.gitignore
src/code.txt
tracked.txt

Names and order will differ in your repository. This is one pathname per line, not a report on whether each file is clean: a modified tracked file still appears, because its indexed version is still there. Put a path after -- to narrow the query and make the option boundary explicit:

$ git ls-files -- src/
src/code.txt

Path arguments are matched against paths in the index. They do not turn this into a general filesystem search.

3. Find modifications, deletions and untracked files

Use a separate selection flag for each working-tree state you care about:

$ git ls-files --modified --deleted --others --exclude-standard
new.txt
src/code.txt
tracked.txt

Drop --exclude-standard and an ignored file can turn up among the others: --others does not switch on standard ignore rules by itself, and that mismatch is a common source of noisy scripts.

Checkpoint: compare the result with Git's human-readable status view when you are investigating a change:

$ git status --short
 M tracked.txt
 D src/code.txt
?? new.txt

The two commands answer related questions in different formats. Status is usually easier for a person to read; ls-files earns its keep when you need a precise index or pathname query instead.

4. Add one-letter status tags

Pass --tag to place a single status character before each path:

$ git ls-files --tag --modified --deleted --others --exclude-standard
? new.txt
C src/code.txt
C tracked.txt

The tag explains why the path was picked. ? means untracked; C means a tracked file has an unstaged change. A deleted tracked path is also tagged C here, because an unstaged deletion counts as an unstaged modification. The exact set depends on the flags and the repository state.

Do not parse tagged output as a stable machine protocol if you need richer status detail. The installed manual points instead to git status --porcelain or git diff-files --name-status for scripting.

5. Inspect the index entry and object ID

Use --stage when you need the mode, object name and index stage rather than just the path:

$ git ls-files --stage -- tracked.txt
100644 5626abf0f72e58d7a153368ba57db4c673c0e171 0	tracked.txt

The object ID will differ on your machine. The normal stage is 0. During an unresolved merge, the index can hold stages 1, 2 and 3 for one path; add --unmerged to see only those unresolved entries, which implies staged detail and suppresses the ordinary cached listing.

Need only selected fields? Use the format option:

$ git ls-files --format='%(objectname) %(path)' -- tracked.txt
5626abf0f72e58d7a153368ba57db4c673c0e171 tracked.txt

Supported field names include objectmode, objecttype, objectname, objectsize, stage, end-of-line fields and path. Do not combine --format with options such as --stage, --others, --unmerged, --tag or --eol: Git rejects the incompatible combinations outright.

6. Check line endings and handle unusual names

When line-ending conversion is confusing, ask Git to show the index value, working-tree value and applicable attribute together:

$ git ls-files --eol -- tracked.txt src/code.txt
i/lf    w/lf    attr/                  	tracked.txt
i/lf    w/none  attr/                  	src/code.txt

The output is host-specific. i/ describes the index, w/ describes the working tree, and attr/ shows the relevant attribute. A missing or unreadable working-tree file can produce an empty or different working-tree value.

For scripts, never assume a pathname is safe to split on newlines. By default Git quotes unusual characters according to core.quotePath. Add --null (short form -z) to emit raw names separated by NUL bytes instead:

$ git ls-files --others --exclude-standard --null |
    while IFS= read -r -d '' path; do
        printf 'untracked: %s\n' "$path"
    done

Keep the NUL delimiter all the way through the consumer. A command that converts the stream back to newline-separated text throws away the protection you just asked for.

7. Treat ignored-file queries as deliberate

To list ignored files, combine --ignored with an explicit cached or other view and an exclude source:

$ git ls-files --others --ignored --exclude-standard
debug.log

--ignored on its own is not enough: the manual requires either --cached or --others, plus at least one exclude option. For a repository's standard rules, --exclude-standard is normally the least surprising choice. To test one extra pattern without touching any ignore file, use a shell wildcard as an exclude instead:

$ git ls-files --others --exclude='*.tmp'
scratch.tmp

Quote the pattern so the shell hands it to Git unchanged. These commands only inspect paths: they do not delete ignored files, add them to the index or alter .gitignore. Be careful with a later command such as git clean, though: that is a separate, destructive operation and should never be assumed from an inventory.

8. Diagnose an unexpected path or failure

If a path is supposed to be tracked, require an index match and use the exit status in a script:

$ git ls-files --error-unmatch -- path/to/file
error: pathspec 'path/to/file' did not match any file(s) known to git
Did you forget to 'git add'?
$ printf 'exit status: %s\n' "$?"
exit status: 1

The error text is from Git 2.43.0 and can vary slightly by version. The useful contract is the non-zero status when a requested path is missing from the index. Check spelling, repository location and whether the file was ever added, before you go anywhere near ignore rules.

If you are in a subdirectory, output is normally relative to it. Add --full-name when a script needs paths relative to the repository root instead:

$ cd src
$ git ls-files --full-name -- code.txt
src/code.txt

Do not confuse a relative display path with a different index entry. --full-name changes the presentation, not what Git tracks.

Done means