Git

What Does Git Prune Do? Get the Details

What Does Git Prune Do? Get the Details

Most people who run git prune by hand are surprised by how little it does. It deletes unreachable loose objects from the object database in .git/objects, and that’s the whole job. Branches, tags, working tree files and objects already stored in packs stay exactly as they were.

You rarely need to type it yourself. git gc calls it during routine cleanup and applies the grace period that gc.pruneExpire sets.

Two other commands borrow the name without sharing the work. git fetch –prune and git remote prune delete stale remote-tracking refs, not objects. And according to the Git manual (git-scm.com), unreachable objects already inside packs survive the command untouched.

What does git prune do?

YouTube player

It removes data that no ref, index entry or reflog entry points to anymore, and nothing else. The target is always the loose objects sitting in .git/objects.

Inside a Git repository, every commit, tree, blob and tag is stored as an object. Some of them end up unreachable after a hard reset, a rebase or a deleted branch. A file that was staged and then changed again before the commit leaves one behind too.

On the deletion side, git prune takes loose objects that nothing reaches, along with loose copies of objects that also exist in a pack (it runs git prune-packed for this). Entries in .git/shallow that no ref reaches get cleaned out as well.

Refs of every kind are left alone, whether they’re branches, tags or remote-tracking ones, and so are the files in your working tree. Unreachable objects already stored inside packs stay put until a repack.

Under the hood, the command runs git fsck –unreachable against every ref in refs/, plus any extra heads you pass, then removes whatever that trace did not reach. The Git manual (git-scm.com, 2025) points most users to git gc instead, since gc calls prune as one of its housekeeping steps.

The similarly named git remote prune and git fetch –prune work on remote-tracking branches, not objects. They get their own section at the end of this article.

How does git prune decide that an object is unreachable?

YouTube player

Git follows chains from a handful of starting points. If commit parents, trees and tag targets connect an object to one of them, it counts as reachable. Anything outside those chains is fair game.

Why is GitHub the heart of open source?

Uncover GitHub statistics: developer community growth, repository trends, collaboration patterns, and the platform that powers modern software development.

Explore GitHub Data →

The starting points come from git fsck, the model git prune follows, which by default begins at the index, every ref in refs/ and every reflog (git fsck manual, git-scm.com). The git gc notes add remote-tracking branches to the pile of things that keep objects alive. In practice that means local branches and tags, remote-tracking branches, the index with its staged content, reflog entries for HEAD and branches, and any extra heads passed on the command line.

Git notes are the exception. A note attached to an object does not keep it alive, according to the git-gc documentation.

A commit orphaned by git reset stays protected for as long as its reflog entry lives. Ordinary reflog entries expire after 90 days, while entries for commits no longer on the branch tip (pre-amend and pre-rebase commits) expire after 30 days, per the git-gc documentation (git-scm.com, 2025).

Why git prune often shows no output

YouTube player

Plenty of tutorials demonstrate git prune right after a reset and hit the same thing: an empty dry run, because the reflog still reaches the commit (Atlassian walkthrough).


# orphan the last commit

git reset --hard HEAD~1

# empty output, the reflog still reaches it

git prune --dry-run --verbose

# drop reflog protection (destructive), then preview again

git reflog expire --expire=now --expire-unreachable=now --all
git prune --dry-run --verbose --expire=now

That last command finally lists the orphaned objects. Try it in a throwaway repository only, since expiring the reflog removes your own safety net.

Looking to sharpen your Git skills? Branching, merging, rebasing, and everything else you need - including git rebase, git stash, and commit workflows - is on one page in the Git Cheat Sheet.

Git prune syntax and options

The synopsis is short. Four flags and an optional list of heads, all on one line.

git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]
OptionWhat it doesExample
-n, –dry-runReports what would be removed, deletes nothinggit prune -n
-v, –verboseLists every removed objectgit prune -v
–progressShows progress while it runsgit prune –progress
–expire <time>Only expires loose objects older than the cutoffgit prune –expire=2.weeks.ago
<head>Keeps objects reachable from the listed headsgit prune $(cd ../another && git rev-parse –all)

–expire accepts date strings such as 2.weeks.ago or now. The manual documents no default cutoff for the bare command, so pass one explicitly whenever other Git work might be running.

The head argument adds starting points whose reachable objects are kept. The manual’s own example protects objects that a second repository borrows through .git/objects/info/alternates.

