<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akinkunmi</title>
    <description>The latest articles on DEV Community by Akinkunmi (@akinkunmi006).</description>
    <link>https://dev.to/akinkunmi006</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F272377%2F91d0b14e-5a1a-4297-9813-0b00d67cb1dc.jpg</url>
      <title>DEV Community: Akinkunmi</title>
      <link>https://dev.to/akinkunmi006</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akinkunmi006"/>
    <language>en</language>
    <item>
      <title>GitDeclutter: know which local Git branches are safe to delete</title>
      <dc:creator>Akinkunmi</dc:creator>
      <pubDate>Sat, 15 Aug 2026 21:56:53 +0000</pubDate>
      <link>https://dev.to/akinkunmi006/gitdeclutter-know-which-local-git-branches-are-safe-to-delete-1k80</link>
      <guid>https://dev.to/akinkunmi006/gitdeclutter-know-which-local-git-branches-are-safe-to-delete-1k80</guid>
      <description>&lt;p&gt;A merged pull request does not mean the local branch on your laptop is empty.&lt;/p&gt;

&lt;p&gt;I kept learning that the hard way. GitHub showed the PR as merged. The remote branch was already gone. &lt;code&gt;git branch --merged&lt;/code&gt; looked clean enough. Then I found two commits that had never left my machine;  written after I clicked merge, or left behind by a squash.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git branch -D feature/foo&lt;/code&gt; deletes that branch. That is all it does. It does not tell you whether the work is gone, and it does not find the other twelve leftovers sitting next to it. You can pass more names, but you still have to choose them.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/kunmi02/git-declutter" rel="noopener noreferrer"&gt;GitDeclutter&lt;/a&gt;: a small Go CLI that finds stale local branches, tells you which ones are safe, and can delete that SAFE set in one pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Know what's safe to delete before you delete it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Official repo: &lt;a href="https://github.com/kunmi02/git-declutter" rel="noopener noreferrer"&gt;github.com/kunmi02/git-declutter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not the older Python project of the same name. That one turns a folder of copied files into a Git repo. This one classifies stale &lt;strong&gt;local branches&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode &lt;code&gt;git branch --merged&lt;/code&gt; misses
&lt;/h2&gt;

&lt;p&gt;Squash-and-merge is the usual trap. GitHub records a new commit on &lt;code&gt;main&lt;/code&gt;. Your local feature branch still has the original commits. Ancestry no longer matches, so a naive "is this merged?" check is either too aggressive or too timid.&lt;/p&gt;

&lt;p&gt;The other trap is quieter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR merged at C

A──B──C

Local branch later becomes:

A──B──C──D──E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pull request is done. Commits &lt;code&gt;D&lt;/code&gt; and &lt;code&gt;E&lt;/code&gt; may exist only on your laptop. Deleting the branch throws them away.&lt;/p&gt;

&lt;p&gt;GitDeclutter treats a merged PR as evidence, not as permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git declutter scan&lt;/code&gt; never deletes anything. It reads local Git history, remote-tracking state, and — when available — GitHub or GitLab pull request metadata. Then it puts every local branch in one of four buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SAFE&lt;/strong&gt; — strong evidence that deleting it will not lose unique work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REVIEW&lt;/strong&gt; — it looks stale, but the evidence is incomplete&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KEEP&lt;/strong&gt; — unique or active work is still there&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PROTECTED&lt;/strong&gt; — current branch, default branch, a worktree, or a configured pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;False negatives are acceptable. False positives are not. If it is unsure, it will not recommend automatic deletion.&lt;/p&gt;

&lt;p&gt;A scan looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git declutter scan

GitDeclutter
Repository: payments-api

SAFE TO REMOVE                                    12

✓ feature/oauth
  PR #418 merged · remote deleted · no local changes

NEEDS REVIEW                                      3

⚠ experiment/cache
  Remote deleted · no merged PR found

KEEP                                              4

✕ feature/refunds
  3 commits exist only locally

PROTECTED                                         3

🔒 main
🔒 release/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you disagree with a row, ask for the reasons:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git declutter why feature/refunds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get the signals, not a yes/no from a script you cannot audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup is optional, bulk, and recoverable
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git branch -D&lt;/code&gt; is a delete command. GitDeclutter is a decide-then-delete command.&lt;/p&gt;

&lt;p&gt;When you are ready, &lt;code&gt;git declutter clean&lt;/code&gt; lists every &lt;strong&gt;SAFE&lt;/strong&gt; branch and lets you remove them in one pass — interactively, or all at once with &lt;code&gt;--safe-only --yes&lt;/code&gt;. That is the bulk path: you are not typing twelve branch names into &lt;code&gt;git branch -D&lt;/code&gt;. &lt;strong&gt;REVIEW&lt;/strong&gt;, &lt;strong&gt;KEEP&lt;/strong&gt;, and &lt;strong&gt;PROTECTED&lt;/strong&gt; never go on that path.&lt;/p&gt;

&lt;p&gt;Before it deletes, it stores a recovery ref (&lt;code&gt;refs/git-declutter/recovery/...&lt;/code&gt;) for 30 days by default. &lt;code&gt;git declutter restore &amp;lt;branch&amp;gt;&lt;/code&gt; brings it back. Use &lt;code&gt;--permanent&lt;/code&gt; only when you mean it.&lt;/p&gt;

&lt;p&gt;It also re-checks the branch immediately before deletion. If the tip moved after the scan, that branch is skipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;You need Git. You do not need Go after the binary is on your &lt;code&gt;PATH&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/kunmi02/git-declutter@latest
git declutter scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GOBIN&lt;/code&gt; (or &lt;code&gt;$(go env GOPATH)/bin&lt;/code&gt;) must be on your &lt;code&gt;PATH&lt;/code&gt;. Prebuilt macOS, Linux, and Windows archives will live on &lt;a href="https://github.com/kunmi02/git-declutter/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt; once version tags are published.&lt;/p&gt;

&lt;p&gt;GitHub token via &lt;code&gt;gh&lt;/code&gt; or &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;, GitLab via &lt;code&gt;glab&lt;/code&gt; or &lt;code&gt;GITLAB_TOKEN&lt;/code&gt;. Offline mode exists if you do not want provider calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git declutter scan &lt;span class="nt"&gt;--offline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no GitDeclutter account and no telemetry. Provider APIs are used only for repo metadata (owner, branch names, SHAs, PR state) — not for source, diffs, or commit messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I did not just wrap &lt;code&gt;git branch -D&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git branch -D&lt;/code&gt; is the right tool when you already know the name and accept the loss. It will not scan the repo, classify leftovers, or give you a recoverable bulk cleanup of only the stale ones.&lt;/p&gt;

&lt;p&gt;The gap is the decision, then the list. I wanted something that would rather leave a stale branch sitting there than pretend a squash merged, locally-extended branch was empty and, when the evidence is strong, let you clear the SAFE ones in one go.&lt;/p&gt;

&lt;p&gt;If that is a problem you have, start with a scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git declutter scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source, issues, and install: &lt;a href="https://github.com/kunmi02/git-declutter" rel="noopener noreferrer"&gt;https://github.com/kunmi02/git-declutter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>git</category>
      <category>go</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
