Home / Guides / Find unpushed Git commits in all your repos

Find unpushed Git commits, local branches and stashes in all your repos

Git only protects work that is committed and pushed. Anything else lives on one disk. If you have more than a few repositories, you cannot remember which ones are in that state. This guide gives you the commands, a script that checks them all, and the limits of doing it that way.

Short answer.

For one repository, git status, git stash list and git log --branches --not --remotes tell you everything that is not safely on the server. For many repositories, loop over them with the script below. A script only tells you when you remember to run it; a tray app such as GitRepoWatcher checks on a schedule and warns you.

The four kinds of work that are not safe

  1. Uncommitted changes. Modified files, and new files that Git has not been told about (untracked).
  2. Unpushed commits. Commits on a branch that is ahead of its remote branch.
  3. Local-only branches. Branches that were never pushed, so they have no remote branch to be ahead of.
  4. Stashes. Work parked with git stash and then forgotten. A stash is never pushed.

The commands, for one repository

git status --porcelain
git stash list
git log --branches --not --remotes --oneline
git for-each-ref refs/heads --format="%(refname:short)  upstream=%(upstream:short)  %(upstream:track)"
  • git status --porcelain prints one line per changed or untracked file and nothing when the working tree is clean. Add -uno to ignore untracked files.
  • git stash list prints one line per stash.
  • git log --branches --not --remotes --oneline prints every commit that is on some local branch and on no remote branch. That covers unpushed commits and local-only branches in one list.
  • git for-each-ref with %(upstream:track) shows, per branch, how many commits it is ahead of or behind its remote branch, and shows an empty upstream for a branch that was never pushed.

A script that checks every repository under a folder

This PowerShell script finds each repository under a root folder and prints a table of the ones that need attention: how many files are uncommitted, how many commits are unpushed, which branches exist only locally, and how many stashes there are. Repositories that are fully pushed are left out. It only reads; it never changes a repository.

# Lists Git repos under a folder that have uncommitted files, commits not pushed,
# branches with no upstream, or stashes. Read-only.
param([string]$Root = 'C:\Code')

Get-ChildItem -Path $Root -Directory -Recurse -Force -Filter .git -ErrorAction SilentlyContinue |
    ForEach-Object {
        $repo = $_.Parent.FullName
        $dirty   = @(git -C $repo status --porcelain).Count
        $stashes = @(git -C $repo stash list).Count
        $ahead   = 0
        $noUpstream = @()
        foreach ($b in git -C $repo for-each-ref refs/heads --format='%(refname:short)|%(upstream:short)|%(upstream:track)') {
            $name, $up, $track = $b -split '\|', 3
            if (-not $up) { $noUpstream += $name }
            elseif ($track -match 'ahead (\d+)') { $ahead += [int]$Matches[1] }
        }
        if ($dirty -or $stashes -or $ahead -or $noUpstream) {
            [pscustomobject]@{
                Repo = $repo; Uncommitted = $dirty; Unpushed = $ahead
                LocalOnlyBranches = $noUpstream -join ', '; Stashes = $stashes
            }
        }
    } | Format-Table -AutoSize -Wrap

Save it as Find-UnpushedGit.ps1 and run .\Find-UnpushedGit.ps1 -Root D:\Code. Point it at your code folder rather than a whole drive, because it searches every folder below the root. Git must be installed and on the PATH.

What a script cannot do

  • It reflects the last fetch. "Ahead of the remote" is measured against what the repository last knew about the remote. A colleague's newer push, or a branch you deleted on the server, only shows after a git fetch. The same is true of every tool.
  • It runs only when you run it. Forgotten work is forgotten because you did not think to check. A scheduled task can run the script, but it cannot show you the result.
  • It shows counts, not detail. To see which files and commits, you open each repository.
  • A repository with no remote at all lists every branch as local-only, because none of them has anywhere to be pushed.
  • It skips repositories where .git is a file, such as linked worktrees and submodules, because it looks for a .git folder.
  • It does not alert anyone. There is no tray icon, no notification, and no check between runs.

Questions

How do I see unpushed commits on every branch?

Run git log --branches --not --remotes --oneline. It lists every commit that is on a local branch and on no remote branch, so it covers unpushed commits and branches you never pushed.

Does a stash get pushed?

No. A stash stays in the local repository only, so a stash is lost if the disk is. Check for them with git stash list.

Why does it say nothing is unpushed when a colleague has pushed?

Git compares your branch with the remote branch as it was at the last fetch. Run git fetch to refresh it. GitRepoWatcher's unpushed counts are measured the same way, against the last time each repository fetched from its server.

Does GitRepoWatcher send my code or repository names anywhere?

No. It reads your repositories on your own computer only. The only network traffic is the license check described in our privacy policy.

Do I need Git installed for GitRepoWatcher?

Yes. It uses the Git that is already installed on your PC to read each repository.

Related

An unhandled error has occurred. Reload 🗙