You’ll see git prune –force in some tutorials. The manual lists no such flag, and the page has not changed since Git 2.43.0 in November 2023 (git-scm.com).

How do git gc and git prune work together?

git gc calls git prune with a 2-week grace period by default, so unreachable objects younger than that survive a normal cleanup. They aren’t alternatives. Run gc and prune runs with it.

Grace period and automatic runs

gc.pruneExpire sets the grace period. The value “now” disables it and “never” suppresses pruning. For a single run, git gc –prune=<date> overrides the setting, and git gc –no-prune skips loose-object pruning entirely.

The loose-object threshold that triggers an automatic gc lives in gc.auto.

Change the grace period permanently with git config, or per run with –prune. Passing now drops the grace period completely, which the manual flags as a corruption risk when another process writes to the repository at the same time.

Porcelain commands that create objects check the repository size afterward and start git gc –auto once loose objects pass the gc.auto threshold. Setting gc.auto to 0 turns that heuristic off.

The git-gc documentation lists these defaults, unchanged since Git 2.52 (November 2025).

  • gc.pruneExpire: 2 weeks ago
  • gc.auto: about 6700 loose objects
  • gc.reflogExpire: 90 days
  • gc.reflogExpireUnreachable: 30 days
  • gc.worktreePruneExpire: 3 months

How cruft packs change what prune finds

The default for cruft packs flipped at some point. The Git 2.40 manual page lists gc.cruftPacks as false, while the 2.45 manual and the current documentation list it as true.

With cruft packs on, gc moves unreachable objects into a separate pack and expires them with repack –cruft –cruft-expiration. It no longer scatters them as loose files across .git/objects.

A repository that gc maintains regularly therefore has fewer loose unreachable objects for git prune to remove.

For routine cleanup, just run git gc. Reach for git prune directly when the job is narrow, like the alternates case above or a dry-run look at what’s unreachable.

How to run git prune safely

The order matters. Measure the object database, list the candidates, preview with a dry run, delete with an explicit cutoff, then measure again. That makes six steps, and only the fifth deletes anything.

  1. Run git count-objects -v -H and note the loose object count, loose size and pack size.
  2. Use git fsck --unreachable to print every object that no ref, index entry or reflog entry reaches.
  3. Add --no-reflogs to the same command. The commits it lists are the ones only the reflog is protecting.
  4. Check what you found. git cat-file -t <id> shows the object type, and git cat-file -p <id> prints its content.
  5. Run git prune --dry-run --verbose --expire=1.day.ago, read the list, and repeat without --dry-run.
  6. Run git count-objects -v -H again. The loose count should drop and nothing else should move.

For a dangling commit, the hash on the fsck line is an ordinary commit hash. Paste it into git cat-file or git show before deciding the commit is junk.

Reading git count-objects output

The count and size fields cover loose objects and their disk use, which is the only pool git prune can shrink. The in-pack, packs and size-pack fields cover packed objects and the space the packs take, and prune never reduces those.

prune-packable counts loose objects that also sit in a pack. git prune-packed removes them (git count-objects manual, git-scm.com).

The garbage field reports files that are neither valid loose objects nor valid packs. They’re listed separately and deserve a manual look.

If size-pack dwarfs size, a prune run will barely register. The next section covers why.

When git prune does not help, and what it can break

git prune frees nothing when the waste sits inside packs. It deletes for good what it does remove, and it can cut away objects that another process or repository still needs. The Git manuals document each of these.

Packed unreachable objects stay put

The git-prune manual states that unreachable objects already inside packs remain after a run.

git repack -a -d packs everything referenced into one pack and removes the redundant old packs, which drops the unreachable packed objects along with them. git repack -A -d goes the other way and loosens unreachable objects from old packs, so a later prune or gc expires them under the normal rules.

The repack manual (git-scm.com) says the first form cleans up objects that git prune leaves behind and git fsck still reports as dangling. A repository whose pack size dominates is a repack job, not a prune job.

Deletion is permanent

Once a loose object file is gone, only another copy brings it back. The git fsck manual says corrupt objects have to be recovered from backups or other archives, and the same logic applies to objects you have pruned. A few things can save you.

  • Another clone that fetched the commits
  • git fsck --lost-found before pruning, which writes dangling objects into .git/lost-found/commit/ and .git/lost-found/other/
  • --expire-to=<dir> on git gc or git repack with cruft packs, which keeps a copy of pruned objects as a backup

