<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="../feed.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">

<channel>
<title>Susam's Git Pages</title>
<link>https://susam.net/tag/git.html</link>
<atom:link rel="self" type="application/rss+xml" href="https://susam.net/tag/git.xml"/>
<description>Feed for Susam's Git Pages</description>

<item>
<title>Multiple URLs in Git Remote</title>
<link>https://susam.net/multiple-urls-in-git-remote.html</link>
<guid isPermaLink="false">muigr</guid>
<pubDate>Wed, 29 Apr 2026 00:00:00 +0000</pubDate>
<description>
<![CDATA[
<p>
  Typically a Git remote contains a single URL.  For example, when we
  clone a repository, a remote named <code>origin</code> is
  automatically created and its URL is set to the location of the
  upstream repository.  For example:
</p>
<pre>
<samp>$ <kbd>git remote -v</kbd>
origin  https://codeberg.org/spxy/spica.git (fetch)
origin  https://codeberg.org/spxy/spica.git (push)
$ <kbd>sed '/remote/,$!d' .git/config</kbd>
[remote "origin"]
        url = https://codeberg.org/spxy/spica.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
        remote = origin
        merge = refs/heads/main</samp>
</pre>
<p>
  A perhaps less known detail is that we can set multiple URLs for a
  remote.  For example:
</p>
<pre>
<samp>$ <kbd>git remote set-url origin --add https://github.com/spxy/spica.git</kbd>
$ <kbd>git remote -v</kbd>
origin  https://codeberg.org/spxy/spica.git (fetch)
origin  https://codeberg.org/spxy/spica.git (push)
origin  https://github.com/spxy/spica.git (push)
$ <kbd>sed '/remote/,$!d' .git/config</kbd>
[remote "origin"]
        url = https://codeberg.org/spxy/spica.git
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = https://github.com/spxy/spica.git
[branch "main"]
        remote = origin
        merge = refs/heads/main</samp>
</pre>
<p>
  As evident from the above output, with multiple URLs for the same
  remote, the first one becomes the fetch URL whereas all URLs become
  push URLs.  For example:
</p>
<pre>
<samp>$ <kbd>git pull</kbd>
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (3/3), 327 bytes | 109.00 KiB/s, done.
From https://codeberg.org/spxy/spica
   d473dde..31db503  main       -&gt; origin/main
Updating d473dde..31db503
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
$ <kbd>git log --oneline</kbd>
31db503 (HEAD -&gt; main, origin/main, origin/HEAD) Explain that Spica is a binary star system
d473dde Create README.md</samp>
</pre>
<p>
  The output above confirms that the fetch was performed from the
  first remote URL.  Although the output shows that
  <code>origin</code> contains commit 31db503, it is possible
  that not all remote locations for <code>origin</code> have received
  this commit yet.  Since we have not configured a
  <code>pushurl</code>, pushes are sent to all remote URLs by default.
  For example:
</p>
<pre>
<samp>$ <kbd>echo 'It is about 250 light years away from the Sun.' &gt;&gt; README.md</kbd>
$ <kbd>git add README.md</kbd>
$ <kbd>git commit -m 'Mention distance from the Sun'</kbd>
[main 816998d] Mention distance from the Sun
 1 file changed, 1 insertion(+)
$ <kbd>git push</kbd>
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 10 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 325 bytes | 325.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To https://codeberg.org/spxy/spica.git
   31db503..816998d  main -&gt; main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 10 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (9/9), 791 bytes | 791.00 KiB/s, done.
Total 9 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/spxy/spica.git
 * [new branch]      main -&gt; main
$ <kbd>git log --oneline</kbd>
816998d (HEAD -&gt; main, origin/main, origin/HEAD) Mention distance from the Sun
31db503 Explain that Spica is a binary star system
d473dde Create README.md</samp>
</pre>
<p>
  A <code>pushurl</code> can be set as follows:
</p>
<pre>
<samp>$ <kbd>git remote set-url --push origin https://github.com/spxy/spica.git</kbd>
$ <kbd>git remote -v</kbd>
origin  https://codeberg.org/spxy/spica.git (fetch)
origin  https://github.com/spxy/spica.git (push)
$ <kbd>sed '/remote/,$!d' .git/config</kbd>
[remote "origin"]
        url = https://codeberg.org/spxy/spica.git
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = https://github.com/spxy/spica.git
        pushurl = https://github.com/spxy/spica.git
[branch "main"]
        remote = origin
        merge = refs/heads/main</samp>
</pre>
<p>
  When one or more <code>pushurl</code> locations are set, pushes are
  sent to only those locations.  Fetches continue to occur from the
  first URL as before.
</p>
<pre>
<samp>$ <kbd>echo 'Its two stars orbit each other roughly every four days.' &gt;&gt; README.md</kbd>
$ <kbd>git add README.md</kbd>
$ <kbd>git commit -m 'Mention the four-day orbital period'</kbd>
[main 2e9f4e8] Mention the four-day orbital period
 1 file changed, 1 insertion(+)
$ <kbd>git push</kbd>
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 10 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 342 bytes | 342.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/spxy/spica.git
   816998d..2e9f4e8  main -&gt; main
$ <kbd>git log --oneline</kbd>
2e9f4e8 (HEAD -&gt; main, origin/main, origin/HEAD) Mention the four-day orbital period
816998d Mention distance from the Sun
31db503 Explain that Spica is a binary star system
d473dde Create README.md</samp>
</pre>
<p>
  Note that in this case, the new commit has been pushed only to the
  second remote URL.  If we perform a pull, Git will fetch changes
  from the first remote URL and will move <code>origin/main</code>
  back to the latest commit available there.  For example:
</p>
<pre>
<samp>$ <kbd>git pull</kbd>
From https://codeberg.org/spxy/spica
 + 2e9f4e8...816998d main       -&gt; origin/main  (forced update)
Already up to date.
$ <kbd>git log --oneline</kbd>
2e9f4e8 (HEAD -&gt; main) Mention the four-day orbital period
816998d (origin/main, origin/HEAD) Mention distance from the Sun
31db503 Explain that Spica is a binary star system
d473dde Create README.md</samp>
</pre>
<p>
  For this reason, configuring a separate <code>pushurl</code> should
  be done with care.  It is useful only in a narrow range of scenarios
  such as fetching from a primary repository that we want to treat as
  read-only while pushing changes to a mirror.
</p>
<p>
  The difference between <code>pushurl</code> and a normal remote URL
  is explained in <code>man git-fetch</code> as well as <code>man
  git-push</code> of Git 2.54.0 as follows:
</p>
<blockquote>
  The &lt;pushurl&gt; is used for pushes only.  It is optional and
  defaults to &lt;URL&gt;.  Pushing to a remote affects all defined
  pushurls or all defined urls if no pushurls are defined.  Fetch,
  however, will only fetch from the first defined url if multiple urls
  are defined.
</blockquote>
<!-- ### -->
<p>
  <a href="https://susam.net/multiple-urls-in-git-remote.html">Read on website</a> |
  <a href="https://susam.net/tag/git.html">#git</a> |
  <a href="https://susam.net/tag/technology.html">#technology</a>
</p>
]]>
</description>
</item>
<item>
<title>Git Checkout, Reset and Restore</title>
<link>https://susam.net/git-checkout-reset-restore.html</link>
<guid isPermaLink="false">gcrrp</guid>
<pubDate>Thu, 12 Mar 2026 00:00:00 +0000</pubDate>
<description>
<![CDATA[
<p>
  I have always used the <code>git checkout</code> and <code>git
  reset</code> commands to reset my working tree or index but since
  Git 2.23 there has been a <code>git restore</code> command available
  for these purposes.  In this post, I record how some of the 'older'
  commands I use map to the new ones.  Well, the new commands aren't
  exactly new since Git 2.23 was released in 2019, so this post is
  perhaps six years too late.  Even so, I want to write this down for
  future reference.  It is worth noting that the old and new commands
  are not always equivalent.  I'll talk more about this briefly as we
  discuss the commands.  However, they can be used to perform similar
  tasks.  Some of these tasks are discussed below.
</p>
<h2 id="contents">Contents<a href="#contents"></a></h2>
<ul>
  <li><a href="#experimental-setup">Experimental Setup</a></li>
  <li><a href="#reset-working-directory">Reset the Working Tree</a></li>
  <li><a href="#reset-index">Reset the Index</a></li>
  <li><a href="#reset-working-directory-and-index">Reset the Working Tree and Index</a></li>
  <li><a href="#summary">Summary</a></li>
</ul>
<h2 id="experimental-setup">Experimental Setup<a href="#experimental-setup"></a></h2>
<p>
  To experiment quickly, we first create an example Git repository.
</p>
<pre><code>mkdir foo/; cd foo/; touch a b c
git init; git add a b c; git commit -m hello</code></pre>
<p>
  Now we make changes to the files and stage some of the changes.  We
  then add more unstaged changes to one of the staged files.
</p>
<pre><code>date | tee a b c d; git add a b d; echo &gt; b</code></pre>
<p>
  At this point, the working tree and index look like this:
</p>
<pre><samp>$ <kbd>git status</kbd>
On branch main
Changes to be committed:
  (use "git restore --staged &lt;file&gt;..." to unstage)
        <span class="c2">modified:   a
        modified:   b
        new file:   d</span>

Changes not staged for commit:
  (use "git add &lt;file&gt;..." to update what will be committed)
  (use "git restore &lt;file&gt;..." to discard changes in working directory)
        <span class="c4">modified:   b
        modified:   c</span></samp></pre>
<p>
  File <code>a</code> has staged changes.  File <code>b</code> has
  both staged and unstaged changes.  File <code>c</code> has only
  unstaged changes.  File <code>d</code> is a new staged file.  In
  each experiment below, we will work with this setup.
</p>
<p>
  All results discussed in this post were obtained using Git 2.47.3 on
  Debian 13.2 (Trixie).
</p>
<h2 id="reset-working-directory">Reset the Working Tree<a href="#reset-working-directory"></a></h2>
<p>
  As a reminder, we will always use the following command between
  experiments to ensure that we restore the experimental setup each
  time:
</p>
<pre><code>date | tee a b c d; git add a b d; echo &gt; b</code></pre>
<p>
  To discard the changes in the working tree and reset the files in
  the working tree from the index, I typically run:
</p>
<pre><code>git checkout .</code></pre>
<p>
  However, the modern way to do this is to use the following command:
</p>
<pre><code>git restore .</code></pre>
<p>
  Both commands leave the working tree and the index in the following
  state:
</p>
<pre><samp>$ <kbd>git status</kbd>
On branch main
Changes to be committed:
  (use "git restore --staged &lt;file&gt;..." to unstage)
        <span class="c2">modified:   a
        modified:   b
        new file:   d</span></samp></pre>
<p>
  Both commands operate only on the working tree.  They do not alter
  the index.  Therefore the staged changes remain intact in the index.
</p>
<h2 id="reset-index">Reset the Index<a href="#reset-index"></a></h2>
<p>
  Another common situation is when we have staged some changes but
  want to unstage them.  First, we restore the experimental setup:
</p>
<pre><code>date | tee a b c d; git add a b d; echo &gt; b</code></pre>
<p>
  I normally run the following command to do so:
</p>
<pre><code>git reset</code></pre>
<p>
  The modern way to do this is:
</p>
<pre><code>git restore -S .</code></pre>
<p>
  Both commands leave the working tree and the index in the following
  state:
</p>
<pre><samp>$ <kbd>git status</kbd>
On branch main
Changes not staged for commit:
  (use "git add &lt;file&gt;..." to update what will be committed)
  (use "git restore &lt;file&gt;..." to discard changes in working directory)
        <span class="c4">modified:   a
        modified:   b
        modified:   c</span>

Untracked files:
  (use "git add &lt;file&gt;..." to include in what will be committed)
        <span class="c4">d</span>

no changes added to commit (use "git add" and/or "git commit -a")</samp></pre>
<p>
  The <code>-S</code> (<code>--staged</code>) option tells <code>git
  restore</code> to operate on the index (not the working tree) and
  reset the index entries for the specified files to match the
  version in <code>HEAD</code>.  The unstaged changes remain intact as
  modified files in the working tree.  With the <code>-S</code>
  option, no changes are made to the working tree.
</p>
<p>
  From the arguments we can see that the old and new commands are not
  exactly equivalent.  Without any arguments, the <code>git
  reset</code> command resets the entire index to <code>HEAD</code>,
  so all staged changes become unstaged.  Similarly, when we run
  <code>git restore -S</code> without specifying a commit, branch or
  tag using the <code>-s</code> (<code>--source</code>) option, it
  defaults to resetting the index from <code>HEAD</code>.
  The <code>.</code> at the end ensures that all paths under the
  current directory are affected.  When we run the command at the
  top-level directory of the repository, all paths are affected and
  the entire index gets reset.  As a result, both the old and the new
  commands accomplish the same result.
</p>
<h2 id="reset-working-directory-and-index">Reset the Working Tree and Index<a href="#reset-working-directory-and-index"></a></h2>
<p>
  Once again, we restore the experimental setup.
</p>
<pre><code>date | tee a b c d; git add a b d; echo &gt; b</code></pre>
<p>
  This time we not only want to unstage the changes but also discard
  the changes in the working tree.  In other words, we want to reset
  both the working tree and the index from <code>HEAD</code>.  This is
  a dangerous operation because any uncommitted changes discarded in
  this manner cannot be restored using Git.
</p>
<pre><code>git reset --hard</code></pre>
<p>
  The modern way to do this is:
</p>
<pre><code>git restore -WS .</code></pre>
<p>
  The working tree is now clean:
</p>
<pre><samp>$ <kbd>git status</kbd>
On branch main
nothing to commit, working tree clean</samp></pre>
<p>
  The <code>-W</code> (<code>--worktree</code>) option makes the
  command operate on the working tree.  The <code>-S</code>
  (<code>--staged</code>) option resets the index as described in the
  previous section.  As a result, this command unstages any changes
  and discards any modifications in the working tree.
</p>
<p>
  Note that when neither of these options is specified,
  <code>-W</code> is implied by default.  That's why the
  bare <code>git restore .</code> command in the previous section
  discards the changes in the working tree.
</p>
<h2 id="summary">Summary<a href="#summary"></a></h2>
<p>
  The following table summarises how the three pairs of commands
  discussed above affect the working tree and the index, assuming the
  commands are run at the top-level directory of a repository.
</p>
<div style="overflow: auto">
  <table class="grid" style="margin: 0">
    <thead>
      <tr>
        <th>Old</th>
        <th>New</th>
        <th>Working Tree</th>
        <th>Index</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="pre"><code>git checkout .</code></td>
        <td class="pre"><code>git restore .</code></td>
        <td>Reset to match the index.</td>
        <td>No change.</td>
      </tr>
      <tr>
        <td class="pre"><code>git reset</code></td>
        <td class="pre"><code>git restore -S .</code></td>
        <td>No change.</td>
        <td>Reset to match <code>HEAD</code>.</td>
      </tr>
      <tr>
        <td class="pre"><code>git reset --hard</code></td>
        <td class="pre"><code>git restore -SW .</code></td>
        <td>Reset to match <code>HEAD</code>.</td>
        <td>Reset to match <code>HEAD</code>.</td>
      </tr>
    </tbody>
  </table>
</div>
<p>
  The <code>git restore</code> command is meant to provide a clearer
  interface for resetting the working tree and the index.  I still use
  the older commands out of habit.  Perhaps I will adopt the new ones
  in another six years, but at least I have the mapping written down
  now.
</p>
<!-- ### -->
<p>
  <a href="https://susam.net/git-checkout-reset-restore.html">Read on website</a> |
  <a href="https://susam.net/tag/git.html">#git</a> |
  <a href="https://susam.net/tag/technology.html">#technology</a> |
  <a href="https://susam.net/tag/how-to.html">#how-to</a>
</p>
]]>
</description>
</item>


</channel>
</rss>
