<?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: Rajat</title>
    <description>The latest articles on DEV Community by Rajat (@saboorajat).</description>
    <link>https://dev.to/saboorajat</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3976165%2F722a3ba3-f849-4641-96d0-266d7301244c.png</url>
      <title>DEV Community: Rajat</title>
      <link>https://dev.to/saboorajat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saboorajat"/>
    <language>en</language>
    <item>
      <title>git reflog: the undo button you didn't know you had</title>
      <dc:creator>Rajat</dc:creator>
      <pubDate>Tue, 09 Jun 2026 14:20:05 +0000</pubDate>
      <link>https://dev.to/saboorajat/git-reflog-the-undo-button-you-didnt-know-you-had-5078</link>
      <guid>https://dev.to/saboorajat/git-reflog-the-undo-button-you-didnt-know-you-had-5078</guid>
      <description>&lt;p&gt;You've just run &lt;code&gt;git reset --hard HEAD~5&lt;/code&gt;. The terminal goes quiet. Your stomach drops.&lt;/p&gt;

&lt;p&gt;Five commits — three hours of work — are gone. You didn't push them. You didn't open a PR. Your editor doesn't have them in its undo buffer anymore. The Git object database has freed the references and is going to garbage-collect them eventually.&lt;/p&gt;

&lt;p&gt;You can get every one of them back, perfectly, in about forty seconds.&lt;/p&gt;

&lt;p&gt;This is the most important command in Git that almost nobody learns until they've already lost work twice. It's called &lt;code&gt;git reflog&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What reflog actually is
&lt;/h2&gt;

&lt;p&gt;A "ref" in Git is just a pointer. &lt;code&gt;HEAD&lt;/code&gt; is a ref. Every branch name is a ref. Every tag is a ref. When you make a commit, Git updates whatever ref you're currently sitting on to point at the new commit's SHA. When you switch branches, the &lt;code&gt;HEAD&lt;/code&gt; ref moves to the tip of the new branch.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;reflog&lt;/strong&gt; is a journal of every ref movement. Every time &lt;code&gt;HEAD&lt;/code&gt; shifts — every commit, every checkout, every merge, every reset, every rebase step — Git writes a line into the reflog with the previous SHA, the new SHA, and a short reason like &lt;code&gt;"commit"&lt;/code&gt; or &lt;code&gt;"reset: moving to HEAD~5"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That journal lives in &lt;code&gt;.git/logs/HEAD&lt;/code&gt; and &lt;code&gt;.git/logs/refs/heads/&amp;lt;branch&amp;gt;&lt;/code&gt;. It's local to your machine. It's not pushed anywhere. It's not part of the repo. Nobody else can see it.&lt;/p&gt;

&lt;p&gt;And, critically — and this is the part that saves your three hours of work — the &lt;strong&gt;commits the reflog references are not deleted until garbage collection runs&lt;/strong&gt;, which by default is &lt;strong&gt;30 days from now&lt;/strong&gt;. Even when those commits are no longer reachable from any branch, no longer in any working tree, no longer mentioned by any tag — they sit in &lt;code&gt;.git/objects/&lt;/code&gt;, untouched, waiting for you to point a branch at them.&lt;/p&gt;

&lt;p&gt;A failed &lt;code&gt;reset --hard&lt;/code&gt; doesn't delete commits. It just unparks them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The commands you actually need
&lt;/h2&gt;

&lt;p&gt;There are only three. Memorise them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog
git reflog show &amp;lt;branch&amp;gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;sha-or-ref&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through a recovery from start to finish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Look at the reflog
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;You'll get something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;a1b2c3d HEAD@{0}: reset: moving to HEAD~5
9f4e2c8 HEAD@{1}: commit: feat: wire payment confirmation email
6c1a5e3 HEAD@{2}: commit: feat: add payment confirmation route
3d8f2b1 HEAD@{3}: commit: chore: bump stripe SDK
0e7a9d4 HEAD@{4}: commit: refactor: extract email queue
b4c8e2f HEAD@{5}: commit: feat: add email queue worker
2a1b3c4 HEAD@{6}: checkout: moving from main to feature/payments
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read this top-down as "what HEAD just did, in reverse order". The most recent thing is &lt;code&gt;HEAD@{0}&lt;/code&gt; — the &lt;code&gt;reset --hard&lt;/code&gt; we just regretted. The five commits we lost are right there at &lt;code&gt;HEAD@{1}&lt;/code&gt; through &lt;code&gt;HEAD@{5}&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Move HEAD back
&lt;/h3&gt;

&lt;p&gt;The simplest recovery: put your branch back where it was before the reset. The reflog tells you the SHA of the commit you regret leaving — it's the entry that says &lt;code&gt;commit:&lt;/code&gt; immediately above the bad &lt;code&gt;reset:&lt;/code&gt; line. In the example above, that's &lt;code&gt;9f4e2c8&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;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; 9f4e2c8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your branch is now at &lt;code&gt;9f4e2c8&lt;/code&gt; again. All five commits are reachable from the tip. &lt;code&gt;git log&lt;/code&gt; shows them. Your working tree matches commit &lt;code&gt;9f4e2c8&lt;/code&gt;. Three hours of work, restored.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four scenarios where reflog saves you
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Hard reset you regret
&lt;/h3&gt;

