{"id":1220,"date":"2026-08-18T16:24:56","date_gmt":"2026-08-18T16:24:56","guid":{"rendered":"https:\/\/techno.slomka.biz\/?p=1220"},"modified":"2026-08-18T16:28:21","modified_gmt":"2026-08-18T16:28:21","slug":"git-switch-and-git-restore-the-split-you-probably-missed-as-i-did","status":"publish","type":"post","link":"https:\/\/techno.slomka.biz\/?p=1220","title":{"rendered":"git switch and git restore: the split you probably missed (as I did)"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">git checkout has been split into git switch and git restore.<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>git checkout main<\/code> and <code>git switch main <\/code>do exactly the same thing. So why does the second one exist?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because <code>checkout <\/code>is two commands wearing one name. It moves <code>HEAD <\/code>to another branch, and it overwrites files in your working tree from some commit. Those operations have nothing in common except that both touch files on disk \u2014 and one of them is destructive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That collision produces a genuinely bad failure mode:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#EEFFFF;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#304047;color:#d5ffff\">Bash<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#EEFFFF;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>git checkout main        # harmless: switch branch\ngit checkout config.py   # silently throws away your uncommitted edits<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki material-theme\" style=\"background-color: #263238\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #FFCB6B\">git<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">checkout<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">main<\/span><span style=\"color: #EEFFFF\">        <\/span><span style=\"color: #546E7A; font-style: italic\"># harmless: switch branch<\/span><\/span>\n<span class=\"line\"><span style=\"color: #FFCB6B\">git<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">checkout<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">config.py<\/span><span style=\"color: #EEFFFF\">   <\/span><span style=\"color: #546E7A; font-style: italic\"># silently throws away your uncommitted edits<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Same verb, same shape, no confirmation, no reflog entry to crawl back through. The working-tree copy is simply gone. And when a branch and a path share a name, you get to remember which side of a <code>--<\/code> separator each argument belongs on.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The split<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Git 2.23 (August 2019) carved <code>checkout <\/code>into two purpose-built commands: <code>switch <\/code>for refs, <code>restore <\/code>for file contents. For six years they carried a <code>THIS <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">COMMAND IS EXPERIMENTAL<\/mark><\/code> banner in the man pages, which is why plenty of people tried them once and went back. That banner is gone: <strong>Git 2.51 (August 2025) declared both stable<\/strong>, with a command-line interface guaranteed to stay backwards compatible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you last looked in 2019 and filed them under &#8220;wait and see&#8221; \u2014 the waiting is over. \ud83e\udd29<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Old<\/th><th>New<\/th><th>Job<\/th><\/tr><\/thead><tbody><tr><td><code>git checkout main<\/code><\/td><td><code>git switch main<\/code><\/td><td>switch branch<\/td><\/tr><tr><td><code>git checkout -b feat<\/code><\/td><td><code>git switch -c feat<\/code><\/td><td>create and switch<\/td><\/tr><tr><td><code>git checkout -B feat<\/code><\/td><td><code>git switch -C feat<\/code><\/td><td>create or reset, then switch<\/td><\/tr><tr><td><code>git checkout -<\/code><\/td><td><code>git switch -<\/code><\/td><td>back to previous branch<\/td><\/tr><tr><td><code>git checkout abc123<\/code><\/td><td><code>git switch --detach abc123<\/code><\/td><td>inspect a commit<\/td><\/tr><tr><td><code>git checkout -- file.py<\/code><\/td><td><code>git restore file.py<\/code><\/td><td>discard working-tree changes<\/td><\/tr><tr><td><code>git checkout abc123 -- file.py<\/code><\/td><td><code>git restore --source=abc123 file.py<\/code><\/td><td>pull a file out of a commit<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">What you actually gain<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Detached HEAD becomes deliberate.<\/strong> <code>git checkout v1.4.2<\/code> drops you into detached <code>HEAD<\/code> with a paragraph of explanation you&#8217;ve long since learned to scroll past.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#EEFFFF;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#304047;color:#d5ffff\">Bash<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#EEFFFF;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>git switch v1.4.2<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki material-theme\" style=\"background-color: #263238\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #FFCB6B\">git<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">switch<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">v1.4.2<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">refuses:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#EEFFFF;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#304047;color:#d5ffff\">YAML<\/span><span role=\"button\" tabindex=\"0\" style=\"color:#EEFFFF;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" aria-hidden=\"true\" readonly>fatal: a branch is expected, got tag<\/textarea><\/pre><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki material-theme\" style=\"background-color: #263238\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #F07178\">fatal<\/span><span style=\"color: #89DDFF\">:<\/span><span style=\"color: #EEFFFF\"> <\/span><span style=\"color: #C3E88D\">a branch is expected, got tag<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">You have to write <code>--detach<\/code> to say you meant it. That single change removes the most common way beginners lose commits \u2014 and the most common way the rest of us end up committing onto nothing at 2 a.m.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Destructive actions are spelled out.<\/strong> <code>switch<\/code>&#8216;s forcing flag is <code>--discard-changes<\/code>. The <code>-f<\/code> alias still works, but the long form says what it costs, and that&#8217;s the one that ends up in your shell history and your team&#8217;s runbook.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>switch<\/code> cannot touch files.<\/strong> It takes no pathspec at all. There is no argument you can typo into a file-clobbering command.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>restore<\/code> separates the two places a change lives.<\/strong> <code>--worktree<\/code> (the default), <code>--staged<\/code>, or both. <code>checkout<\/code> conflated the index and the working tree; <code>reset<\/code> was the other half of the answer. Now one command covers restoring content, and its scope is explicit.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">No, <code>checkout<\/code> isn&#8217;t going anywhere<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Git project has been clear that <code>checkout<\/code> stays. Every tutorial, script, CI pipeline, and muscle memory on earth depends on it, and nothing is served by breaking that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But muscle memory is exactly the argument for switching. <code>checkout<\/code> reads the same whether it&#8217;s about to change your branch or delete your afternoon&#8217;s work. <code>switch<\/code> and <code>restore<\/code> tell you which one you asked for, before you press Enter.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Sources: <a href=\"https:\/\/git-scm.com\/docs\/git-switch\">git-switch documentation<\/a>, <a href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-51\/\">Highlights from Git 2.51<\/a>, <a href=\"https:\/\/github.blog\/open-source\/git\/highlights-from-git-2-23\/\">Highlights from Git 2.23<\/a><\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>git checkout has been split into git switch and git restore. git checkout main and git switch main do exactly the same thing. So why does the second one exist? Because checkout is two commands wearing one name. It moves HEAD to another branch, and it overwrites files in your working tree from some commit. Those operations have nothing in common except that both touch files on disk \u2014 and one of them is destructive. That collision produces a genuinely bad failure mode: Same verb, same shape, no confirmation, no reflog entry to crawl back through. The working-tree copy is simply gone. And when a branch and a path share a name, you get to remember which side of a &#8212; separator each argument belongs on. The split Git 2.23 (August 2019) carved checkout into two purpose-built commands: switch for refs, restore for file contents. For six years they carried a THIS COMMAND IS EXPERIMENTAL banner in the man pages, which is why plenty of people tried them once and went back. That banner is gone: Git 2.51 (August 2025) declared both stable, with a command-line interface guaranteed to stay backwards compatible. If you last looked in 2019 and filed them under &#8220;wait and see&#8221; \u2014 the waiting is over. \ud83e\udd29 Old New Job git checkout main git switch main switch branch git checkout -b feat git switch -c feat create and switch git checkout -B feat git switch -C feat create or reset, then switch git checkout &#8211; git switch &#8211; back to previous branch git checkout abc123 git switch &#8211;detach abc123 inspect a commit git checkout &#8212; file.py git restore file.py discard working-tree changes git checkout abc123 &#8212; file.py git restore &#8211;source=abc123 file.py pull a file out of a commit What you actually gain Detached HEAD becomes deliberate. git checkout v1.4.2 drops you into detached HEAD with a paragraph of explanation you&#8217;ve long since learned to scroll past. refuses: You have to write &#8211;detach to say you meant it. That single change removes the most common way beginners lose commits \u2014 and the most common way the rest of us end up committing onto nothing at 2 a.m. Destructive actions are spelled out. switch&#8216;s forcing flag is &#8211;discard-changes. The -f alias still works, but the long form says what it costs, and that&#8217;s the one that ends up in your shell history and your team&#8217;s runbook. switch cannot touch files. It takes no pathspec at all. There is no argument you can typo into a file-clobbering command. restore separates the two places a change lives. &#8211;worktree (the default), &#8211;staged, or both. checkout conflated the index and the working tree; reset was the other half of the answer. Now one command covers restoring content, and its scope is explicit. No, checkout isn&#8217;t going anywhere The Git project has been clear that checkout stays. Every tutorial, script, CI pipeline, and muscle memory on earth depends on it, and nothing is served by breaking that. But muscle memory is exactly the argument for switching. checkout reads the same whether it&#8217;s about to change your branch or delete your afternoon&#8217;s work. switch and restore tell you which one you asked for, before you press Enter. Sources: git-switch documentation, Highlights from Git 2.51, Highlights from Git 2.23<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[62],"class_list":["post-1220","post","type-post","status-publish","format-standard","hentry","category-git","tag-git"],"_links":{"self":[{"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=\/wp\/v2\/posts\/1220","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1220"}],"version-history":[{"count":11,"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=\/wp\/v2\/posts\/1220\/revisions"}],"predecessor-version":[{"id":1231,"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=\/wp\/v2\/posts\/1220\/revisions\/1231"}],"wp:attachment":[{"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1220"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1220"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/techno.slomka.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1220"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}