The lost-found copy is the cheapest option. Run it before the dry run, not after the delete.

Concurrent writers and shared object stores

A running Git process can write an object before it creates a ref to it. If prune runs in that gap, the object disappears and the process fails or leaves the repository corrupt.

The git-gc manual calls the risk low in practice and also says its two mitigations (keeping recently modified objects, and refreshing modification times on writes) fall short of a complete fix. Both statements hold, so on busy repositories I’d plan around the second one.

Shared object stores add another trap. If other repositories borrow from yours through .git/objects/info/alternates, the manual’s example passes their refs as extra heads so their objects count as reachable.

Skip those heads and prune sees only your own refs.

Git prune vs git remote prune, git fetch –prune and git worktree prune

YouTube player

Only git prune and git prune-packed delete objects. The rest of the prune family deletes refs or administrative files, so the right pick depends on what has gone stale.

CommandWhat it removesWhere it actsTypical trigger
git pruneUnreachable loose objects.git/objectsCleanup after resets and rebases
git prune-packedLoose objects already in a pack.git/objectsAfter a repack
git fetch –pruneRemote-tracking refs deleted on the remoterefs/remotes/<remote>Branches deleted upstream
git remote prune <remote>The same stale refs, without fetchingrefs/remotes/<remote>Cleanup without new data
git worktree pruneMetadata of missing linked worktrees$GIT\_DIR/worktreesWorktree folder deleted by hand

Some tutorials fold the remote-branch cleanup into the definition of git prune. Atlassian’s tutorial keeps them apart, and so does the git-prune manual, which describes object deletion only. Treat the manual as the authority.

Before it fetches anything, git fetch with –prune removes remote-tracking refs that no longer exist on the remote. Set fetch.prune globally or remote.<name>.prune per remote to make it automatic (git fetch manual, git-scm.com).

Tags need care. Tags fetched through default auto-following are not pruned, but an explicit tag refspec or --prune-tags makes Git delete local tags missing on the remote, including ones you created yourself.

git remote prune does the same ref cleanup without fetching new refs, and its –dry-run option lists what would go first.

git worktree prune removes the administrative files for linked worktrees whose folder is missing. Use git worktree list to see which entries are marked prunable, and git worktree lock to protect one on a drive that isn’t always mounted.

No single prune command covers objects, remote branches and worktrees. Match the command to the stale thing.

Common Questions About git prune

Does git prune delete my stashes?

Not while the entry exists. The newest stash lives in refs/stash and older ones in that ref’s reflog, so all of them stay reachable.

After git stash drop or git stash clear, the commits become unreachable and fall under pruning. The stash manual (git-scm.com) suggests listing them with git fsck –unreachable before that happens.

How does git gc –prune=now differ from git prune –expire=now?

Both delete unreachable loose objects regardless of age. git gc piles repacking, reflog expiry and worktree pruning onto the same pass, while git prune performs the object step alone.

Both also skip the grace period, so both carry the concurrency risk the git-gc manual describes for –prune=now.

When does git fetch end up pruning objects?

Only when automatic maintenance runs the gc task. By default fetch ends with git maintenance run –auto (–no-auto-maintenance disables it), and gc calls prune once loose objects pass gc.auto.

The geometric strategy skips prune and moves unreachable objects into a cruft pack during an all-into-one repack (git-maintenance manual, git-scm.com).

Does git prune need a network connection?

No. git prune reads local refs and the local object database, so it works offline.

git fetch –prune and git remote prune are different. They ask the remote which branches still exist before removing stale remote-tracking refs.

What to Run When git prune Frees Little

If git prune frees almost nothing, the repository’s weight sits in packs, and git repack -a -d is the next command. A size-pack value far above size in git count-objects -v confirms it.

  • Measure with git count-objects -v -H
  • Copy the repository, then run git repack -a -d
  • Re-measure before doing anything else

Measuring first matters because a full repack rewrites every object into one pack, and the git-maintenance manual (git-scm.com) says its gc task, which does exactly that, can be expensive on large repositories.

None of this touches reachable history. Objects a branch or tag reaches survive every prune and repack, so oversized old blobs stay however often either command runs.

As of the Git 2.56 documentation (September 2026), manual git maintenance run defaults to the geometric strategy, which handles unreachable objects through cruft packs.

Bogdan Sandu

Stay sharp. Ship better code.

Every week: one curated article, one tool worth knowing, one tip you can use tomorrow. No noise, no padding.