&lt;p&gt;The scenario above. Either you reset more than you meant to, or you reset and then realised "wait, I needed that branch state for something."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog                           &lt;span class="c"&gt;# find the SHA you want back&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;sha&amp;gt;               &lt;span class="c"&gt;# move HEAD there&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Deleted a branch with unmerged work
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="nt"&gt;-D&lt;/span&gt; feature/payments
&lt;span class="c"&gt;# (panic)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Branches are just refs. Deleting one removes the pointer but &lt;strong&gt;leaves the commits intact&lt;/strong&gt; for the reflog window. Recovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog | &lt;span class="nb"&gt;grep &lt;/span&gt;payments           &lt;span class="c"&gt;# find the last SHA the branch was at&lt;/span&gt;
git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature/payments &amp;lt;sha&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Rebase ate someone else's commits
&lt;/h3&gt;

&lt;p&gt;You ran &lt;code&gt;git rebase main&lt;/code&gt; and resolved a conflict by picking your side. Now you realise that side dropped Alice's commits that were on the branch before you started.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Find the entry that says &lt;code&gt;rebase: start&lt;/code&gt; or &lt;code&gt;rebase (start)&lt;/code&gt;. Whatever HEAD was at &lt;em&gt;just before&lt;/em&gt; the rebase began is your pre-rebase state. Reset to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Force-pushed over a teammate's work
&lt;/h3&gt;

&lt;p&gt;Your teammate pushed something. You force-pushed your version over it without pulling first. Their commits are gone from the remote — and from your local, because you &lt;code&gt;git push --force&lt;/code&gt;'d.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On their machine:&lt;/span&gt;
git reflog | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;                &lt;span class="c"&gt;# they can still see their commits&lt;/span&gt;
git push &lt;span class="nt"&gt;--force&lt;/span&gt; origin feature      &lt;span class="c"&gt;# re-publish their state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this exact reason: &lt;strong&gt;never &lt;code&gt;git push --force&lt;/code&gt;. Use &lt;code&gt;git push --force-with-lease&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing reflog won't save you from
&lt;/h2&gt;

&lt;p&gt;Reflog tracks ref movement. It does &lt;strong&gt;not&lt;/strong&gt; track changes to files you never committed.&lt;/p&gt;

&lt;p&gt;If you ran &lt;code&gt;git checkout -- &amp;lt;file&amp;gt;&lt;/code&gt; (or its modern equivalent &lt;code&gt;git restore &amp;lt;file&amp;gt;&lt;/code&gt;) on a file you'd been editing but never staged, that work is lost. The file's pre-edit state replaced your edits in the working tree, and nothing was ever written to the object database. The reflog can't help.&lt;/p&gt;

&lt;p&gt;The rule: &lt;strong&gt;anything Git ever assigned a SHA to is recoverable for ~30 days. Anything Git never saw is gone the moment you tell it to discard.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How long do I actually have?
&lt;/h2&gt;

&lt;p&gt;By default, two windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reachable but unreferenced commits&lt;/strong&gt; (e.g., dangling after a reset): &lt;strong&gt;90 days&lt;/strong&gt; before &lt;code&gt;git gc&lt;/code&gt; is allowed to remove them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unreachable commits&lt;/strong&gt; (orphan branches, dropped stashes): &lt;strong&gt;30 days&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also extend these in &lt;code&gt;~/.gitconfig&lt;/code&gt; if you want longer recovery windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[gc]&lt;/span&gt;
  &lt;span class="py"&gt;reflogExpire&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;365.days&lt;/span&gt;
  &lt;span class="py"&gt;reflogExpireUnreachable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;365.days&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I run with 365 on personal machines. The disk cost is negligible, and the peace of mind is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical habit
&lt;/h2&gt;

&lt;p&gt;I keep this command aliased in my &lt;code&gt;.gitconfig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[alias]&lt;/span&gt;
  &lt;span class="py"&gt;rl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;reflog --date=relative&lt;/span&gt;
  &lt;span class="py"&gt;oops&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;!git reset --hard HEAD@{1}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first gives me a human-friendly reflog with relative dates. The second is the literal undo button: it moves HEAD back to where it was one operation ago, no matter what that operation was. &lt;code&gt;git oops&lt;/code&gt; is the muscle memory I want when I realise I just did something wrong.&lt;/p&gt;

&lt;p&gt;Try this once today: do a &lt;code&gt;git reset --hard HEAD~1&lt;/code&gt; on a throwaway branch, then recover with &lt;code&gt;git oops&lt;/code&gt;. The first time you do it deliberately, the panic-flavored memory of doing it accidentally evaporates.&lt;/p&gt;

&lt;p&gt;You don't need to learn Git's internals to ship code. But you do need to know the reflog exists. The next three-hour mistake you save yourself from will make it the highest-ROI five minutes of Git education you ever have.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I wrote this as part of &lt;a href="https://www.gitflow.dev" rel="noopener noreferrer"&gt;gitflow.dev&lt;/a&gt; — interactive Git training with a real Git engine in the browser. Free, no sign-up to read. If you found this useful, the &lt;a href="https://www.gitflow.dev/scenarios/lost-commits-reflog" rel="noopener noreferrer"&gt;reflog recovery scenario&lt;/a&gt; lets you try the recovery in a live terminal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
