.gitignore for Rust

target/ !**/src/**/target/ **/*.rs.bk *.pdb *.profraw *.profdata

A Rust .gitignore needs target/, Cargo’s build directory, and a few files tools leave beside the source: **/*.rs.bk backups from rustfmt, *.pdb debug symbols from MSVC builds, the *.profraw profiles that -C instrument-coverage writes, and the *.profdata files merged from them. With one more line that keeps a source module called target, that is the tool’s whole Rust template.

What it ignores, and what it keeps

target/ holds everything cargo build, cargo test and cargo doc produce: debug/, release/, the incremental cache and the rendered docs. It is rebuilt from source, tied to your machine and toolchain, and grows to gigabytes.

Keep Cargo.lock. In an application it pins exactly what you tested and shipped, and since 2023 the Cargo team’s advice is to commit it for libraries too. Older versions of cargo new --lib added Cargo.lock to .gitignore, so delete that line if your file still has it. In a scratch repository with this file, git check-ignore -v -n --no-index prints the file, line and pattern that matched each path, or :: when nothing did:

.gitignore:3:target/	target/debug/app
.gitignore:3:target/	fuzz/target/x
::	src/target/mod.rs
.gitignore:5:!**/src/**/target/	src/target
::	Cargo.lock
::	Cargo.toml
.gitignore:7:**/*.rs.bk	src/main.rs.bk
.gitignore:11:*.profraw	default_123.profraw

Why target/ has a second line

A pattern whose only slash is at the end matches at every level (gitignore(5)). That catches the target/ of a nested crate such as fuzz/, but on its own it would also hide a module directory called target, so the template re-includes any target/ under a src/ folder. cargo new takes the other route and writes /target, anchored to the root, which leaves nested crates’ build output untracked but visible.

Keeping one folder of target/

Git never looks inside a directory it has excluded, so a !target/doc/ line after target/ changes nothing: target/doc/index.html is still reported against target/ on line 3. Ignore the directory’s contents instead, then re-include the folder, with these two lines in place of target/:

/target/*
!/target/doc/
::	target/doc/index.html
.gitignore:3:/target/*	target/debug/app

Already committed target/?

.gitignore only affects untracked files. If target/ was committed before the line existed, Git keeps tracking it, and git check-ignore without --no-index prints nothing for it. Untrack it, keeping the files on disk, and commit:

git rm -r --cached target
git commit -m "Stop tracking target/"

Editor and OS files

.DS_Store, Thumbs.db and Vim’s *.swp come from your machine, not from Rust, so they belong in a global ignore file rather than every project’s:

git config --global core.excludesFile ~/.gitignore_global

Without that setting, Git reads ~/.config/git/ignore, or $XDG_CONFIG_HOME/git/ignore when that variable is set.

Try it in the Gitignore Generator

In the file: Rust

.gitignore

1 template · 6 pattern lines
# Rust
# Build output, including the incremental cache and docs
target/
# A source module called target is kept
!**/src/**/target/
# Backups rustfmt leaves next to a file it rewrote
**/*.rs.bk
# Debug symbols from MSVC builds
*.pdb
# Raw profiles from -Cinstrument-coverage
*.profraw
*.profdata
  • ✓Not ignored: no line in this file matches "/Cargo.lock".

Lines are applied top to bottom, and the last one that matches wins — which is why a line of your own can overrule a template.

Runs in your browser. Nothing you enter is sent anywhere.