Restore Tracked Files Safely with git checkout-index
You will finish with a practical way to copy tracked files from Git's index into the working tree, including a controlled export of the whole index. The examples use Git 2.43.0, supplied here by git-man version 1:2.43.0-1ubuntu7.3.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need an existing Git working tree with committed or staged content. These commands do not need elevated privileges. They can overwrite local files, so inspect the target paths first and keep a recoverable copy of anything you have not committed.
Checkpoint: understand what will change
git checkout-index reads the index, sometimes called the staging area. It copies the cached version of each selected path into the working directory. By default it does not overwrite a file that is already there. It does not fetch from a remote and it does not update the index unless you ask for its stat information to be refreshed.
Check the repository and the paths before doing anything destructive:
$ git status --short
$ git ls-files --error-unmatch path/to/file
The first command shows local changes. The second confirms that the path is tracked in the index. Replace path/to/file with a real path; the command exits non-zero when the path is not indexed.
1. Restore one tracked file without overwriting it
Give the path after --. This makes it clear that the remaining argument is a filename, even if it begins with a hyphen:
$ git checkout-index -- path/to/file
If the working-tree file is absent, its indexed contents are created. If it already exists, Git leaves it alone and may report that it will not overwrite it. That default is the useful safety boundary. A command with no path does nothing, so do not assume that bare git checkout-index restores everything.
Checkpoint: compare the restored file with the index version:
$ git diff -- path/to/file
$ git diff --cached -- path/to/file
The first comparison is against the index and should be empty if the working file now matches it. The second compares the index with HEAD; it tells you whether the cached version itself is staged content rather than the last commit.
2. Force an overwrite only after checking the path
-f or --force replaces an existing working-tree file with its indexed contents:
$ git diff -- path/to/file
$ git checkout-index -f -- path/to/file
$ git diff -- path/to/file
Warning: the force command changes the file immediately. Any uncommitted working-tree content in that path is lost. The index and HEAD do not provide an automatic undo for content that existed only in the working tree.
If you forced the wrong file, recovery depends on where the lost content still exists. Check an editor backup, filesystem snapshot, separate copy, or a commit. If the content was staged before the overwrite, git checkout-index -f -- path/to/file can restore that staged version again. Otherwise stop writing to the affected filesystem and use your normal recovery process.
3. Restore the complete index
Use -a or --all when the target is every ordinary entry in the index:
$ git checkout-index -a
This creates missing files but still does not overwrite existing ones. To refresh every path from the index, combine it with -f:
$ git checkout-index -f -a
That combination is broad and destructive to uncommitted working-tree edits. Review git status --short first. If you only want files that already exist, add -n or --no-create; it refreshes existing paths and does not create new ones:
$ git checkout-index -n -f -a
The index can mark paths with the skip-worktree bit. Ordinary -a respects that bit. Add --ignore-skip-worktree-bits only when you deliberately want those paths included:
$ git checkout-index -f -a --ignore-skip-worktree-bits
4. Export the index into another directory
The prefix option is useful for producing a tree of files without changing the repository's normal working paths. The destination must already exist, and the prefix should normally end in a slash:
$ mkdir -p ../exported-tree
$ git checkout-index --prefix=../exported-tree/ -a
Git writes each indexed path below that prefix. For example, src/main.c becomes ../exported-tree/src/main.c. The trailing slash matters because the prefix is literal text: --prefix=../exported-tree would produce a name such as ../exported-treesrc/main.c.
Existing files below the destination are protected by the normal no-overwrite rule. If you intend to replace an earlier export, inspect it first and use -f only after confirming that the destination is disposable. This operation does not need root and does not alter the index.
5. Feed paths from another command
For a generated list, use --stdin rather than putting every path into one shell command:
$ find . -name '*.h' -print0 | git checkout-index -f -z --stdin
-z changes the separator to a NUL byte, so filenames containing spaces, quotes or newlines are passed as complete paths. The -- marker is not used here because --stdin supplies the paths. The force flag still makes this destructive: the command replaces matching working-tree files with their indexed copies.
For a small, known list, a quoted shell list is easier to review:
$ printf '%s\0' 'docs/one.h' 'src/two.h' | git checkout-index -z --stdin
Without -z, standard input uses one path per line. Prefer NUL separation whenever another program is producing the list.
6. Handle merge stages or temporary output
An unresolved merge can leave multiple index stages for a path. --stage=1, --stage=2 or --stage=3 selects one stage, provided the requested number is between 1 and 3:
$ git checkout-index --stage=2 -- path/to/conflicted-file
Use this when an external tool needs one particular side of an unmerged entry. Do not guess which stage is correct; inspect the merge context and the tool's documentation first.
--temp writes each selected entry to a temporary file instead of the working tree and prints a tab-separated association:
$ git checkout-index --temp -- path/to/file
.merge_file_XYZ123 path/to/file
The temporary filename is relative to the repository's top level. The path shown is the tracked path relative to the current directory. With --stage=all, Git implies --temp and prints three temporary names, one for each available stage, followed by the path. A dot means that stage is absent.
Temporary files are for the consuming tool to process. They are not a persistent restore point, and the command does not update index stat information in this mode.
Done means
- You confirmed the target paths with
git statusorgit ls-files. - You used a specific path or
-adeliberately; baregit checkout-indexdid nothing. - You used
-fonly after accepting that uncommitted file content could be lost. - You verified the result with
git diff -- path/to/fileor inspected the export. - You used
--stdin -zfor generated filenames that may contain whitespace or newlines.